Merge "Require CREATE_VIRTUAL_DEVICE permission to register or unregister an AccessibilityDisplayProxy" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 29ab455..e60ed4a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -727,8 +727,11 @@
                     // Exception-throwing-can down the road to JobParameters.completeWork >:(
                     return true;
                 }
-                mService.mJobs.touchJob(mRunningJob);
-                return mRunningJob.completeWorkLocked(workId);
+                if (mRunningJob.completeWorkLocked(workId)) {
+                    mService.mJobs.touchJob(mRunningJob);
+                    return true;
+                }
+                return false;
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 1971a11..537a670 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -828,6 +828,10 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the JobWorkItem queue was updated,
+     * and {@code false} if nothing changed.
+     */
     public boolean completeWorkLocked(int workId) {
         if (executingWork != null) {
             final int N = executingWork.size();
diff --git a/core/api/current.txt b/core/api/current.txt
index 8a6bb7b..2db3eae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6784,7 +6784,7 @@
     method public boolean areNotificationsEnabled();
     method public boolean areNotificationsPaused();
     method public boolean canNotifyAsPackage(@NonNull String);
-    method public boolean canSendFullScreenIntent();
+    method public boolean canUseFullScreenIntent();
     method public void cancel(int);
     method public void cancel(@Nullable String, int);
     method public void cancelAll();
@@ -19527,9 +19527,9 @@
 
   public final class DisplayManager {
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int);
-    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int);
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
-    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method public android.view.Display getDisplay(int);
     method public android.view.Display[] getDisplays();
     method public android.view.Display[] getDisplays(String);
@@ -19584,6 +19584,30 @@
     method public void onStopped();
   }
 
+  public final class VirtualDisplayConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getDensityDpi();
+    method @NonNull public java.util.List<java.lang.String> getDisplayCategories();
+    method public int getFlags();
+    method public int getHeight();
+    method @NonNull public String getName();
+    method public float getRequestedRefreshRate();
+    method @Nullable public android.view.Surface getSurface();
+    method public int getWidth();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.VirtualDisplayConfig> CREATOR;
+  }
+
+  public static final class VirtualDisplayConfig.Builder {
+    ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int);
+    method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String);
+    method @NonNull public android.hardware.display.VirtualDisplayConfig build();
+    method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int);
+    method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float);
+    method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setSurface(@Nullable android.view.Surface);
+  }
+
 }
 
 package android.hardware.fingerprint {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3432ba4..3307a4f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3217,8 +3217,8 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull public android.content.Context createContext();
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
-    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
-    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+    method @Deprecated @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
@@ -3347,10 +3347,15 @@
 
   public interface VirtualSensorCallback {
     method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+    method public default void onDirectChannelConfigured(@IntRange(from=1) int, @NonNull android.companion.virtual.sensor.VirtualSensor, int, @IntRange(from=1) int);
+    method public default void onDirectChannelCreated(@IntRange(from=1) int, @NonNull android.os.SharedMemory);
+    method public default void onDirectChannelDestroyed(@IntRange(from=1) int);
   }
 
   public final class VirtualSensorConfig implements android.os.Parcelable {
     method public int describeContents();
+    method public int getDirectChannelTypesSupported();
+    method public int getHighestDirectReportRateLevel();
     method @NonNull public String getName();
     method public int getType();
     method @Nullable public String getVendor();
@@ -3361,6 +3366,8 @@
   public static final class VirtualSensorConfig.Builder {
     ctor public VirtualSensorConfig.Builder(int, @NonNull String);
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int);
+    method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int);
     method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
   }
 
@@ -10023,7 +10030,7 @@
     method public int describeContents();
     method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo();
     method public int getNetworkSource();
-    method @NonNull public int[] getSecurityTypes();
+    method @NonNull public java.util.Set<java.lang.Integer> getSecurityTypes();
     method @NonNull public String getSsid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.KnownNetwork> CREATOR;
@@ -10033,10 +10040,10 @@
 
   public static final class KnownNetwork.Builder {
     ctor public KnownNetwork.Builder();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder addSecurityType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int);
-    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSecurityTypes(@NonNull int[]);
     method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String);
   }
 
@@ -10105,7 +10112,7 @@
     method public long getDeviceId();
     method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo();
     method @Nullable public String getHotspotBssid();
-    method @Nullable public int[] getHotspotSecurityTypes();
+    method @NonNull public java.util.Set<java.lang.Integer> getHotspotSecurityTypes();
     method @Nullable public String getHotspotSsid();
     method @NonNull public String getNetworkName();
     method public int getNetworkType();
@@ -10119,11 +10126,11 @@
 
   public static final class TetherNetwork.Builder {
     ctor public TetherNetwork.Builder();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder addHotspotSecurityType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceId(long);
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo);
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotBssid(@NonNull String);
-    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSecurityTypes(@NonNull int[]);
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSsid(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkType(int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b0929b5..50275ab 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -36,6 +36,7 @@
 import static android.window.ConfigurationHelper.shouldUpdateResources;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -49,6 +50,7 @@
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupAnnotations.OperationType;
+import android.app.compat.CompatChanges;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -206,6 +208,7 @@
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SafeZipPathValidatorCallback;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.policy.DecorView;
 import com.android.internal.util.ArrayUtils;
@@ -219,6 +222,7 @@
 import dalvik.system.CloseGuard;
 import dalvik.system.VMDebug;
 import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
 
 import libcore.io.ForwardingOs;
 import libcore.io.IoUtils;
@@ -6703,6 +6707,11 @@
         // Let libcore handle any compat changes after installing the list of compat changes.
         AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
 
+        // Initialize the zip path validator callback depending on the targetSdk.
+        // This has to be after AppCompatCallbacks#install() so that the Compat
+        // checks work accordingly.
+        initZipPathValidatorCallback();
+
         mBoundApplication = data;
         mConfigurationController.setConfiguration(data.config);
         mConfigurationController.setCompatConfiguration(data.config);
@@ -7070,6 +7079,18 @@
         }
     }
 
+    /**
+     * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+     * entry names.
+     * Otherwise: clear the callback to the default validation.
+     */
+    private void initZipPathValidatorCallback() {
+        if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+            ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+        } else {
+            ZipPathValidator.clearCallback();
+        }
+    }
 
     private void handleSetContentCaptureOptionsCallback(String packageName) {
         if (mContentCaptureOptionsCallback != null) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index fe40a4c..f48181b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -60,7 +60,6 @@
     private String[] mRequireNoneOfPermissions;
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private long mIdForResponseEvent;
-    private @Nullable IntentFilter mRemoveMatchingFilter;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
     private @Nullable String mDeliveryGroupMatchingKey;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
@@ -190,12 +189,6 @@
             "android:broadcast.idForResponseEvent";
 
     /**
-     * Corresponds to {@link #setRemoveMatchingFilter}.
-     */
-    private static final String KEY_REMOVE_MATCHING_FILTER =
-            "android:broadcast.removeMatchingFilter";
-
-    /**
      * Corresponds to {@link #setDeliveryGroupPolicy(int)}.
      */
     private static final String KEY_DELIVERY_GROUP_POLICY =
@@ -274,18 +267,6 @@
     }
 
     /**
-     * {@hide}
-     * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead.
-     */
-    @Deprecated
-    public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
-            @NonNull IntentFilter removeMatchingFilter) {
-        BroadcastOptions opts = new BroadcastOptions();
-        opts.setRemoveMatchingFilter(removeMatchingFilter);
-        return opts;
-    }
-
-    /**
      * Creates a new {@code BroadcastOptions} with no options initially set.
      */
     public BroadcastOptions() {
@@ -315,8 +296,6 @@
         mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
         mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
-        mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
-                IntentFilter.class);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
         mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
@@ -797,31 +776,6 @@
     }
 
     /**
-     * When enqueuing this broadcast, remove all pending broadcasts previously
-     * sent by this app which match the given filter.
-     * <p>
-     * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
-     * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
-     *
-     * @hide
-     * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead.
-     */
-    @Deprecated
-    public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
-        mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
-    }
-
-    /** @hide */
-    public void clearRemoveMatchingFilter() {
-        mRemoveMatchingFilter = null;
-    }
-
-    /** @hide */
-    public @Nullable IntentFilter getRemoveMatchingFilter() {
-        return mRemoveMatchingFilter;
-    }
-
-    /**
      * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
      * the same delivery group has to be handled.
      *
@@ -1092,9 +1046,6 @@
         if (mIdForResponseEvent != 0) {
             b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
         }
-        if (mRemoveMatchingFilter != null) {
-            b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
-        }
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f653e13..5c38c42 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -341,12 +341,6 @@
             in String message, boolean force, int exceptionTypeId);
     void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName,
             int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras);
-    /** @deprecated -- use getProviderMimeTypeAsync */
-    @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives =
-            "Use {@link android.content.ContentResolver#getType} public API instead.")
-    String getProviderMimeType(in Uri uri, int userId);
-
-    oneway void getProviderMimeTypeAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
     oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
     // Cause the specified process to dump the specified heap.
     boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 91efa75..d2f2c3c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -873,7 +873,7 @@
      * permission to your manifest, and use
      * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}.
      */
-    public boolean canSendFullScreenIntent() {
+    public boolean canUseFullScreenIntent() {
         final int result = PermissionChecker.checkPermissionForPreflight(mContext,
                 android.Manifest.permission.USE_FULL_SCREEN_INTENT,
                 mContext.getAttributionSource());
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 4d2c7cb..6cc4c8a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -89,14 +89,6 @@
 
     private static final String TAG = "VirtualDeviceManager";
 
-    private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
-            DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
-
     /**
      * Broadcast Action: A Virtual Device was removed.
      *
@@ -539,7 +531,11 @@
          * not create the virtual display.
          *
          * @see DisplayManager#createVirtualDisplay
+         *
+         * @deprecated use {@link #createVirtualDisplay(VirtualDisplayConfig, Executor,
+         * VirtualDisplay.Callback)}
          */
+        @Deprecated
         @Nullable
         public VirtualDisplay createVirtualDisplay(
                 @IntRange(from = 1) int width,
@@ -552,30 +548,16 @@
             VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
                     getVirtualDisplayName(), width, height, densityDpi)
                     .setSurface(surface)
-                    .setFlags(getVirtualDisplayFlags(flags))
+                    .setFlags(flags)
                     .build();
-            return createVirtualDisplayInternal(config, executor, callback);
+            return createVirtualDisplay(config, executor, callback);
         }
 
         /**
          * Creates a virtual display for this virtual device. All displays created on the same
          * device belongs to the same display group.
          *
-         * @param width The width of the virtual display in pixels, must be greater than 0.
-         * @param height The height of the virtual display in pixels, must be greater than 0.
-         * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
-         * @param displayCategories The categories of the virtual display, indicating the type of
-         * activities allowed to run on the display. Activities can declare their type using
-         * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
-         * @param surface The surface to which the content of the virtual display should
-         * be rendered, or null if there is none initially. The surface can also be set later using
-         * {@link VirtualDisplay#setSurface(Surface)}.
-         * @param flags A combination of virtual display flags accepted by
-         * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
-         * automatically set for all virtual devices:
-         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
-         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
-         * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+         * @param config The configuration of the display.
          * @param executor The executor on which {@code callback} will be invoked. This is ignored
          * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
          * not be null.
@@ -587,28 +569,6 @@
          */
         @Nullable
         public VirtualDisplay createVirtualDisplay(
-                @IntRange(from = 1) int width,
-                @IntRange(from = 1) int height,
-                @IntRange(from = 1) int densityDpi,
-                @NonNull List<String> displayCategories,
-                @Nullable Surface surface,
-                @VirtualDisplayFlag int flags,
-                @Nullable @CallbackExecutor Executor executor,
-                @Nullable VirtualDisplay.Callback callback) {
-            VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
-                    getVirtualDisplayName(), width, height, densityDpi)
-                    .setDisplayCategories(displayCategories)
-                    .setSurface(surface)
-                    .setFlags(getVirtualDisplayFlags(flags))
-                    .build();
-            return createVirtualDisplayInternal(config, executor, callback);
-        }
-
-        /**
-         * @hide
-         */
-        @Nullable
-        private VirtualDisplay createVirtualDisplayInternal(
                 @NonNull VirtualDisplayConfig config,
                 @Nullable @CallbackExecutor Executor executor,
                 @Nullable VirtualDisplay.Callback callback) {
@@ -897,16 +857,6 @@
             }
         }
 
-        /**
-         * Returns the display flags that should be added to a particular virtual display.
-         * Additional device-level flags from {@link
-         * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will
-         * be added by DisplayManagerService.
-         */
-        private int getVirtualDisplayFlags(int flags) {
-            return DEFAULT_VIRTUAL_DISPLAY_FLAGS | flags;
-        }
-
         private String getVirtualDisplayName() {
             try {
                 // Currently this just use the device ID, which means all of the virtual displays
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d8076b5..9f3b601 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -35,6 +35,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SharedMemory;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -577,6 +578,25 @@
                 mExecutor.execute(() -> mCallback.onConfigurationChanged(
                         sensor, enabled, samplingPeriod, batchReportingLatency));
             }
+
+            @Override
+            public void onDirectChannelCreated(int channelHandle,
+                    @NonNull SharedMemory sharedMemory) {
+                mExecutor.execute(
+                        () -> mCallback.onDirectChannelCreated(channelHandle, sharedMemory));
+            }
+
+            @Override
+            public void onDirectChannelDestroyed(int channelHandle) {
+                mExecutor.execute(() -> mCallback.onDirectChannelDestroyed(channelHandle));
+            }
+
+            @Override
+            public void onDirectChannelConfigured(int channelHandle, @NonNull VirtualSensor sensor,
+                    int rateLevel, int reportToken) {
+                mExecutor.execute(() -> mCallback.onDirectChannelConfigured(
+                        channelHandle, sensor, rateLevel, reportToken));
+            }
         }
 
         /**
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
index 7da9c32..3cb0572 100644
--- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
@@ -17,6 +17,7 @@
 package android.companion.virtual.sensor;
 
 import android.companion.virtual.sensor.VirtualSensor;
+import android.os.SharedMemory;
 
 /**
  * Interface for notifying the sensor owner about whether and how sensor events should be injected.
@@ -36,4 +37,31 @@
      */
     void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros,
             int batchReportLatencyMicros);
+
+    /**
+     * Called when a sensor direct channel is created.
+     *
+     * @param channelHandle Identifier of the channel that was created.
+     * @param sharedMemory The shared memory region for the direct sensor channel.
+     */
+    void onDirectChannelCreated(int channelHandle, in SharedMemory sharedMemory);
+
+    /**
+     * Called when a sensor direct channel is destroyed.
+     *
+     * @param channelHandle Identifier of the channel that was destroyed.
+     */
+    void onDirectChannelDestroyed(int channelHandle);
+
+    /**
+     * Called when a sensor direct channel is configured.
+     *
+     * @param channelHandle Identifier of the channel that was configured.
+     * @param sensor The sensor, for which the channel was configured.
+     * @param rateLevel The rate level used to configure the direct sensor channel.
+     * @param reportToken A positive sensor report token, used to differentiate between events from
+     * different sensors within the same channel.
+     */
+    void onDirectChannelConfigured(int channelHandle, in VirtualSensor sensor, int rateLevel,
+            int reportToken);
 }
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
index e097189..f7af283 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
@@ -17,8 +17,13 @@
 package android.companion.virtual.sensor;
 
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.os.MemoryFile;
+import android.os.SharedMemory;
 
 import java.time.Duration;
 
@@ -50,4 +55,74 @@
      */
     void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
             @NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency);
+
+    /**
+     * Called when a {@link android.hardware.SensorDirectChannel} is created.
+     *
+     * <p>The {@link android.hardware.SensorManager} instance used to create the direct channel must
+     * be associated with the virtual device.
+     *
+     * <p>A typical order of callback invocations is:
+     * <ul>
+     *     <li>{@code onDirectChannelCreated} - the channel handle and the associated shared memory
+     *     should be stored by the virtual device</li>
+     *     <li>{@code onDirectChannelConfigured} with a positive {@code rateLevel} - the virtual
+     *     device should start writing to the shared memory for the associated channel with the
+     *     requested parameters.</li>
+     *     <li>{@code onDirectChannelConfigured} with a {@code rateLevel = RATE_STOP} - the virtual
+     *     device should stop writing to the shared memory for the associated channel.</li>
+     *     <li>{@code onDirectChannelDestroyed} - the shared memory associated with the channel
+     *     handle should be closed.</li>
+     * </ul>
+     *
+     * @param channelHandle Identifier of the newly created channel.
+     * @param sharedMemory writable shared memory region.
+     *
+     * @see android.hardware.SensorManager#createDirectChannel(MemoryFile)
+     * @see #onDirectChannelConfigured
+     * @see #onDirectChannelDestroyed
+     */
+    default void onDirectChannelCreated(@IntRange(from = 1) int channelHandle,
+            @NonNull SharedMemory sharedMemory) {}
+
+    /**
+     * Called when a {@link android.hardware.SensorDirectChannel} is destroyed.
+     *
+     * <p>The virtual device must perform any clean-up and close the shared memory that was
+     * received with the {@link #onDirectChannelCreated} callback and the corresponding
+     * {@code channelHandle}.
+     *
+     * @param channelHandle Identifier of the channel that was destroyed.
+     *
+     * @see SensorDirectChannel#close()
+     */
+    default void onDirectChannelDestroyed(@IntRange(from = 1) int channelHandle) {}
+
+    /**
+     * Called when a {@link android.hardware.SensorDirectChannel} is configured.
+     *
+     * <p>Sensor events for the corresponding sensor should be written at the indicated rate to the
+     * shared memory region that was received with the {@link #onDirectChannelCreated} callback and
+     * the corresponding {@code channelHandle}. The events should be written in the correct format
+     * and with the provided {@code reportToken} until the channel is reconfigured with
+     * {@link SensorDirectChannel#RATE_STOP}.
+     *
+     * <p>The sensor must support direct channel in order for this callback to be invoked. Only
+     * {@link MemoryFile} sensor direct channels are supported for virtual sensors.
+     *
+     * @param channelHandle Identifier of the channel that was configured.
+     * @param sensor The sensor, for which the channel was configured.
+     * @param rateLevel The rate level used to configure the direct sensor channel.
+     * @param reportToken A positive sensor report token, used to differentiate between events from
+     * different sensors within the same channel.
+     *
+     * @see VirtualSensorConfig.Builder#setHighestDirectReportRateLevel(int)
+     * @see VirtualSensorConfig.Builder#setDirectChannelTypesSupported(int)
+     * @see android.hardware.SensorManager#createDirectChannel(MemoryFile)
+     * @see #onDirectChannelCreated
+     * @see SensorDirectChannel#configure(Sensor, int)
+     */
+    default void onDirectChannelConfigured(@IntRange(from = 1) int channelHandle,
+            @NonNull VirtualSensor sensor, @SensorDirectChannel.RateLevel int rateLevel,
+            @IntRange(from = 1) int reportToken) {}
 }
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 6d45365..ffbdff8 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -21,11 +21,13 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.Objects;
 
+
 /**
  * Configuration for creation of a virtual sensor.
  * @see VirtualSensor
@@ -33,6 +35,14 @@
  */
 @SystemApi
 public final class VirtualSensorConfig implements Parcelable {
+    private static final String TAG = "VirtualSensorConfig";
+
+    // Mask for direct mode highest rate level, bit 7, 8, 9.
+    private static final int DIRECT_REPORT_MASK = 0x380;
+    private static final int DIRECT_REPORT_SHIFT = 7;
+
+    // Mask for supported direct channel, bit 10, 11
+    private static final int DIRECT_CHANNEL_SHIFT = 10;
 
     private final int mType;
     @NonNull
@@ -40,16 +50,21 @@
     @Nullable
     private final String mVendor;
 
-    private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor) {
+    private final int mFlags;
+
+    private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+            int flags) {
         mType = type;
         mName = name;
         mVendor = vendor;
+        mFlags = flags;
     }
 
     private VirtualSensorConfig(@NonNull Parcel parcel) {
         mType = parcel.readInt();
         mName = parcel.readString8();
         mVendor = parcel.readString8();
+        mFlags = parcel.readInt();
     }
 
     @Override
@@ -62,6 +77,7 @@
         parcel.writeInt(mType);
         parcel.writeString8(mName);
         parcel.writeString8(mVendor);
+        parcel.writeInt(mFlags);
     }
 
     /**
@@ -92,22 +108,64 @@
     }
 
     /**
+     * Returns the highest supported direct report mode rate level of the sensor.
+     *
+     * @see Sensor#getHighestDirectReportRateLevel()
+     */
+    @SensorDirectChannel.RateLevel
+    public int getHighestDirectReportRateLevel() {
+        int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT);
+        return rateLevel <= SensorDirectChannel.RATE_VERY_FAST
+                ? rateLevel : SensorDirectChannel.RATE_VERY_FAST;
+    }
+
+    /**
+     * Returns a combination of all supported direct channel types.
+     *
+     * @see Builder#setDirectChannelTypesSupported(int)
+     * @see Sensor#isDirectChannelTypeSupported(int)
+     */
+    public @SensorDirectChannel.MemoryType int getDirectChannelTypesSupported() {
+        int memoryTypes = 0;
+        if ((mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0) {
+            memoryTypes |= SensorDirectChannel.TYPE_MEMORY_FILE;
+        }
+        if ((mFlags & (1 << (DIRECT_CHANNEL_SHIFT + 1))) > 0) {
+            memoryTypes |= SensorDirectChannel.TYPE_HARDWARE_BUFFER;
+        }
+        return memoryTypes;
+    }
+
+    /**
+     * Returns the sensor flags.
+     * @hide
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
      * Builder for {@link VirtualSensorConfig}.
      */
     public static final class Builder {
 
+        private static final int FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED =
+                1 << DIRECT_CHANNEL_SHIFT;
         private final int mType;
         @NonNull
         private final String mName;
         @Nullable
         private String mVendor;
+        private int mFlags;
+        @SensorDirectChannel.RateLevel
+        int mHighestDirectReportRateLevel;
 
         /**
          * Creates a new builder.
          *
          * @param type The type of the sensor, matching {@link Sensor#getType}.
          * @param name The name of the sensor. Must be unique among all sensors with the same type
-         * that belong to the same virtual device.
+         *             that belong to the same virtual device.
          */
         public Builder(int type, @NonNull String name) {
             mType = type;
@@ -119,7 +177,19 @@
          */
         @NonNull
         public VirtualSensorConfig build() {
-            return new VirtualSensorConfig(mType, mName, mVendor);
+            if (mHighestDirectReportRateLevel > 0) {
+                if ((mFlags & FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED) == 0) {
+                    throw new IllegalArgumentException("Setting direct channel type is required "
+                            + "for sensors with direct channel support.");
+                }
+                mFlags |= mHighestDirectReportRateLevel << DIRECT_REPORT_SHIFT;
+            }
+            if ((mFlags & FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED) > 0
+                    && mHighestDirectReportRateLevel == 0) {
+                throw new IllegalArgumentException("Highest direct report rate level is "
+                        + "required for sensors with direct channel support.");
+            }
+            return new VirtualSensorConfig(mType, mName, mVendor, mFlags);
         }
 
         /**
@@ -130,6 +200,44 @@
             mVendor = vendor;
             return this;
         }
+
+        /**
+         * Sets the highest supported rate level for direct sensor report.
+         *
+         * @see VirtualSensorConfig#getHighestDirectReportRateLevel()
+         */
+        @NonNull
+        public VirtualSensorConfig.Builder setHighestDirectReportRateLevel(
+                @SensorDirectChannel.RateLevel int rateLevel) {
+            mHighestDirectReportRateLevel = rateLevel;
+            return this;
+        }
+
+        /**
+         * Sets whether direct sensor channel of the given types is supported.
+         *
+         * @param memoryTypes A combination of {@link SensorDirectChannel.MemoryType} flags
+         * indicating the types of shared memory supported for creating direct channels. Only
+         * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for virtual
+         * sensors.
+         * @throws IllegalArgumentException if {@link SensorDirectChannel#TYPE_HARDWARE_BUFFER} is
+         * set to be supported.
+         */
+        @NonNull
+        public VirtualSensorConfig.Builder setDirectChannelTypesSupported(
+                @SensorDirectChannel.MemoryType int memoryTypes) {
+            if ((memoryTypes & SensorDirectChannel.TYPE_MEMORY_FILE) > 0) {
+                mFlags |= FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED;
+            } else {
+                mFlags &= ~FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED;
+            }
+            if ((memoryTypes & ~SensorDirectChannel.TYPE_MEMORY_FILE) > 0) {
+                throw new IllegalArgumentException(
+                        "Only TYPE_MEMORY_FILE direct channels can be supported for virtual "
+                                + "sensors.");
+            }
+            return this;
+        }
     }
 
     @NonNull
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 795c77f..d3502c5 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -389,6 +389,8 @@
 
         @Override
         public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) {
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             final Bundle result = new Bundle();
             try {
                 result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri));
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index ffdc7b38..6b09c30 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.MathUtils;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -98,22 +99,81 @@
     public static FontScaleConverter forScale(float fontScale) {
         if (fontScale <= 1) {
             // We don't need non-linear curves for shrinking text or for 100%.
-            // Also, fontScale==0 should not have a curve either
+            // Also, fontScale==0 should not have a curve either.
+            // And ignore negative font scales; that's just silly.
             return null;
         }
 
         FontScaleConverter lookupTable = get(fontScale);
-        // TODO(b/247861716): interpolate between two tables when null
+        if (lookupTable != null) {
+            return lookupTable;
+        }
 
-        return lookupTable;
+        // Didn't find an exact match: interpolate between two existing tables
+        final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale));
+        if (index >= 0) {
+            // This should never happen, should have been covered by get() above.
+            return LOOKUP_TABLES.valueAt(index);
+        }
+        // Didn't find an exact match: interpolate between two existing tables
+        final int lowerIndex = -(index + 1) - 1;
+        final int higherIndex = lowerIndex + 1;
+        if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) {
+            // We have gone beyond our bounds and have nothing to interpolate between. Just give
+            // them a straight linear table instead.
+            // This works because when FontScaleConverter encounters a size beyond its bounds, it
+            // calculates a linear fontScale factor using the ratio of the last element pair.
+            return new FontScaleConverter(new float[] {1f}, new float[] {fontScale});
+        } else {
+            float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex));
+            float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex));
+            float interpolationPoint = MathUtils.constrainedMap(
+                    /* rangeMin= */ 0f,
+                    /* rangeMax= */ 1f,
+                    startScale,
+                    endScale,
+                    fontScale
+            );
+            return createInterpolatedTableBetween(
+                    LOOKUP_TABLES.valueAt(lowerIndex),
+                    LOOKUP_TABLES.valueAt(higherIndex),
+                    interpolationPoint);
+        }
+    }
+
+    @NonNull
+    private static FontScaleConverter createInterpolatedTableBetween(
+            FontScaleConverter start,
+            FontScaleConverter end,
+            float interpolationPoint
+    ) {
+        float[] commonSpSizes = new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100f};
+        float[] dpInterpolated = new float[commonSpSizes.length];
+
+        for (int i = 0; i < commonSpSizes.length; i++) {
+            float sp = commonSpSizes[i];
+            float startDp = start.convertSpToDp(sp);
+            float endDp = end.convertSpToDp(sp);
+            dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint);
+        }
+
+        return new FontScaleConverter(commonSpSizes, dpInterpolated);
+    }
+
+    private static int getKey(float fontScale) {
+        return (int) (fontScale * SCALE_KEY_MULTIPLIER);
+    }
+
+    private static float getScaleFromKey(int key) {
+        return (float) key / SCALE_KEY_MULTIPLIER;
     }
 
     private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) {
-        LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter);
+        LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter);
     }
 
     @Nullable
     private static FontScaleConverter get(float scaleKey) {
-        return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER));
+        return LOOKUP_TABLES.get(getKey(scaleKey));
     }
 }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1a3e0b0..73157e6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -92,7 +92,8 @@
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
-            long nativeInstance, long size, int channelType, int fd, HardwareBuffer buffer);
+            long nativeInstance, int deviceId, long size, int channelType, int fd,
+            HardwareBuffer buffer);
     private static native void nativeDestroyDirectChannel(
             long nativeInstance, int channelHandle);
     private static native int nativeConfigDirectChannel(
@@ -695,6 +696,10 @@
     /** @hide */
     protected SensorDirectChannel createDirectChannelImpl(
             MemoryFile memoryFile, HardwareBuffer hardwareBuffer) {
+        int deviceId = mContext.getDeviceId();
+        if (isDeviceSensorPolicyDefault(deviceId)) {
+            deviceId = DEVICE_ID_DEFAULT;
+        }
         int id;
         int type;
         long size;
@@ -713,8 +718,8 @@
             }
 
             size = memoryFile.length();
-            id = nativeCreateDirectChannel(
-                    mNativeInstance, size, SensorDirectChannel.TYPE_MEMORY_FILE, fd, null);
+            id = nativeCreateDirectChannel(mNativeInstance, deviceId, size,
+                    SensorDirectChannel.TYPE_MEMORY_FILE, fd, null);
             if (id <= 0) {
                 throw new UncheckedIOException(
                         new IOException("create MemoryFile direct channel failed " + id));
@@ -738,7 +743,7 @@
             }
             size = hardwareBuffer.getWidth();
             id = nativeCreateDirectChannel(
-                    mNativeInstance, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER,
+                    mNativeInstance, deviceId, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER,
                     -1, hardwareBuffer);
             if (id <= 0) {
                 throw new UncheckedIOException(
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b766cd1..50dd7a0 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -989,24 +989,6 @@
 
     /**
      * Creates a virtual display.
-     *
-     * @see #createVirtualDisplay(String, int, int, int, float, Surface, int,
-     * Handler, VirtualDisplay.Callback)
-     */
-    @Nullable
-    public VirtualDisplay createVirtualDisplay(@NonNull String name,
-            @IntRange(from = 1) int width,
-            @IntRange(from = 1) int height,
-            @IntRange(from = 1) int densityDpi,
-            float requestedRefreshRate,
-            @Nullable Surface surface,
-            @VirtualDisplayFlag int flags) {
-        return createVirtualDisplay(name, width, height, densityDpi, requestedRefreshRate,
-                surface, flags, null, null);
-    }
-
-    /**
-     * Creates a virtual display.
      * <p>
      * The content of a virtual display is rendered to a {@link Surface} provided
      * by the application.
@@ -1056,8 +1038,23 @@
             @VirtualDisplayFlag int flags,
             @Nullable VirtualDisplay.Callback callback,
             @Nullable Handler handler) {
-        return createVirtualDisplay(name, width, height, densityDpi, 0.0f, surface,
-                flags, handler, callback);
+        final VirtualDisplayConfig.Builder builder =
+                new VirtualDisplayConfig.Builder(name, width, height, densityDpi);
+        builder.setFlags(flags);
+        if (surface != null) {
+            builder.setSurface(surface);
+        }
+        return createVirtualDisplay(builder.build(), handler, callback);
+    }
+
+    /**
+     * Creates a virtual display.
+     *
+     * @see #createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
+     */
+    @Nullable
+    public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig config) {
+        return createVirtualDisplay(config, /*handler=*/null, /*callback=*/null);
     }
 
     /**
@@ -1084,21 +1081,7 @@
      * turning off the screen.
      * </p>
      *
-     * @param name The name of the virtual display, must be non-empty.
-     * @param width The width of the virtual display in pixels, must be greater than 0.
-     * @param height The height of the virtual display in pixels, must be greater than 0.
-     * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
-     * @param requestedRefreshRate The requested refresh rate in frames per second.
-     * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
-     * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
-     * up or down to a divisor of the physical display. If 0 is specified, the virtual
-     * display is refreshed at the physical display refresh rate.
-     * @param surface The surface to which the content of the virtual display should
-     * be rendered, or null if there is none initially.
-     * @param flags A combination of virtual display flags:
-     * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
-     * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
-     * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+     * @param config The configuration of the virtual display, must be non-null.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
      * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
@@ -1106,33 +1089,14 @@
      * not create the virtual display.
      *
      * @throws SecurityException if the caller does not have permission to create
-     * a virtual display with the specified flags.
+     * a virtual display with flags specified in the configuration.
      */
     @Nullable
-    public VirtualDisplay createVirtualDisplay(@NonNull String name,
-            @IntRange(from = 1) int width,
-            @IntRange(from = 1) int height,
-            @IntRange(from = 1) int densityDpi,
-            float requestedRefreshRate,
-            @Nullable Surface surface,
-            @VirtualDisplayFlag int flags,
+    public VirtualDisplay createVirtualDisplay(
+            @NonNull VirtualDisplayConfig config,
             @Nullable Handler handler,
             @Nullable VirtualDisplay.Callback callback) {
-        if (!ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE && requestedRefreshRate != 0.0f) {
-            Slog.e(TAG, "Please turn on ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE to use the new api");
-            return null;
-        }
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
-                height, densityDpi);
-        builder.setFlags(flags);
-        if (surface != null) {
-            builder.setSurface(surface);
-        }
-        if (requestedRefreshRate != 0.0f) {
-            builder.setRequestedRefreshRate(requestedRefreshRate);
-        }
-        return createVirtualDisplay(null /* projection */, builder.build(), callback, handler,
+        return createVirtualDisplay(null /* projection */, config, callback, handler,
                 null /* windowContext */);
     }
 
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index f6a2e33..6b56a06 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -18,121 +18,44 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManager.VirtualDisplayFlag;
 import android.media.projection.MediaProjection;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.Display;
 import android.view.Surface;
 
-import com.android.internal.util.DataClass;
-
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
- * Holds configuration used to create {@link VirtualDisplay} instances. See
- * {@link MediaProjection#createVirtualDisplay} and
- * {@link android.companion.virtual.VirtualDeviceManager.VirtualDevice#createVirtualDisplay}.
+ * Holds configuration used to create {@link VirtualDisplay} instances.
  *
- * @hide
+ * @see DisplayManager#createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
+ * @see MediaProjection#createVirtualDisplay
  */
-@DataClass(genParcelable = true, genAidl = true, genBuilder = true)
 public final class VirtualDisplayConfig implements Parcelable {
-    /**
-     * The name of the virtual display, must be non-empty.
-     */
-    @NonNull
-    private String mName;
 
-    /**
-     * The width of the virtual display in pixels. Must be greater than 0.
-     */
-    @IntRange(from = 1)
-    private int mWidth;
+    private final String mName;
+    private final int mWidth;
+    private final int mHeight;
+    private final int mDensityDpi;
+    private final int mFlags;
+    private final Surface mSurface;
+    private final String mUniqueId;
+    private final int mDisplayIdToMirror;
+    private final boolean mWindowManagerMirroring;
+    private ArrayList<String> mDisplayCategories = null;
+    private final float mRequestedRefreshRate;
 
-    /**
-     * The height of the virtual display in pixels. Must be greater than 0.
-     */
-    @IntRange(from = 1)
-    private int mHeight;
-
-    /**
-     * The density of the virtual display in dpi. Must be greater than 0.
-     */
-    @IntRange(from = 1)
-    private int mDensityDpi;
-
-    /**
-     * A combination of virtual display flags.
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
-     * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
-     */
-    @VirtualDisplayFlag
-    private int mFlags = 0;
-
-    /**
-     * The surface to which the content of the virtual display should be rendered, or null if
-     * there is none initially.
-     */
-    @Nullable
-    private Surface mSurface = null;
-
-    /**
-     * The unique identifier for the display. Shouldn't be displayed to the user.
-     * @hide
-     */
-    @Nullable
-    private String mUniqueId = null;
-
-    /**
-     * The id of the display that the virtual display should mirror, or
-     * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
-     */
-    private int mDisplayIdToMirror = DEFAULT_DISPLAY;
-
-    /**
-     * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
-     * if DisplayManager should record contents instead.
-     */
-    private boolean mWindowManagerMirroring = false;
-
-    /**
-     * The display categories. If set, only corresponding activities from the same category can be
-     * shown on the display.
-     */
-    @DataClass.PluralOf("displayCategory")
-    @NonNull private List<String> mDisplayCategories = new ArrayList<>();
-
-    /**
-     * The refresh rate of a virtual display in frames per second.
-     * If this value is non-zero, this is the requested refresh rate to set.
-     * If this value is zero, the system chooses a default refresh rate.
-     */
-    private float mRequestedRefreshRate = 0.0f;
-
-
-
-    // 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/hardware/display/VirtualDisplayConfig.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 */ VirtualDisplayConfig(
+    private VirtualDisplayConfig(
             @NonNull String name,
             @IntRange(from = 1) int width,
             @IntRange(from = 1) int height,
@@ -142,219 +65,200 @@
             @Nullable String uniqueId,
             int displayIdToMirror,
             boolean windowManagerMirroring,
-            @NonNull List<String> displayCategories,
+            @NonNull ArrayList<String> displayCategories,
             float requestedRefreshRate) {
-        this.mName = name;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mName);
-        this.mWidth = width;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mWidth,
-                "from", 1);
-        this.mHeight = height;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mHeight,
-                "from", 1);
-        this.mDensityDpi = densityDpi;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mDensityDpi,
-                "from", 1);
-        this.mFlags = flags;
-        com.android.internal.util.AnnotationValidations.validate(
-                VirtualDisplayFlag.class, null, mFlags);
-        this.mSurface = surface;
-        this.mUniqueId = uniqueId;
-        this.mDisplayIdToMirror = displayIdToMirror;
-        this.mWindowManagerMirroring = windowManagerMirroring;
-        this.mDisplayCategories = displayCategories;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mDisplayCategories);
-        this.mRequestedRefreshRate = requestedRefreshRate;
-
-        // onConstructed(); // You can define this method to get a callback
+        mName = name;
+        mWidth = width;
+        mHeight = height;
+        mDensityDpi = densityDpi;
+        mFlags = flags;
+        mSurface = surface;
+        mUniqueId = uniqueId;
+        mDisplayIdToMirror = displayIdToMirror;
+        mWindowManagerMirroring = windowManagerMirroring;
+        mDisplayCategories = displayCategories;
+        mRequestedRefreshRate = requestedRefreshRate;
     }
 
     /**
-     * The name of the virtual display, must be non-empty.
+     * Returns the name of the virtual display.
      */
-    @DataClass.Generated.Member
-    public @NonNull String getName() {
+    @NonNull
+    public String getName() {
         return mName;
     }
 
     /**
-     * The width of the virtual display in pixels. Must be greater than 0.
+     * Returns the width of the virtual display in pixels.
      */
-    @DataClass.Generated.Member
-    public @IntRange(from = 1) int getWidth() {
+    public int getWidth() {
         return mWidth;
     }
 
     /**
-     * The height of the virtual display in pixels. Must be greater than 0.
+     * Returns the height of the virtual display in pixels.
      */
-    @DataClass.Generated.Member
-    public @IntRange(from = 1) int getHeight() {
+    public int getHeight() {
         return mHeight;
     }
 
     /**
-     * The density of the virtual display in dpi. Must be greater than 0.
+     * Returns the density of the virtual display in dpi.
      */
-    @DataClass.Generated.Member
-    public @IntRange(from = 1) int getDensityDpi() {
+    public int getDensityDpi() {
         return mDensityDpi;
     }
 
     /**
-     * A combination of virtual display flags.
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
-     * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
-     * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+     * Returns the virtual display flags.
+     *
+     * @see Builder#setFlags
      */
-    @DataClass.Generated.Member
-    public @VirtualDisplayFlag int getFlags() {
+    public int getFlags() {
         return mFlags;
     }
 
     /**
-     * The surface to which the content of the virtual display should be rendered, or null if
-     * there is none initially.
+     * Returns the surface to which the content of the virtual display should be rendered, if any.
+     *
+     * @see Builder#setSurface
      */
-    @DataClass.Generated.Member
-    public @Nullable Surface getSurface() {
+    @Nullable
+    public Surface getSurface() {
         return mSurface;
     }
 
     /**
-     * The unique identifier for the display. Shouldn't be displayed to the user.
-     *
+     * Returns the unique identifier for the display. Shouldn't be displayed to the user.
      * @hide
      */
-    @DataClass.Generated.Member
-    public @Nullable String getUniqueId() {
+    @Nullable
+    public String getUniqueId() {
         return mUniqueId;
     }
 
     /**
-     * The id of the display that the virtual display should mirror, or
-     * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+     * Returns the id of the display that the virtual display should mirror, or
+     * {@link android.view.Display#DEFAULT_DISPLAY} if there is none.
+     * @hide
      */
-    @DataClass.Generated.Member
     public int getDisplayIdToMirror() {
         return mDisplayIdToMirror;
     }
 
     /**
-     * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
+     * Whether if WindowManager is responsible for mirroring content to this VirtualDisplay, or
      * if DisplayManager should record contents instead.
+     * @hide
      */
-    @DataClass.Generated.Member
     public boolean isWindowManagerMirroring() {
         return mWindowManagerMirroring;
     }
 
     /**
-     * The display categories. If set, only corresponding activities from the same category can be
-     * shown on the display.
+     * Returns the display categories.
+     *
+     * @see Builder#setDisplayCategories
      */
-    @DataClass.Generated.Member
-    public @NonNull List<String> getDisplayCategories() {
-        return mDisplayCategories;
+    @NonNull
+    public List<String> getDisplayCategories() {
+        return Collections.unmodifiableList(mDisplayCategories);
     }
 
     /**
-     * The refresh rate of a virtual display in frames per second.
-     * If this value is none zero, this is the requested refresh rate to set.
-     * If this value is zero, the system chooses a default refresh rate.
+     * Returns the refresh rate of a virtual display in frames per second, or zero if it is using a
+     * default refresh rate chosen by the system.
+     *
+     * @see Builder#setRequestedRefreshRate
      */
-    @DataClass.Generated.Member
     public float getRequestedRefreshRate() {
         return mRequestedRefreshRate;
     }
 
     @Override
-    @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        int flg = 0;
-        if (mWindowManagerMirroring) flg |= 0x100;
-        if (mSurface != null) flg |= 0x20;
-        if (mUniqueId != null) flg |= 0x40;
-        dest.writeInt(flg);
-        dest.writeString(mName);
+        dest.writeString8(mName);
         dest.writeInt(mWidth);
         dest.writeInt(mHeight);
         dest.writeInt(mDensityDpi);
         dest.writeInt(mFlags);
-        if (mSurface != null) dest.writeTypedObject(mSurface, flags);
-        if (mUniqueId != null) dest.writeString(mUniqueId);
+        dest.writeTypedObject(mSurface, flags);
+        dest.writeString8(mUniqueId);
         dest.writeInt(mDisplayIdToMirror);
+        dest.writeBoolean(mWindowManagerMirroring);
         dest.writeStringList(mDisplayCategories);
         dest.writeFloat(mRequestedRefreshRate);
     }
 
     @Override
-    @DataClass.Generated.Member
     public int describeContents() { return 0; }
 
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ VirtualDisplayConfig(@NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        int flg = in.readInt();
-        boolean windowManagerMirroring = (flg & 0x100) != 0;
-        String name = in.readString();
-        int width = in.readInt();
-        int height = in.readInt();
-        int densityDpi = in.readInt();
-        int flags = in.readInt();
-        Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
-        String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
-        int displayIdToMirror = in.readInt();
-        List<String> displayCategories = new ArrayList<>();
-        in.readStringList(displayCategories);
-        float requestedRefreshRate = in.readFloat();
-
-        this.mName = name;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mName);
-        this.mWidth = width;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mWidth,
-                "from", 1);
-        this.mHeight = height;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mHeight,
-                "from", 1);
-        this.mDensityDpi = densityDpi;
-        com.android.internal.util.AnnotationValidations.validate(
-                IntRange.class, null, mDensityDpi,
-                "from", 1);
-        this.mFlags = flags;
-        com.android.internal.util.AnnotationValidations.validate(
-                VirtualDisplayFlag.class, null, mFlags);
-        this.mSurface = surface;
-        this.mUniqueId = uniqueId;
-        this.mDisplayIdToMirror = displayIdToMirror;
-        this.mWindowManagerMirroring = windowManagerMirroring;
-        this.mDisplayCategories = displayCategories;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mDisplayCategories);
-        this.mRequestedRefreshRate = requestedRefreshRate;
-
-        // onConstructed(); // You can define this method to get a callback
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof VirtualDisplayConfig)) {
+            return false;
+        }
+        VirtualDisplayConfig that = (VirtualDisplayConfig) o;
+        return Objects.equals(mName, that.mName)
+                && mWidth == that.mWidth
+                && mHeight == that.mHeight
+                && mDensityDpi == that.mDensityDpi
+                && mFlags == that.mFlags
+                && Objects.equals(mSurface, that.mSurface)
+                && Objects.equals(mUniqueId, that.mUniqueId)
+                && mDisplayIdToMirror == that.mDisplayIdToMirror
+                && mWindowManagerMirroring == that.mWindowManagerMirroring
+                && Objects.equals(mDisplayCategories, that.mDisplayCategories)
+                && mRequestedRefreshRate == that.mRequestedRefreshRate;
     }
 
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<VirtualDisplayConfig> CREATOR
+    @Override
+    public int hashCode() {
+        int hashCode = Objects.hash(
+                mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
+                mDisplayIdToMirror, mWindowManagerMirroring, mDisplayCategories,
+                mRequestedRefreshRate);
+        return hashCode;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return "VirtualDisplayConfig("
+                + " mName=" + mName
+                + " mHeight=" + mHeight
+                + " mWidth=" + mWidth
+                + " mDensityDpi=" + mDensityDpi
+                + " mFlags=" + mFlags
+                + " mSurface=" + mSurface
+                + " mUniqueId=" + mUniqueId
+                + " mDisplayIdToMirror=" + mDisplayIdToMirror
+                + " mWindowManagerMirroring=" + mWindowManagerMirroring
+                + " mDisplayCategories=" + mDisplayCategories
+                + " mRequestedRefreshRate=" + mRequestedRefreshRate
+                + ")";
+    }
+
+    private VirtualDisplayConfig(@NonNull Parcel in) {
+        mName = in.readString8();
+        mWidth = in.readInt();
+        mHeight = in.readInt();
+        mDensityDpi = in.readInt();
+        mFlags = in.readInt();
+        mSurface = in.readTypedObject(Surface.CREATOR);
+        mUniqueId = in.readString8();
+        mDisplayIdToMirror = in.readInt();
+        mWindowManagerMirroring = in.readBoolean();
+        mDisplayCategories = new ArrayList<>();
+        in.readStringList(mDisplayCategories);
+        mRequestedRefreshRate = in.readFloat();
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VirtualDisplayConfig> CREATOR
             = new Parcelable.Creator<VirtualDisplayConfig>() {
         @Override
         public VirtualDisplayConfig[] newArray(int size) {
@@ -368,229 +272,161 @@
     };
 
     /**
-     * A builder for {@link VirtualDisplayConfig}
+     * A builder for {@link VirtualDisplayConfig}.
      */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
     public static final class Builder {
-
-        private @NonNull String mName;
-        private @IntRange(from = 1) int mWidth;
-        private @IntRange(from = 1) int mHeight;
-        private @IntRange(from = 1) int mDensityDpi;
-        private @VirtualDisplayFlag int mFlags;
-        private @Nullable Surface mSurface;
-        private @Nullable String mUniqueId;
-        private int mDisplayIdToMirror;
-        private boolean mWindowManagerMirroring;
-        private @NonNull List<String> mDisplayCategories;
-        private float mRequestedRefreshRate;
-
-        private long mBuilderFieldsSet = 0L;
+        private final String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final int mDensityDpi;
+        private int mFlags = 0;
+        private Surface mSurface = null;
+        private String mUniqueId = null;
+        private int mDisplayIdToMirror = DEFAULT_DISPLAY;
+        private boolean mWindowManagerMirroring = false;
+        private ArrayList<String> mDisplayCategories = new ArrayList<>();
+        private float mRequestedRefreshRate = 0.0f;
 
         /**
          * Creates a new Builder.
          *
-         * @param name
-         *   The name of the virtual display, must be non-empty.
-         * @param width
-         *   The width of the virtual display in pixels. Must be greater than 0.
-         * @param height
-         *   The height of the virtual display in pixels. Must be greater than 0.
-         * @param densityDpi
-         *   The density of the virtual display in dpi. Must be greater than 0.
+         * @param name The name of the virtual display, must be non-empty.
+         * @param width The width of the virtual display in pixels. Must be greater than 0.
+         * @param height The height of the virtual display in pixels. Must be greater than 0.
+         * @param densityDpi The density of the virtual display in dpi. Must be greater than 0.
          */
         public Builder(
                 @NonNull String name,
                 @IntRange(from = 1) int width,
                 @IntRange(from = 1) int height,
                 @IntRange(from = 1) int densityDpi) {
+            if (name == null) {
+                throw new IllegalArgumentException("Virtual display name is required");
+            }
+            if (width <= 0) {
+                throw new IllegalArgumentException("Virtual display width must be positive");
+            }
+            if (height <= 0) {
+                throw new IllegalArgumentException("Virtual display height must be positive");
+            }
+            if (densityDpi <= 0) {
+                throw new IllegalArgumentException("Virtual display density must be positive");
+            }
             mName = name;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mName);
             mWidth = width;
-            com.android.internal.util.AnnotationValidations.validate(
-                    IntRange.class, null, mWidth,
-                    "from", 1);
             mHeight = height;
-            com.android.internal.util.AnnotationValidations.validate(
-                    IntRange.class, null, mHeight,
-                    "from", 1);
             mDensityDpi = densityDpi;
-            com.android.internal.util.AnnotationValidations.validate(
-                    IntRange.class, null, mDensityDpi,
-                    "from", 1);
         }
 
         /**
-         * The name of the virtual display, must be non-empty.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setName(@NonNull String value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mName = value;
-            return this;
-        }
-
-        /**
-         * The width of the virtual display in pixels. Must be greater than 0.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setWidth(@IntRange(from = 1) int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mWidth = value;
-            return this;
-        }
-
-        /**
-         * The height of the virtual display in pixels. Must be greater than 0.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setHeight(@IntRange(from = 1) int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
-            mHeight = value;
-            return this;
-        }
-
-        /**
-         * The density of the virtual display in dpi. Must be greater than 0.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setDensityDpi(@IntRange(from = 1) int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
-            mDensityDpi = value;
-            return this;
-        }
-
-        /**
-         * A combination of virtual display flags.
+         * Sets the virtual display flags, a combination of
          * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC},
          * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION},
          * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE},
          * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
          * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
          */
-        @DataClass.Generated.Member
-        public @NonNull Builder setFlags(@VirtualDisplayFlag int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
-            mFlags = value;
+        @NonNull
+        public Builder setFlags(@VirtualDisplayFlag int flags) {
+            mFlags = flags;
             return this;
         }
 
         /**
-         * The surface to which the content of the virtual display should be rendered, or null if
-         * there is none initially.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setSurface(@NonNull Surface value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
-            mSurface = value;
-            return this;
-        }
-
-        /**
-         * The unique identifier for the display. Shouldn't be displayed to the user.
+         * Sets the surface to which the content of the virtual display should be rendered.
          *
+         * <p>The surface can also be set after the display creation using
+         * {@link VirtualDisplay#setSurface(Surface)}.
+         */
+        @NonNull
+        public Builder setSurface(@Nullable Surface surface) {
+            mSurface = surface;
+            return this;
+        }
+
+        /**
+         * Sets the unique identifier for the display.
          * @hide
          */
-        @DataClass.Generated.Member
-        public @NonNull Builder setUniqueId(@NonNull String value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x40;
-            mUniqueId = value;
+        @NonNull
+        public Builder setUniqueId(@Nullable String uniqueId) {
+            mUniqueId = uniqueId;
             return this;
         }
 
         /**
-         * The id of the display that the virtual display should mirror, or
-         * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially.
+         * Sets the id of the display that the virtual display should mirror.
+         * @hide
          */
-        @DataClass.Generated.Member
-        public @NonNull Builder setDisplayIdToMirror(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x80;
-            mDisplayIdToMirror = value;
+        @NonNull
+        public Builder setDisplayIdToMirror(int displayIdToMirror) {
+            mDisplayIdToMirror = displayIdToMirror;
             return this;
         }
 
         /**
-         * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
-         * if DisplayManager should record contents instead.
+         * Sets whether WindowManager is responsible for mirroring content to this VirtualDisplay.
+         * If unset or false, DisplayManager should record contents instead.
+         * @hide
          */
-        @DataClass.Generated.Member
-        public @NonNull Builder setWindowManagerMirroring(boolean value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x100;
-            mWindowManagerMirroring = value;
+        @NonNull
+        public Builder setWindowManagerMirroring(boolean windowManagerMirroring) {
+            mWindowManagerMirroring = windowManagerMirroring;
             return this;
         }
 
         /**
-         * The display categories. If set, only corresponding activities from the same category can be
-         * shown on the display.
+         * Sets the display categories.
+         *
+         * <p>The categories of the display indicate the type of activities allowed to run on that
+         * display. Activities can declare a display category using
+         * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
          */
-        @DataClass.Generated.Member
-        public @NonNull Builder setDisplayCategories(@NonNull List<String> value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x200;
-            mDisplayCategories = value;
-            return this;
-        }
-
-        /** @see #setDisplayCategories */
-        @DataClass.Generated.Member
-        public @NonNull Builder addDisplayCategory(@NonNull String value) {
-            if (mDisplayCategories == null) setDisplayCategories(new ArrayList<>());
-            mDisplayCategories.add(value);
+        @NonNull
+        public Builder setDisplayCategories(@NonNull List<String> displayCategories) {
+            mDisplayCategories.clear();
+            mDisplayCategories.addAll(Objects.requireNonNull(displayCategories));
             return this;
         }
 
         /**
-         * The refresh rate of a virtual display in frames per second.
-         * If this value is none zero, this is the requested refresh rate to set.
-         * If this value is zero, the system chooses a default refresh rate.
+         * Adds a display category.
+         *
+         * @see #setDisplayCategories
          */
-        @DataClass.Generated.Member
-        public @NonNull Builder setRequestedRefreshRate(float value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x400;
-            mRequestedRefreshRate = value;
+        @NonNull
+        public Builder addDisplayCategory(@NonNull String displayCategory) {
+            mDisplayCategories.add(Objects.requireNonNull(displayCategory));
             return this;
         }
 
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull VirtualDisplayConfig build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x800; // Mark builder used
+        /**
+         * Sets the refresh rate of a virtual display in frames per second.
+         *
+         * <p>For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+         * a 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded up
+         * down to a divisor of the physical display. If unset or zero, the virtual display will be
+         * refreshed at the physical display refresh rate.
+         *
+         * @see Display#getRefreshRate()
+         */
+        @NonNull
+        public Builder setRequestedRefreshRate(
+                @FloatRange(from = 0.0f) float requestedRefreshRate) {
+            if (requestedRefreshRate < 0.0f) {
+                throw new IllegalArgumentException(
+                        "Virtual display requested refresh rate must be non-negative");
+            }
+            mRequestedRefreshRate = requestedRefreshRate;
+            return this;
+        }
 
-            if ((mBuilderFieldsSet & 0x10) == 0) {
-                mFlags = 0;
-            }
-            if ((mBuilderFieldsSet & 0x20) == 0) {
-                mSurface = null;
-            }
-            if ((mBuilderFieldsSet & 0x40) == 0) {
-                mUniqueId = null;
-            }
-            if ((mBuilderFieldsSet & 0x80) == 0) {
-                mDisplayIdToMirror = DEFAULT_DISPLAY;
-            }
-            if ((mBuilderFieldsSet & 0x100) == 0) {
-                mWindowManagerMirroring = false;
-            }
-            if ((mBuilderFieldsSet & 0x200) == 0) {
-                mDisplayCategories = new ArrayList<>();
-            }
-            if ((mBuilderFieldsSet & 0x400) == 0) {
-                mRequestedRefreshRate = 0.0f;
-            }
-            VirtualDisplayConfig o = new VirtualDisplayConfig(
+        /**
+         * Builds the {@link VirtualDisplayConfig} instance.
+         */
+        @NonNull
+        public VirtualDisplayConfig build() {
+            return new VirtualDisplayConfig(
                     mName,
                     mWidth,
                     mHeight,
@@ -602,27 +438,6 @@
                     mWindowManagerMirroring,
                     mDisplayCategories,
                     mRequestedRefreshRate);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x800) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
         }
     }
-
-    @DataClass.Generated(
-            time = 1671047069703L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate  int mDisplayIdToMirror\nprivate  boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nprivate  float mRequestedRefreshRate\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54e4909..a3f8599 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -99,7 +99,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
-import com.android.internal.widget.ILockSettings;
 
 import java.io.IOException;
 import java.lang.annotation.ElementType;
@@ -116,6 +115,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * The Settings provider contains global system-level device preferences.
@@ -2978,19 +2978,22 @@
     }
 
     private static final class GenerationTracker {
-        private final MemoryIntArray mArray;
-        private final Runnable mErrorHandler;
+        @NonNull private final String mName;
+        @NonNull private final MemoryIntArray mArray;
+        @NonNull private final Consumer<String> mErrorHandler;
         private final int mIndex;
         private int mCurrentGeneration;
 
-        public GenerationTracker(@NonNull MemoryIntArray array, int index,
-                int generation, Runnable errorHandler) {
+        GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
+                int generation, Consumer<String> errorHandler) {
+            mName = name;
             mArray = array;
             mIndex = index;
             mErrorHandler = errorHandler;
             mCurrentGeneration = generation;
         }
 
+        // This method also updates the obsolete generation code stored locally
         public boolean isGenerationChanged() {
             final int currentGeneration = readCurrentGeneration();
             if (currentGeneration >= 0) {
@@ -3011,9 +3014,7 @@
                 return mArray.get(mIndex);
             } catch (IOException e) {
                 Log.e(TAG, "Error getting current generation", e);
-                if (mErrorHandler != null) {
-                    mErrorHandler.run();
-                }
+                mErrorHandler.accept(mName);
             }
             return -1;
         }
@@ -3023,9 +3024,6 @@
                 mArray.close();
             } catch (IOException e) {
                 Log.e(TAG, "Error closing backing array", e);
-                if (mErrorHandler != null) {
-                    mErrorHandler.run();
-                }
             }
         }
     }
@@ -3088,8 +3086,21 @@
         private final ArraySet<String> mAllFields;
         private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
 
+        // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
         @GuardedBy("this")
-        private GenerationTracker mGenerationTracker;
+        private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
+
+        private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
+            synchronized (NameValueCache.this) {
+                Log.e(TAG, "Error accessing generation tracker - removing");
+                final GenerationTracker tracker = mGenerationTrackers.get(name);
+                if (tracker != null) {
+                    tracker.destroy();
+                    mGenerationTrackers.remove(name);
+                }
+                mValues.remove(name);
+            }
+        };
 
         <T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
                 String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
@@ -3178,6 +3189,43 @@
 
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
+            final boolean isSelf = (userHandle == UserHandle.myUserId());
+            int currentGeneration = -1;
+            boolean needsGenerationTracker = false;
+
+            if (isSelf) {
+                synchronized (NameValueCache.this) {
+                    final GenerationTracker generationTracker = mGenerationTrackers.get(name);
+                    if (generationTracker != null) {
+                        if (generationTracker.isGenerationChanged()) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Generation changed for setting:" + name
+                                        + " type:" + mUri.getPath()
+                                        + " in package:" + cr.getPackageName()
+                                        + " and user:" + userHandle);
+                            }
+                            mValues.remove(name);
+                        } else if (mValues.containsKey(name)) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Cache hit for setting:" + name);
+                            }
+                            return mValues.get(name);
+                        }
+                        currentGeneration = generationTracker.getCurrentGeneration();
+                    } else {
+                        needsGenerationTracker = true;
+                    }
+                }
+            } else {
+                if (DEBUG || LOCAL_LOGV) {
+                    Log.v(TAG, "get setting for user " + userHandle
+                            + " by user " + UserHandle.myUserId() + " so skipping cache");
+                }
+            }
+            if (DEBUG) {
+                Log.i(TAG, "Cache miss for setting:" + name + " for user:" + userHandle);
+            }
+
             // Check if the target settings key is readable. Reject if the caller is not system and
             // is trying to access a settings key defined in the Settings.Secure, Settings.System or
             // Settings.Global and is not annotated as @Readable.
@@ -3211,31 +3259,6 @@
                 }
             }
 
-            final boolean isSelf = (userHandle == UserHandle.myUserId());
-            int currentGeneration = -1;
-            if (isSelf) {
-                synchronized (NameValueCache.this) {
-                    if (mGenerationTracker != null) {
-                        if (mGenerationTracker.isGenerationChanged()) {
-                            if (DEBUG) {
-                                Log.i(TAG, "Generation changed for type:"
-                                        + mUri.getPath() + " in package:"
-                                        + cr.getPackageName() +" and user:" + userHandle);
-                            }
-                            mValues.clear();
-                        } else if (mValues.containsKey(name)) {
-                            return mValues.get(name);
-                        }
-                        if (mGenerationTracker != null) {
-                            currentGeneration = mGenerationTracker.getCurrentGeneration();
-                        }
-                    }
-                }
-            } else {
-                if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
-                        + " by user " + UserHandle.myUserId() + " so skipping cache");
-            }
-
             IContentProvider cp = mProviderHolder.getProvider(cr);
 
             // Try the fast path first, not using query().  If this
@@ -3244,24 +3267,17 @@
             // interface.
             if (mCallGetCommand != null) {
                 try {
-                    Bundle args = null;
+                    Bundle args = new Bundle();
                     if (!isSelf) {
-                        args = new Bundle();
                         args.putInt(CALL_METHOD_USER_KEY, userHandle);
                     }
-                    boolean needsGenerationTracker = false;
-                    synchronized (NameValueCache.this) {
-                        if (isSelf && mGenerationTracker == null) {
-                            needsGenerationTracker = true;
-                            if (args == null) {
-                                args = new Bundle();
-                            }
-                            args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
-                            if (DEBUG) {
-                                Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
-                                        + " in package:" + cr.getPackageName() +" and user:"
-                                        + userHandle);
-                            }
+                    if (needsGenerationTracker) {
+                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+                        if (DEBUG) {
+                            Log.i(TAG, "Requested generation tracker for setting:" + name
+                                    + " type:" + mUri.getPath()
+                                    + " in package:" + cr.getPackageName()
+                                    + " and user:" + userHandle);
                         }
                     }
                     Bundle b;
@@ -3298,33 +3314,24 @@
                                         final int generation = b.getInt(
                                                 CALL_METHOD_GENERATION_KEY, 0);
                                         if (DEBUG) {
-                                            Log.i(TAG, "Received generation tracker for type:"
-                                                    + mUri.getPath() + " in package:"
-                                                    + cr.getPackageName() + " and user:"
-                                                    + userHandle + " with index:" + index);
+                                            Log.i(TAG, "Received generation tracker for setting:"
+                                                    + name
+                                                    + " type:" + mUri.getPath()
+                                                    + " in package:" + cr.getPackageName()
+                                                    + " and user:" + userHandle
+                                                    + " with index:" + index);
                                         }
-                                        if (mGenerationTracker != null) {
-                                            mGenerationTracker.destroy();
-                                        }
-                                        mGenerationTracker = new GenerationTracker(array, index,
-                                                generation, () -> {
-                                            synchronized (NameValueCache.this) {
-                                                Log.e(TAG, "Error accessing generation"
-                                                        + " tracker - removing");
-                                                if (mGenerationTracker != null) {
-                                                    GenerationTracker generationTracker =
-                                                            mGenerationTracker;
-                                                    mGenerationTracker = null;
-                                                    generationTracker.destroy();
-                                                    mValues.clear();
-                                                }
-                                            }
-                                        });
+                                        mGenerationTrackers.put(name, new GenerationTracker(name,
+                                                array, index, generation,
+                                                mGenerationTrackerErrorHandler));
                                         currentGeneration = generation;
                                     }
                                 }
-                                if (mGenerationTracker != null && currentGeneration ==
-                                        mGenerationTracker.getCurrentGeneration()) {
+                                if (mGenerationTrackers.get(name) != null && currentGeneration
+                                        == mGenerationTrackers.get(name).getCurrentGeneration()) {
+                                    if (DEBUG) {
+                                        Log.i(TAG, "Updating cache for setting:" + name);
+                                    }
                                     mValues.put(name, value);
                                 }
                             }
@@ -3367,15 +3374,14 @@
 
                 String value = c.moveToNext() ? c.getString(0) : null;
                 synchronized (NameValueCache.this) {
-                    if (mGenerationTracker != null
-                            && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
+                    if (mGenerationTrackers.get(name) != null && currentGeneration
+                            == mGenerationTrackers.get(name).getCurrentGeneration()) {
+                        if (DEBUG) {
+                            Log.i(TAG, "Updating cache for setting:" + name + " using query");
+                        }
                         mValues.put(name, value);
                     }
                 }
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
-                            name + " = " + (value == null ? "(null)" : value));
-                }
                 return value;
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
@@ -3409,18 +3415,29 @@
             Config.enforceReadPermission(namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
+            boolean needsGenerationTracker = false;
 
             synchronized (NameValueCache.this) {
-                if (mGenerationTracker != null) {
-                    if (mGenerationTracker.isGenerationChanged()) {
+                final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
+                if (generationTracker != null) {
+                    if (generationTracker.isGenerationChanged()) {
                         if (DEBUG) {
-                            Log.i(TAG, "Generation changed for type:" + mUri.getPath()
+                            Log.i(TAG, "Generation changed for prefix:" + prefix
+                                    + " type:" + mUri.getPath()
                                     + " in package:" + cr.getPackageName());
                         }
-                        mValues.clear();
+                        for (int i = 0; i < mValues.size(); ++i) {
+                            String key = mValues.keyAt(i);
+                            if (key.startsWith(prefix)) {
+                                mValues.remove(key);
+                            }
+                        }
                     } else {
                         boolean prefixCached = mValues.containsKey(prefix);
                         if (prefixCached) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Cache hit for prefix:" + prefix);
+                            }
                             if (!names.isEmpty()) {
                                 for (String name : names) {
                                     if (mValues.containsKey(name)) {
@@ -3440,9 +3457,9 @@
                             return keyValues;
                         }
                     }
-                    if (mGenerationTracker != null) {
-                        currentGeneration = mGenerationTracker.getCurrentGeneration();
-                    }
+                    currentGeneration = generationTracker.getCurrentGeneration();
+                } else {
+                    needsGenerationTracker = true;
                 }
             }
 
@@ -3450,20 +3467,20 @@
                 // No list command specified, return empty map
                 return keyValues;
             }
+            if (DEBUG) {
+                Log.i(TAG, "Cache miss for prefix:" + prefix);
+            }
             IContentProvider cp = mProviderHolder.getProvider(cr);
 
             try {
                 Bundle args = new Bundle();
                 args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
-                boolean needsGenerationTracker = false;
-                synchronized (NameValueCache.this) {
-                    if (mGenerationTracker == null) {
-                        needsGenerationTracker = true;
-                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
-                        if (DEBUG) {
-                            Log.i(TAG, "Requested generation tracker for type: "
-                                    + mUri.getPath() + " in package:" + cr.getPackageName());
-                        }
+                if (needsGenerationTracker) {
+                    args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+                    if (DEBUG) {
+                        Log.i(TAG, "Requested generation tracker for prefix:" + prefix
+                                + " type: " + mUri.getPath()
+                                + " in package:" + cr.getPackageName());
                     }
                 }
 
@@ -3516,32 +3533,22 @@
                             final int generation = b.getInt(
                                     CALL_METHOD_GENERATION_KEY, 0);
                             if (DEBUG) {
-                                Log.i(TAG, "Received generation tracker for type:"
-                                        + mUri.getPath() + " in package:"
-                                        + cr.getPackageName() + " with index:" + index);
+                                Log.i(TAG, "Received generation tracker for prefix:" + prefix
+                                        + " type:" + mUri.getPath()
+                                        + " in package:" + cr.getPackageName()
+                                        + " with index:" + index);
                             }
-                            if (mGenerationTracker != null) {
-                                mGenerationTracker.destroy();
-                            }
-                            mGenerationTracker = new GenerationTracker(array, index,
-                                    generation, () -> {
-                                synchronized (NameValueCache.this) {
-                                    Log.e(TAG, "Error accessing generation tracker"
-                                            + " - removing");
-                                    if (mGenerationTracker != null) {
-                                        GenerationTracker generationTracker =
-                                                mGenerationTracker;
-                                        mGenerationTracker = null;
-                                        generationTracker.destroy();
-                                        mValues.clear();
-                                    }
-                                }
-                            });
+                            mGenerationTrackers.put(prefix,
+                                    new GenerationTracker(prefix, array, index, generation,
+                                            mGenerationTrackerErrorHandler));
                             currentGeneration = generation;
                         }
                     }
-                    if (mGenerationTracker != null && currentGeneration
-                            == mGenerationTracker.getCurrentGeneration()) {
+                    if (mGenerationTrackers.get(prefix) != null && currentGeneration
+                            == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
+                        if (DEBUG) {
+                            Log.i(TAG, "Updating cache for prefix:" + prefix);
+                        }
                         // cache the complete list of flags for the namespace
                         mValues.putAll(flagsToValues);
                         // Adding the prefix as a signal that the prefix is cached.
@@ -3557,11 +3564,11 @@
 
         public void clearGenerationTrackerForTest() {
             synchronized (NameValueCache.this) {
-                if (mGenerationTracker != null) {
-                    mGenerationTracker.destroy();
+                for (int i = 0; i < mGenerationTrackers.size(); i++) {
+                    mGenerationTrackers.valueAt(i).destroy();
                 }
+                mGenerationTrackers.clear();
                 mValues.clear();
-                mGenerationTracker = null;
             }
         }
     }
@@ -6184,9 +6191,6 @@
                 sProviderHolder,
                 Secure.class);
 
-        private static ILockSettings sLockSettings = null;
-
-        private static boolean sIsSystemProcess;
         @UnsupportedAppUsage
         private static final HashSet<String> MOVED_TO_LOCK_SETTINGS;
         @UnsupportedAppUsage
@@ -6350,35 +6354,25 @@
                 return Global.getStringForUser(resolver, name, userHandle);
             }
 
-            if (MOVED_TO_LOCK_SETTINGS.contains(name)) {
-                synchronized (Secure.class) {
-                    if (sLockSettings == null) {
-                        sLockSettings = ILockSettings.Stub.asInterface(
-                                (IBinder) ServiceManager.getService("lock_settings"));
-                        sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID;
-                    }
-                }
-                if (sLockSettings != null && !sIsSystemProcess) {
-                    // No context; use the ActivityThread's context as an approximation for
-                    // determining the target API level.
-                    Application application = ActivityThread.currentApplication();
+            if (MOVED_TO_LOCK_SETTINGS.contains(name) && Process.myUid() != Process.SYSTEM_UID) {
+                // No context; use the ActivityThread's context as an approximation for
+                // determining the target API level.
+                Application application = ActivityThread.currentApplication();
 
-                    boolean isPreMnc = application != null
-                            && application.getApplicationInfo() != null
-                            && application.getApplicationInfo().targetSdkVersion
-                            <= VERSION_CODES.LOLLIPOP_MR1;
-                    if (isPreMnc) {
-                        try {
-                            return sLockSettings.getString(name, "0", userHandle);
-                        } catch (RemoteException re) {
-                            // Fall through
-                        }
-                    } else {
-                        throw new SecurityException("Settings.Secure." + name
-                                + " is deprecated and no longer accessible."
-                                + " See API documentation for potential replacements.");
-                    }
+                boolean isPreMnc = application != null
+                    && application.getApplicationInfo() != null
+                    && application.getApplicationInfo().targetSdkVersion
+                    <= VERSION_CODES.LOLLIPOP_MR1;
+                if (isPreMnc) {
+                    // Old apps used to get the three deprecated LOCK_PATTERN_* settings from
+                    // ILockSettings.getString().  For security reasons, we now just return a
+                    // stubbed-out value.  Note: the only one of these three settings actually known
+                    // to have been used was LOCK_PATTERN_ENABLED, and ILockSettings.getString()
+                    // already always returned "0" for that starting in Android 11.
+                    return "0";
                 }
+                throw new SecurityException("Settings.Secure." + name + " is deprecated and no" +
+                        " longer accessible. See API documentation for potential replacements.");
             }
 
             return sNameValueCache.getStringForUser(resolver, name, userHandle);
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index f648ad4..434b1c7 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -53,9 +53,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -83,15 +81,16 @@
      * A mapping between {@link SubscriptionManager.OnSubscriptionsChangedListener} and
      * its callback IOnSubscriptionsChangedListener.
      */
-    private final Map<SubscriptionManager.OnSubscriptionsChangedListener,
-                IOnSubscriptionsChangedListener> mSubscriptionChangedListenerMap = new HashMap<>();
+    private final ConcurrentHashMap<SubscriptionManager.OnSubscriptionsChangedListener,
+            IOnSubscriptionsChangedListener>
+                    mSubscriptionChangedListenerMap = new ConcurrentHashMap<>();
     /**
      * A mapping between {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} and
      * its callback IOnSubscriptionsChangedListener.
      */
-    private final Map<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener,
-            IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap
-            = new HashMap<>();
+    private final ConcurrentHashMap<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener,
+            IOnSubscriptionsChangedListener>
+                    mOpportunisticSubscriptionChangedListenerMap = new ConcurrentHashMap<>();
 
     /**
      * A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 8a9445d..28b98d6 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,14 +16,10 @@
 
 package com.android.internal.os;
 
-import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
-
-import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
 import android.app.IActivityManager;
-import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.type.DefaultMimeMapFactory;
 import android.net.TrafficStats;
@@ -40,7 +36,6 @@
 
 import dalvik.system.RuntimeHooks;
 import dalvik.system.VMRuntime;
-import dalvik.system.ZipPathValidator;
 
 import libcore.content.type.MimeMap;
 
@@ -265,31 +260,10 @@
          */
         TrafficStats.attachSocketTagger();
 
-        /*
-         * Initialize the zip path validator callback depending on the targetSdk.
-         */
-        initZipPathValidatorCallback();
-
         initialized = true;
     }
 
     /**
-     * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
-     * entry names.
-     * Otherwise: clear the callback to the default validation.
-     *
-     * @hide
-     */
-    @TestApi
-    public static void initZipPathValidatorCallback() {
-        if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
-            ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
-        } else {
-            ZipPathValidator.clearCallback();
-        }
-    }
-
-    /**
      * Returns an HTTP user agent of the form
      * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
      */
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 939a0e4..9c6a534 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -266,7 +266,8 @@
 }
 
 static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
-        jlong size, jint channelType, jint fd, jobject hardwareBufferObj) {
+                                      jint deviceId, jlong size, jint channelType, jint fd,
+                                      jobject hardwareBufferObj) {
     const native_handle_t *nativeHandle = nullptr;
     NATIVE_HANDLE_DECLARE_STORAGE(ashmemHandle, 1, 0);
 
@@ -287,7 +288,7 @@
     }
 
     SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
-    return mgr->createDirectChannel(size, channelType, nativeHandle);
+    return mgr->createDirectChannel(deviceId, size, channelType, nativeHandle);
 }
 
 static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
@@ -532,7 +533,7 @@
 
         {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
-        {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+        {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
          (void *)nativeCreateDirectChannel},
 
         {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
index f97099d..16ed3ef 100644
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -17,9 +17,15 @@
 package android.companion.virtual.sensor;
 
 import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+import static android.hardware.SensorDirectChannel.RATE_STOP;
+import static android.hardware.SensorDirectChannel.RATE_VERY_FAST;
+import static android.hardware.SensorDirectChannel.TYPE_HARDWARE_BUFFER;
+import static android.hardware.SensorDirectChannel.TYPE_MEMORY_FILE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.testng.Assert.assertThrows;
+
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
@@ -40,6 +46,8 @@
         final VirtualSensorConfig originalConfig =
                 new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
                         .setVendor(SENSOR_VENDOR)
+                        .setHighestDirectReportRateLevel(RATE_VERY_FAST)
+                        .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
                         .build();
         final Parcel parcel = Parcel.obtain();
         originalConfig.writeToParcel(parcel, /* flags= */ 0);
@@ -49,6 +57,39 @@
         assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
         assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
         assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+        assertThat(recreatedConfig.getHighestDirectReportRateLevel()).isEqualTo(RATE_VERY_FAST);
+        assertThat(recreatedConfig.getDirectChannelTypesSupported()).isEqualTo(TYPE_MEMORY_FILE);
+        // From hardware/libhardware/include/hardware/sensors-base.h:
+        //   0x400 is SENSOR_FLAG_DIRECT_CHANNEL_ASHMEM (i.e. TYPE_MEMORY_FILE)
+        //   0x800 is SENSOR_FLAG_DIRECT_CHANNEL_GRALLOC (i.e. TYPE_HARDWARE_BUFFER)
+        //   7 is SENSOR_FLAG_SHIFT_DIRECT_REPORT
+        assertThat(recreatedConfig.getFlags()).isEqualTo(0x400 | RATE_VERY_FAST << 7);
+    }
+
+    @Test
+    public void hardwareBufferDirectChannelTypeSupported_throwsException() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setDirectChannelTypesSupported(TYPE_HARDWARE_BUFFER | TYPE_MEMORY_FILE));
+    }
+
+    @Test
+    public void directChannelTypeSupported_missingHighestReportRateLevel_throwsException() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
+                        .build());
+    }
+
+    @Test
+    public void directChannelTypeSupported_missingDirectChannelTypeSupported_throwsException() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+                        .setHighestDirectReportRateLevel(RATE_VERY_FAST)
+                        .build());
     }
 
     @Test
@@ -56,5 +97,8 @@
         final VirtualSensorConfig config =
                 new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
         assertThat(config.getVendor()).isNull();
+        assertThat(config.getHighestDirectReportRateLevel()).isEqualTo(RATE_STOP);
+        assertThat(config.getDirectChannelTypesSupported()).isEqualTo(0);
+        assertThat(config.getFlags()).isEqualTo(0);
     }
 }
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index ef106bc..ee1b2aa 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -43,14 +43,56 @@
         assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
     }
 
-    @SmallTest
-    fun missingLookupTableReturnsNull() {
-        assertThat(FontScaleConverterFactory.forScale(3F)).isNull()
+    @LargeTest
+    @Test
+    fun missingLookupTablePastEnd_returnsLinear() {
+        val table = FontScaleConverterFactory.forScale(3F)!!
+        generateSequenceOfFractions(-10000f..10000f, step = 0.01f)
+            .map {
+                assertThat(table.convertSpToDp(it)).isWithin(CONVERSION_TOLERANCE).of(it * 3f)
+            }
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(3f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(24f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(30f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(15f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+        assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(150f)
+        assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(300f)
     }
 
     @SmallTest
-    fun missingLookupTable105ReturnsNull() {
-        assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull()
+    fun missingLookupTable110_returnsInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.1F)!!
+
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.1f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.1f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(11f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.1f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+        assertThat(table.convertSpToDp(50F)).isLessThan(50f * 1.1f)
+        assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.1f)
+    }
+
+    @Test
+    fun missingLookupTable199_returnsInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.9999F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
+    }
+
+    @Test
+    fun missingLookupTable160_returnsInterpolated() {
+        val table = FontScaleConverterFactory.forScale(1.6F)!!
+        assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f * 1.6F)
+        assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.6F)
+        assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f * 1.6F)
+        assertThat(table.convertSpToDp(20F)).isLessThan(20f * 1.6F)
+        assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.6F)
+        assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.6F)
+        assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f)
     }
 
     @SmallTest
@@ -83,7 +125,7 @@
     @Test
     fun allFeasibleScalesAndConversionsDoNotCrash() {
         generateSequenceOfFractions(-10f..10f, step = 0.01f)
-            .mapNotNull{ FontScaleConverterFactory.forScale(it) }
+            .mapNotNull{ FontScaleConverterFactory.forScale(it) }!!
             .flatMap{ table ->
                 generateSequenceOfFractions(-2000f..2000f, step = 0.01f)
                     .map{ Pair(table, it) }
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 2e31bb5..b6fc137 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -32,16 +32,18 @@
 import android.test.mock.MockContentResolver;
 import android.util.MemoryIntArray;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
+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 java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -59,42 +61,63 @@
     private static final String NAMESPACE = "namespace";
     private static final String NAMESPACE2 = "namespace2";
 
+    private static final String SETTING = "test_setting";
+    private static final String SETTING2 = "test_setting2";
+
     @Mock
     private IContentProvider mMockIContentProvider;
     @Mock
     private ContentProvider mMockContentProvider;
     private MockContentResolver mMockContentResolver;
-    private MemoryIntArray mCacheGenerationStore;
-    private int mCurrentGeneration = 123;
+    private MemoryIntArray mConfigsCacheGenerationStore;
+    private MemoryIntArray mSettingsCacheGenerationStore;
 
-    private HashMap<String, HashMap<String, String>> mStorage;
+    private HashMap<String, HashMap<String, String>> mConfigsStorage;
+    private HashMap<String, String> mSettingsStorage;
+
 
     @Before
     public void setUp() throws Exception {
         Settings.Config.clearProviderForTest();
+        Settings.Secure.clearProviderForTest();
         MockitoAnnotations.initMocks(this);
         when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
-        mMockContentResolver = new MockContentResolver(InstrumentationRegistry
-                .getInstrumentation().getContext());
+        mMockContentResolver = new MockContentResolver(
+                InstrumentationRegistry.getInstrumentation().getContext());
         mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
                 mMockContentProvider);
-        mCacheGenerationStore = new MemoryIntArray(1);
-        mStorage = new HashMap<>();
+        mMockContentResolver.addProvider(Settings.Secure.CONTENT_URI.getAuthority(),
+                mMockContentProvider);
+        mConfigsCacheGenerationStore = new MemoryIntArray(2);
+        mConfigsCacheGenerationStore.set(0, 123);
+        mConfigsCacheGenerationStore.set(1, 456);
+        mSettingsCacheGenerationStore = new MemoryIntArray(2);
+        mSettingsCacheGenerationStore.set(0, 234);
+        mSettingsCacheGenerationStore.set(1, 567);
+        mConfigsStorage = new HashMap<>();
+        mSettingsStorage = new HashMap<>();
 
         // Stores keyValues for a given prefix and increments the generation. (Note that this
         // increments the generation no matter what, it doesn't pay attention to if anything
         // actually changed).
         when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
                     HashMap<String, String> keyValues =
                             (HashMap<String, String>) incomingBundle.getSerializable(
-                                    Settings.CALL_METHOD_FLAGS_KEY);
+                                    Settings.CALL_METHOD_FLAGS_KEY, HashMap.class);
                     String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
-                    mStorage.put(prefix, keyValues);
-                    mCacheGenerationStore.set(0, ++mCurrentGeneration);
-
+                    mConfigsStorage.put(prefix, keyValues);
+                    int currentGeneration;
+                    // Different prefixes have different generation codes
+                    if (prefix.equals(NAMESPACE + "/")) {
+                        currentGeneration = mConfigsCacheGenerationStore.get(0);
+                        mConfigsCacheGenerationStore.set(0, ++currentGeneration);
+                    } else if (prefix.equals(NAMESPACE2 + "/")) {
+                        currentGeneration = mConfigsCacheGenerationStore.get(1);
+                        mConfigsCacheGenerationStore.set(1, ++currentGeneration);
+                    }
                     Bundle result = new Bundle();
                     result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
                             Settings.SET_ALL_RESULT_SUCCESS);
@@ -102,32 +125,87 @@
                 });
 
         // Returns the keyValues corresponding to a namespace, or an empty map if the namespace
-        // doesn't have anything stored for it. Returns the generation key if the caller asked
-        // for one.
+        // doesn't have anything stored for it. Returns the generation key if the map isn't empty
+        // and the caller asked for the generation key.
         when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
 
                     String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
 
-                    if (!mStorage.containsKey(prefix)) {
-                        mStorage.put(prefix, new HashMap<>());
+                    if (!mConfigsStorage.containsKey(prefix)) {
+                        mConfigsStorage.put(prefix, new HashMap<>());
                     }
-                    HashMap<String, String> keyValues = mStorage.get(prefix);
+                    HashMap<String, String> keyValues = mConfigsStorage.get(prefix);
 
                     Bundle bundle = new Bundle();
                     bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
 
-                    if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+                    if (!keyValues.isEmpty() && incomingBundle.containsKey(
+                            Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+                        int index = prefix.equals(NAMESPACE + "/") ? 0 : 1;
                         bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                mCacheGenerationStore);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
+                                mConfigsCacheGenerationStore);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
                         bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                                mCacheGenerationStore.get(0));
+                                mConfigsCacheGenerationStore.get(index));
                     }
                     return bundle;
                 });
+
+        // Stores value for a given setting's name and increments the generation. (Note that this
+        // increments the generation no matter what, it doesn't pay attention to if anything
+        // actually changed).
+        when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
+                    Bundle incomingBundle = invocationOnMock.getArgument(4);
+                    String key = invocationOnMock.getArgument(3);
+                    String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
+                    mSettingsStorage.put(key, value);
+                    int currentGeneration;
+                    // Different settings have different generation codes
+                    if (key.equals(SETTING)) {
+                        currentGeneration = mSettingsCacheGenerationStore.get(0);
+                        mSettingsCacheGenerationStore.set(0, ++currentGeneration);
+                    } else if (key.equals(SETTING2)) {
+                        currentGeneration = mSettingsCacheGenerationStore.get(1);
+                        mSettingsCacheGenerationStore.set(1, ++currentGeneration);
+                    }
+                    return null;
+                });
+
+        // Returns the value corresponding to a setting, or null if the setting
+        // doesn't have a value stored for it. Returns the generation key if the value isn't null
+        // and the caller asked for the generation key.
+        when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
+                invocationOnMock -> {
+                    Bundle incomingBundle = invocationOnMock.getArgument(4);
+                    String key = invocationOnMock.getArgument(3);
+                    String value = mSettingsStorage.get(key);
+
+                    Bundle bundle = new Bundle();
+                    bundle.putSerializable(Settings.NameValueTable.VALUE, value);
+
+                    if (value != null && incomingBundle.containsKey(
+                            Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+                        int index = key.equals(SETTING) ? 0 : 1;
+                        bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+                                mSettingsCacheGenerationStore);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+                        bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+                                mSettingsCacheGenerationStore.get(index));
+                    }
+                    return bundle;
+                });
+    }
+
+    @After
+    public void cleanUp() throws IOException {
+        mConfigsStorage.clear();
+        mSettingsStorage.clear();
     }
 
     @Test
@@ -135,16 +213,13 @@
         HashMap<String, String> keyValues = new HashMap<>();
         keyValues.put("a", "b");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class));
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
 
         Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE,
-                Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                NAMESPACE, Collections.emptyList());
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
@@ -156,14 +231,12 @@
         keyValues.put("a", "c");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
 
         Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
 
         Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
@@ -177,36 +250,31 @@
         HashMap<String, String> keyValues = new HashMap<>();
         keyValues.put("a", "b");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+        verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
                 any(), any(Bundle.class));
 
+        Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
+                NAMESPACE, Collections.emptyList());
+        verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_LIST_CONFIG),
+                any(), any(Bundle.class));
+        assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+
         HashMap<String, String> keyValues2 = new HashMap<>();
         keyValues2.put("c", "d");
         keyValues2.put("e", "f");
         Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
-                any(), any(Bundle.class));
-
-        Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE,
-                Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
-        assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+                eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
 
         Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE2,
-                Collections.emptyList());
+                NAMESPACE2, Collections.emptyList());
         verify(mMockIContentProvider, times(2)).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
+        // Modifying the second namespace doesn't affect the cache of the first namespace
         verifyNoMoreInteractions(mMockIContentProvider);
         assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
 
@@ -219,17 +287,90 @@
     @Test
     public void testCaching_emptyNamespace() throws Exception {
         Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
-                NAMESPACE,
-                Collections.emptyList());
-        verify(mMockIContentProvider).call(any(), any(),
-                eq(Settings.CALL_METHOD_LIST_CONFIG),
-                any(), any(Bundle.class));
+                NAMESPACE, Collections.emptyList());
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(returnedValues).isEmpty();
 
         Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
                 NAMESPACE, Collections.emptyList());
-        verifyNoMoreInteractions(mMockIContentProvider);
+        // Empty list won't be cached
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
         assertThat(cachedKeyValues).isEmpty();
     }
 
+    @Test
+    public void testCaching_singleSetting() throws Exception {
+        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isEqualTo("a");
+
+        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue).isEqualTo("a");
+
+        // Modify the value to invalidate the cache.
+        Settings.Secure.putString(mMockContentResolver, SETTING, "b");
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue2).isEqualTo("b");
+
+        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue2).isEqualTo("b");
+    }
+
+    @Test
+    public void testCaching_multipleSettings() throws Exception {
+        Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isEqualTo("a");
+
+        Settings.Secure.putString(mMockContentResolver, SETTING2, "b");
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+        String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue2).isEqualTo("b");
+
+        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        // Modifying the second setting doesn't affect the cache of the first setting
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue).isEqualTo("a");
+
+        String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+        verifyNoMoreInteractions(mMockIContentProvider);
+        assertThat(cachedValue2).isEqualTo("b");
+    }
+
+    @Test
+    public void testCaching_nullSetting() throws Exception {
+        String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        verify(mMockIContentProvider, times(1)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(returnedValue).isNull();
+
+        String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+        // Empty list won't be cached
+        verify(mMockIContentProvider, times(2)).call(any(), any(),
+                eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+        assertThat(cachedValue).isNull();
+    }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ca1367a..0692052 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -248,17 +248,22 @@
 
     @Test
     public void testSystemDrivenInsetsAnimationLoggingListener_onReady() {
+        var loggingListener = mock(WindowInsetsAnimationControlListener.class);
+
         prepareControls();
         // only the original thread that created view hierarchy can touch its views
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            WindowInsetsAnimationControlListener loggingListener =
-                    mock(WindowInsetsAnimationControlListener.class);
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
             mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
-            verify(loggingListener).onReady(notNull(), anyInt());
+            // When using the animation thread, this must not invoke onReady()
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
         });
+        // Wait for onReady() being dispatched on the animation thread.
+        InsetsAnimationThread.get().getThreadHandler().runWithScissors(() -> {}, 500);
+
+        verify(loggingListener).onReady(notNull(), anyInt());
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
deleted file mode 100644
index c540a15..0000000
--- a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * 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.internal.os;
-
-import static org.junit.Assert.assertThrows;
-
-import android.compat.testing.PlatformCompatChangeRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Test SafeZipPathCallback.
- */
-@RunWith(AndroidJUnit4.class)
-public class SafeZipPathValidatorCallbackTest {
-    @Rule
-    public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
-
-    @Before
-    public void setUp() {
-        RuntimeInit.initZipPathValidatorCallback();
-    }
-
-    @Test
-    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
-    public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
-            throws Exception {
-        final String[] dangerousEntryNames = {
-            "../foo.bar",
-            "foo/../bar.baz",
-            "foo/../../bar.baz",
-            "foo.bar/..",
-            "foo.bar/../",
-            "..",
-            "../",
-            "/foo",
-        };
-        for (String entryName : dangerousEntryNames) {
-            final File tempFile = File.createTempFile("smdc", "zip");
-            try {
-                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
-
-                assertThrows(
-                        "ZipException expected for entry: " + entryName,
-                        ZipException.class,
-                        () -> {
-                            new ZipFile(tempFile);
-                        });
-            } finally {
-                tempFile.delete();
-            }
-        }
-    }
-
-    @Test
-    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
-    public void
-            testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
-                    throws Exception {
-        final String[] dangerousEntryNames = {
-            "../foo.bar",
-            "foo/../bar.baz",
-            "foo/../../bar.baz",
-            "foo.bar/..",
-            "foo.bar/../",
-            "..",
-            "../",
-            "/foo",
-        };
-        for (String entryName : dangerousEntryNames) {
-            byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
-            try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
-                assertThrows(
-                        "ZipException expected for entry: " + entryName,
-                        ZipException.class,
-                        () -> {
-                            zis.getNextEntry();
-                        });
-            }
-        }
-    }
-
-    @Test
-    @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
-    public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
-            throws Exception {
-        final String[] normalEntryNames = {
-            "foo", "foo.bar", "foo..bar",
-        };
-        for (String entryName : normalEntryNames) {
-            final File tempFile = File.createTempFile("smdc", "zip");
-            try {
-                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
-                try {
-                    new ZipFile((tempFile));
-                } catch (ZipException e) {
-                    throw new AssertionError("ZipException not expected for entry: " + entryName);
-                }
-            } finally {
-                tempFile.delete();
-            }
-        }
-    }
-
-    @Test
-    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
-    public void
-            testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
-                    throws Exception {
-        final String[] normalEntryNames = {
-            "foo", "foo.bar", "foo..bar",
-        };
-        for (String entryName : normalEntryNames) {
-            byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
-            try {
-                ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
-                zis.getNextEntry();
-            } catch (ZipException e) {
-                throw new AssertionError("ZipException not expected for entry: " + entryName);
-            }
-        }
-    }
-
-    @Test
-    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
-    public void
-            testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
-                    throws Exception {
-        final String[] entryNames = {
-            "../foo.bar",
-            "foo/../bar.baz",
-            "foo/../../bar.baz",
-            "foo.bar/..",
-            "foo.bar/../",
-            "..",
-            "../",
-            "/foo",
-            "foo",
-            "foo.bar",
-            "foo..bar",
-        };
-        for (String entryName : entryNames) {
-            final File tempFile = File.createTempFile("smdc", "zip");
-            try {
-                writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
-                try {
-                    new ZipFile((tempFile));
-                } catch (ZipException e) {
-                    throw new AssertionError("ZipException not expected for entry: " + entryName);
-                }
-            } finally {
-                tempFile.delete();
-            }
-        }
-    }
-
-    @Test
-    @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
-    public void
-            testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
-                    throws Exception {
-        final String[] entryNames = {
-            "../foo.bar",
-            "foo/../bar.baz",
-            "foo/../../bar.baz",
-            "foo.bar/..",
-            "foo.bar/../",
-            "..",
-            "../",
-            "/foo",
-            "foo",
-            "foo.bar",
-            "foo..bar",
-        };
-        for (String entryName : entryNames) {
-            byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
-            try {
-                ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
-                zis.getNextEntry();
-            } catch (ZipException e) {
-                throw new AssertionError("ZipException not expected for entry: " + entryName);
-            }
-        }
-    }
-
-    private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
-            throws IOException {
-        FileOutputStream tempFileStream = new FileOutputStream(tempFile);
-        writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
-        tempFileStream.close();
-    }
-
-    private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
-            throws IOException {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        writeZipOutputStreamWithEmptyEntry(bos, entryName);
-        return bos.toByteArray();
-    }
-
-    private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
-            throws IOException {
-        ZipOutputStream zos = new ZipOutputStream(os);
-        ZipEntry entry = new ZipEntry(entryName);
-        zos.putNextEntry(entry);
-        zos.write(new byte[2]);
-        zos.closeEntry();
-        zos.close();
-    }
-}
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 6a1313e..66fabec 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -341,7 +341,6 @@
      * @hide so only calls from module can utilize it
      */
     long getNativeWrapperInstance() {
-        nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed);
         return mNativeMeshWrapper;
     }
 
@@ -383,5 +382,4 @@
 
     private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values);
 
-    private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed);
 }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index bcbe706..536bb49 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -332,6 +332,7 @@
         "jni/android_graphics_Matrix.cpp",
         "jni/android_graphics_Picture.cpp",
         "jni/android_graphics_DisplayListCanvas.cpp",
+        "jni/android_graphics_Mesh.cpp",
         "jni/android_graphics_RenderNode.cpp",
         "jni/android_nio_utils.cpp",
         "jni/android_util_PathParser.cpp",
@@ -351,7 +352,6 @@
         "jni/ImageDecoder.cpp",
         "jni/Interpolator.cpp",
         "jni/MeshSpecification.cpp",
-        "jni/Mesh.cpp",
         "jni/MaskFilter.cpp",
         "jni/NinePatch.cpp",
         "jni/NinePatchPeeker.cpp",
@@ -538,6 +538,7 @@
         "Interpolator.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
+        "Mesh.cpp",
         "MemoryPolicy.cpp",
         "PathParser.cpp",
         "Properties.cpp",
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index e2127ef..a18ba1c 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,4 +52,5 @@
 X(DrawVectorDrawable)
 X(DrawRippleDrawable)
 X(DrawWebView)
-X(DrawMesh)
+X(DrawSkMesh)
+X(DrawMesh)
\ No newline at end of file
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 2f0f7f5..41ced8c 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,8 +53,8 @@
     // Whether or not to only purge scratch resources when triggering UI Hidden or background
     // collection
     bool purgeScratchOnly = true;
-    // Whether or not to trigger releasing GPU context when all contexts are stopped
-    bool releaseContextOnStoppedOnly = true;
+    // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+    bool releaseContextOnStoppedOnly = false;
 };
 
 const MemoryPolicy& loadMemoryPolicy();
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
new file mode 100644
index 0000000..e59bc95
--- /dev/null
+++ b/libs/hwui/Mesh.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Mesh.h"
+
+#include <GLES/gl.h>
+#include <SkMesh.h>
+
+#include "SafeMath.h"
+
+static size_t min_vcount_for_mode(SkMesh::Mode mode) {
+    switch (mode) {
+        case SkMesh::Mode::kTriangles:
+            return 3;
+        case SkMesh::Mode::kTriangleStrip:
+            return 3;
+    }
+}
+
+// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
+std::tuple<bool, SkString> Mesh::validate() {
+#define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__))
+    if (!mMeshSpec) {
+        FAIL_MESH_VALIDATE("MeshSpecification is required.");
+    }
+    if (mVertexBufferData.empty()) {
+        FAIL_MESH_VALIDATE("VertexBuffer is required.");
+    }
+
+    auto meshStride = mMeshSpec->stride();
+    auto meshMode = SkMesh::Mode(mMode);
+    SafeMath sm;
+    size_t vsize = sm.mul(meshStride, mVertexCount);
+    if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+        FAIL_MESH_VALIDATE(
+                "The vertex buffer offset and vertex count reads beyond the end of the"
+                " vertex buffer.");
+    }
+
+    if (mVertexOffset % meshStride != 0) {
+        FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
+                           mVertexOffset, meshStride);
+    }
+
+    if (size_t uniformSize = mMeshSpec->uniformSize()) {
+        if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+            FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
+                               mBuilder->fUniforms->size(), uniformSize);
+        }
+    }
+
+    auto modeToStr = [](SkMesh::Mode m) {
+        switch (m) {
+            case SkMesh::Mode::kTriangles:
+                return "triangles";
+            case SkMesh::Mode::kTriangleStrip:
+                return "triangle-strip";
+        }
+    };
+    if (!mIndexBufferData.empty()) {
+        if (mIndexCount < min_vcount_for_mode(meshMode)) {
+            FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
+                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+        }
+        size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
+        if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+            FAIL_MESH_VALIDATE(
+                    "The index buffer offset and index count reads beyond the end of the"
+                    " index buffer.");
+        }
+        // If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
+        if (!SkIsAlign2(mIndexOffset)) {
+            FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
+        }
+    } else {
+        if (mVertexCount < min_vcount_for_mode(meshMode)) {
+            FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
+                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+        }
+        SkASSERT(!fICount);
+        SkASSERT(!fIOffset);
+    }
+
+    if (!sm.ok()) {
+        FAIL_MESH_VALIDATE("Overflow");
+    }
+#undef FAIL_MESH_VALIDATE
+    return {true, {}};
+}
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
new file mode 100644
index 0000000..9836817
--- /dev/null
+++ b/libs/hwui/Mesh.h
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+
+#ifndef MESH_H_
+#define MESH_H_
+
+#include <GrDirectContext.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+class MeshUniformBuilder {
+public:
+    struct MeshUniform {
+        template <typename T>
+        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+                const T& val) {
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+            } else if (sizeof(val) != fVar->sizeInBytes()) {
+                LOG_FATAL("Incorrect value size");
+            } else {
+                void* dst = reinterpret_cast<void*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                memcpy(dst, &val, sizeof(val));
+            }
+        }
+
+        MeshUniform& operator=(const SkMatrix& val) {
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+                LOG_FATAL("Incorrect value size");
+            } else {
+                float* data = reinterpret_cast<float*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                data[0] = val.get(0);
+                data[1] = val.get(3);
+                data[2] = val.get(6);
+                data[3] = val.get(1);
+                data[4] = val.get(4);
+                data[5] = val.get(7);
+                data[6] = val.get(2);
+                data[7] = val.get(5);
+                data[8] = val.get(8);
+            }
+            return *this;
+        }
+
+        template <typename T>
+        bool set(const T val[], const int count) {
+            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+                return false;
+            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+                LOG_FATAL("Incorrect value size");
+                return false;
+            } else {
+                void* dst = reinterpret_cast<void*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                memcpy(dst, val, sizeof(T) * count);
+            }
+            return true;
+        }
+
+        MeshUniformBuilder* fOwner;
+        const SkRuntimeEffect::Uniform* fVar;
+    };
+    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+        fMeshSpec = sk_sp(meshSpec);
+        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
+    }
+
+    sk_sp<SkData> fUniforms;
+
+private:
+    void* writableUniformData() {
+        if (!fUniforms->unique()) {
+            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+        }
+        return fUniforms->writable_data();
+    }
+
+    sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+class Mesh {
+public:
+    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+         size_t vertexBufferSize, jint vertexCount, jint vertexOffset,
+         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+            : mMeshSpec(meshSpec)
+            , mMode(mode)
+            , mVertexCount(vertexCount)
+            , mVertexOffset(vertexOffset)
+            , mBuilder(std::move(builder))
+            , mBounds(bounds) {
+        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+    }
+
+    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+         size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer,
+         size_t indexBufferSize, jint indexCount, jint indexOffset,
+         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+            : mMeshSpec(meshSpec)
+            , mMode(mode)
+            , mVertexCount(vertexCount)
+            , mVertexOffset(vertexOffset)
+            , mIndexCount(indexCount)
+            , mIndexOffset(indexOffset)
+            , mBuilder(std::move(builder))
+            , mBounds(bounds) {
+        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+        copyToVector(mIndexBufferData, indexBuffer, indexBufferSize);
+    }
+
+    Mesh(Mesh&&) = default;
+
+    Mesh& operator=(Mesh&&) = default;
+
+    [[nodiscard]] std::tuple<bool, SkString> validate();
+
+    void updateSkMesh(GrDirectContext* context) const {
+        GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
+        if (context) {
+            genId = context->directContextID();
+        }
+
+        if (mIsDirty || genId != mGenerationId) {
+            auto vb = SkMesh::MakeVertexBuffer(
+                    context, reinterpret_cast<const void*>(mVertexBufferData.data()),
+                    mVertexBufferData.size());
+            auto meshMode = SkMesh::Mode(mMode);
+            if (!mIndexBufferData.empty()) {
+                auto ib = SkMesh::MakeIndexBuffer(
+                        context, reinterpret_cast<const void*>(mIndexBufferData.data()),
+                        mIndexBufferData.size());
+                mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+                                            ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
+                                            mBounds)
+                                .mesh;
+            } else {
+                mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+                                     mBuilder->fUniforms, mBounds)
+                                .mesh;
+            }
+            mIsDirty = false;
+            mGenerationId = genId;
+        }
+    }
+
+    SkMesh& getSkMesh() const {
+        LOG_FATAL_IF(mIsDirty,
+                     "Attempt to obtain SkMesh when Mesh is dirty, did you "
+                     "forget to call updateSkMesh with a GrDirectContext? "
+                     "Defensively creating a CPU mesh");
+        return mMesh;
+    }
+
+    void markDirty() { mIsDirty = true; }
+
+    MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+
+private:
+    void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
+        if (src) {
+            dst.resize(srcSize);
+            memcpy(dst.data(), src, srcSize);
+        }
+    }
+
+    sk_sp<SkMeshSpecification> mMeshSpec;
+    int mMode = 0;
+
+    std::vector<uint8_t> mVertexBufferData;
+    size_t mVertexCount = 0;
+    size_t mVertexOffset = 0;
+
+    std::vector<uint8_t> mIndexBufferData;
+    size_t mIndexCount = 0;
+    size_t mIndexOffset = 0;
+
+    std::unique_ptr<MeshUniformBuilder> mBuilder;
+    SkRect mBounds{};
+
+    mutable SkMesh mMesh{};
+    mutable bool mIsDirty = true;
+    mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+};
+#endif  // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 659aec0..0b58406 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -24,6 +24,7 @@
 #include <experimental/type_traits>
 #include <utility>
 
+#include "Mesh.h"
 #include "SkAndroidFrameworkUtils.h"
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
@@ -502,14 +503,14 @@
         c->drawVertices(vertices, mode, paint);
     }
 };
-struct DrawMesh final : Op {
-    static const auto kType = Type::DrawMesh;
-    DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+struct DrawSkMesh final : Op {
+    static const auto kType = Type::DrawSkMesh;
+    DrawSkMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
             : cpuMesh(mesh), blender(std::move(blender)), paint(paint) {
         isGpuBased = false;
     }
 
-    SkMesh cpuMesh;
+    const SkMesh& cpuMesh;
     mutable SkMesh gpuMesh;
     sk_sp<SkBlender> blender;
     SkPaint paint;
@@ -517,6 +518,7 @@
     mutable GrDirectContext::DirectContextID contextId;
     void draw(SkCanvas* c, const SkMatrix&) const {
         GrDirectContext* directContext = c->recordingContext()->asDirectContext();
+
         GrDirectContext::DirectContextID id = directContext->directContextID();
         if (!isGpuBased || contextId != id) {
             sk_sp<SkMesh::VertexBuffer> vb =
@@ -543,6 +545,18 @@
         c->drawMesh(gpuMesh, blender, paint);
     }
 };
+
+struct DrawMesh final : Op {
+    static const auto kType = Type::DrawMesh;
+    DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+            : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+    const Mesh& mesh;
+    sk_sp<SkBlender> blender;
+    SkPaint paint;
+
+    void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh.getSkMesh(), blender, paint); }
+};
 struct DrawAtlas final : Op {
     static const auto kType = Type::DrawAtlas;
     DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -859,6 +873,10 @@
 }
 void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
                                const SkPaint& paint) {
+    this->push<DrawSkMesh>(0, mesh, blender, paint);
+}
+void DisplayListData::drawMesh(const Mesh& mesh, const sk_sp<SkBlender>& blender,
+                               const SkPaint& paint) {
     this->push<DrawMesh>(0, mesh, blender, paint);
 }
 void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
@@ -1205,6 +1223,9 @@
                                  const SkPaint& paint) {
     fDL->drawMesh(mesh, blender, paint);
 }
+void RecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+    fDL->drawMesh(mesh, blender, paint);
+}
 void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
                                    const SkRect texs[], const SkColor colors[], int count,
                                    SkBlendMode bmode, const SkSamplingOptions& sampling,
@@ -1223,5 +1244,14 @@
     fDL->drawWebView(drawable);
 }
 
+[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
+    LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
+    if (meshWrapper) {
+        return meshWrapper->getSkMesh();
+    } else {
+        return *mesh;
+    }
+}
+
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 8409e13..1f4ba5d 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -28,6 +28,7 @@
 #include <log/log.h>
 
 #include <cstdlib>
+#include <utility>
 #include <vector>
 
 #include "CanvasTransform.h"
@@ -40,6 +41,7 @@
 
 enum class SkBlendMode;
 class SkRRect;
+class Mesh;
 
 namespace android {
 namespace uirenderer {
@@ -66,6 +68,18 @@
 
 static_assert(sizeof(DisplayListOp) == 4);
 
+class DrawMeshPayload {
+public:
+    explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
+    explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
+
+    [[nodiscard]] const SkMesh& getSkMesh() const;
+
+private:
+    const SkMesh* mesh = nullptr;
+    const Mesh* meshWrapper = nullptr;
+};
+
 struct DrawImagePayload {
     explicit DrawImagePayload(Bitmap& bitmap)
             : image(bitmap.makeImage()), palette(bitmap.palette()) {
@@ -143,6 +157,7 @@
     void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
 
     void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+    void drawMesh(const Mesh&, const sk_sp<SkBlender>&, const SkPaint&);
 
     void drawAnnotation(const SkRect&, const char*, SkData*);
     void drawDrawable(SkDrawable*, const SkMatrix*);
@@ -247,6 +262,7 @@
                      SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
 
+    void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint);
     void drawVectorDrawable(VectorDrawableRoot* tree);
     void drawWebView(skiapipeline::FunctorDrawable*);
 
diff --git a/libs/hwui/SafeMath.h b/libs/hwui/SafeMath.h
new file mode 100644
index 0000000..4d6adf5
--- /dev/null
+++ b/libs/hwui/SafeMath.h
@@ -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.
+ */
+
+#ifndef SkSafeMath_DEFINED
+#define SkSafeMath_DEFINED
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+// Copy of Skia's SafeMath API used to validate Mesh parameters to support
+// deferred creation of SkMesh instances on RenderThread.
+// SafeMath always check that a series of operations do not overflow.
+// This must be correct for all platforms, because this is a check for safety at runtime.
+
+class SafeMath {
+public:
+    SafeMath() = default;
+
+    bool ok() const { return fOK; }
+    explicit operator bool() const { return fOK; }
+
+    size_t mul(size_t x, size_t y) {
+        return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y);
+    }
+
+    size_t add(size_t x, size_t y) {
+        size_t result = x + y;
+        fOK &= result >= x;
+        return result;
+    }
+
+    /**
+     *  Return a + b, unless this result is an overflow/underflow. In those cases, fOK will
+     *  be set to false, and it is undefined what this returns.
+     */
+    int addInt(int a, int b) {
+        if (b < 0 && a < std::numeric_limits<int>::min() - b) {
+            fOK = false;
+            return a;
+        } else if (b > 0 && a > std::numeric_limits<int>::max() - b) {
+            fOK = false;
+            return a;
+        }
+        return a + b;
+    }
+
+    // These saturate to their results
+    static size_t Add(size_t x, size_t y) {
+        SafeMath tmp;
+        size_t sum = tmp.add(x, y);
+        return tmp.ok() ? sum : SIZE_MAX;
+    }
+
+    static size_t Mul(size_t x, size_t y) {
+        SafeMath tmp;
+        size_t prod = tmp.mul(x, y);
+        return tmp.ok() ? prod : SIZE_MAX;
+    }
+
+private:
+    uint32_t mul32(uint32_t x, uint32_t y) {
+        uint64_t bx = x;
+        uint64_t by = y;
+        uint64_t result = bx * by;
+        fOK &= result >> 32 == 0;
+        // Overflow information is capture in fOK. Return the result modulo 2^32.
+        return (uint32_t)result;
+    }
+
+    uint64_t mul64(uint64_t x, uint64_t y) {
+        if (x <= std::numeric_limits<uint64_t>::max() >> 32 &&
+            y <= std::numeric_limits<uint64_t>::max() >> 32) {
+            return x * y;
+        } else {
+            auto hi = [](uint64_t x) { return x >> 32; };
+            auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
+
+            uint64_t lx_ly = lo(x) * lo(y);
+            uint64_t hx_ly = hi(x) * lo(y);
+            uint64_t lx_hy = lo(x) * hi(y);
+            uint64_t hx_hy = hi(x) * hi(y);
+            uint64_t result = 0;
+            result = this->add(lx_ly, (hx_ly << 32));
+            result = this->add(result, (lx_hy << 32));
+            fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0;
+
+            return result;
+        }
+    }
+    bool fOK = true;
+};
+
+#endif  // SkSafeMath_DEFINED
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d0124f5..7a12769 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -39,21 +39,22 @@
 #include <SkShader.h>
 #include <SkTextBlob.h>
 #include <SkVertices.h>
+#include <log/log.h>
+#include <ui/FatVector.h>
 
 #include <memory>
 #include <optional>
 #include <utility>
 
 #include "CanvasProperty.h"
+#include "Mesh.h"
 #include "NinePatchUtils.h"
 #include "VectorDrawable.h"
 #include "hwui/Bitmap.h"
 #include "hwui/MinikinUtils.h"
 #include "hwui/PaintFilter.h"
-#include <log/log.h>
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/HolePunch.h"
-#include <ui/FatVector.h>
 
 namespace android {
 
@@ -572,8 +573,14 @@
     applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
 }
 
-void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
-    mCanvas->drawMesh(mesh, blender, paint);
+void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+    GrDirectContext* context = nullptr;
+    auto recordingContext = mCanvas->recordingContext();
+    if (recordingContext) {
+        context = recordingContext->asDirectContext();
+    }
+    mesh.updateSkMesh(context);
+    mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index f2c286a..b785989 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -129,8 +129,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) override;
     virtual void drawPath(const SkPath& path, const Paint& paint) override;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
-    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
-                          const SkPaint& paint) override;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
 
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
     virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 2a20191..44ee31d 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -16,18 +16,17 @@
 
 #pragma once
 
-#include <cutils/compiler.h>
-#include <utils/Functor.h>
 #include <SaveFlags.h>
-
-#include <androidfw/ResourceTypes.h>
-#include "Properties.h"
-#include "pipeline/skia/AnimatedDrawables.h"
-#include "utils/Macros.h"
-
 #include <SkBitmap.h>
 #include <SkCanvas.h>
 #include <SkMatrix.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <utils/Functor.h>
+
+#include "Properties.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include "utils/Macros.h"
 
 class SkAnimatedImage;
 enum class SkBlendMode;
@@ -35,6 +34,7 @@
 class SkRRect;
 class SkRuntimeShaderBuilder;
 class SkVertices;
+class Mesh;
 
 namespace minikin {
 class Font;
@@ -227,7 +227,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) = 0;
     virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
-    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender>, const Paint& paint) = 0;
 
     // Bitmap-based
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/jni/Interpolator.cpp b/libs/hwui/jni/Interpolator.cpp
index fc3d70b..c71e308 100644
--- a/libs/hwui/jni/Interpolator.cpp
+++ b/libs/hwui/jni/Interpolator.cpp
@@ -24,12 +24,8 @@
 
     AutoJavaFloatArray autoValues(env, valueArray);
     AutoJavaFloatArray autoBlend(env, blendArray, 4);
-#ifdef SK_SCALAR_IS_FLOAT
     SkScalar* scalars = autoValues.ptr();
     SkScalar* blend = autoBlend.ptr();
-#else
-    #error Need to convert float array to SkScalar array before calling the following function.
-#endif
 
     interp->setKeyFrame(index, msec, scalars, blend);
 }
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
deleted file mode 100644
index b13d9ba..0000000
--- a/libs/hwui/jni/Mesh.cpp
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <GLES/gl.h>
-#include <Mesh.h>
-#include <SkMesh.h>
-
-#include "GraphicsJNI.h"
-#include "graphics_jni_helpers.h"
-
-namespace android {
-
-sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
-                                            jboolean isDirect) {
-    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
-    auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
-    return vertexBuffer;
-}
-
-sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
-                                          jboolean isDirect) {
-    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
-    auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
-    return indexBuffer;
-}
-
-static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
-                  jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
-                  jfloat right, jfloat bottom) {
-    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
-    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
-            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
-    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshResult = SkMesh::Make(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
-    if (!meshResult.error.isEmpty()) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
-    }
-
-    auto meshPtr = std::make_unique<MeshWrapper>(
-            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
-    return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
-                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
-                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
-                         jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
-    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
-    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
-            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect);
-    sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
-            genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
-    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-
-    auto meshResult = SkMesh::MakeIndexed(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            skIndexBuffer, indexCount, indexOffset,
-            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
-    if (!meshResult.error.isEmpty()) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
-    }
-    auto meshPtr = std::make_unique<MeshWrapper>(
-            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
-    return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    auto mesh = wrapper->mesh;
-    if (indexed) {
-        wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(),
-                                            sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
-                                            mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
-                                            mesh.indexCount(), mesh.indexOffset(),
-                                            wrapper->builder.fUniforms, mesh.bounds())
-                                .mesh;
-    } else {
-        wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(),
-                                     sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
-                                     mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds())
-                                .mesh;
-    }
-}
-
-static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
-    va_list args;
-    va_start(args, fmt);
-    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
-    va_end(args);
-    return ret;
-}
-
-static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
-    switch (type) {
-        case SkRuntimeEffect::Uniform::Type::kFloat:
-        case SkRuntimeEffect::Uniform::Type::kFloat2:
-        case SkRuntimeEffect::Uniform::Type::kFloat3:
-        case SkRuntimeEffect::Uniform::Type::kFloat4:
-        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
-        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
-        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
-            return false;
-        case SkRuntimeEffect::Uniform::Type::kInt:
-        case SkRuntimeEffect::Uniform::Type::kInt2:
-        case SkRuntimeEffect::Uniform::Type::kInt3:
-        case SkRuntimeEffect::Uniform::Type::kInt4:
-            return true;
-    }
-}
-
-static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
-                                      const char* uniformName, const float values[], int count,
-                                      bool isColor) {
-    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
-    if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
-    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
-        if (isColor) {
-            jniThrowExceptionFmt(
-                    env, "java/lang/IllegalArgumentException",
-                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
-                    uniformName, uniform.fVar->flags);
-        } else {
-            ThrowIAEFmt(env,
-                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
-                        uniformName);
-        }
-    } else if (isIntUniformType(uniform.fVar->type)) {
-        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
-                    uniformName);
-    } else if (!uniform.set<float>(values, count)) {
-        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
-                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
-    }
-}
-
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
-                                jint count) {
-    auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    const float values[4] = {value1, value2, value3, value4};
-    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
-}
-
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
-                                     jfloatArray jvalues, jboolean isColor) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, jUniformName);
-    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
-    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
-                              autoValues.length(), isColor);
-}
-
-static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
-                                    const char* uniformName, const int values[], int count) {
-    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
-    if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
-    } else if (!isIntUniformType(uniform.fVar->type)) {
-        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
-                    uniformName);
-    } else if (!uniform.set<int>(values, count)) {
-        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
-                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
-    }
-}
-
-static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                              jint value1, jint value2, jint value3, jint value4, jint count) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    const int values[4] = {value1, value2, value3, value4};
-    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
-}
-
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                                   jintArray values) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    AutoJavaIntArray autoValues(env, values, 0);
-    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
-                            autoValues.length());
-}
-
-static void MeshWrapper_destroy(MeshWrapper* wrapper) {
-    delete wrapper;
-}
-
-static jlong getMeshFinalizer(JNIEnv*, jobject) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
-}
-
-static const JNINativeMethod gMeshMethods[] = {
-        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
-        {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
-        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
-         (void*)makeIndexed},
-        {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
-
-int register_android_graphics_Mesh(JNIEnv* env) {
-    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
-    return 0;
-}
-
-}  // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
deleted file mode 100644
index 61c2260..0000000
--- a/libs/hwui/jni/Mesh.h
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-
-#include <SkMesh.h>
-#include <jni.h>
-
-#include <log/log.h>
-#include <utility>
-
-#include "graphics_jni_helpers.h"
-
-#define gIndexByteSize 2
-
-// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
-// direct and indrect buffers, allowing access to the underlying data in both
-// situations. If passed a null buffer, we will throw NullPointerException,
-// and c_data will return nullptr.
-//
-// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
-// conversion.
-class ScopedJavaNioBuffer {
-public:
-    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
-            : mEnv(env), mBuffer(buffer) {
-        if (buffer == nullptr) {
-            mDataBase = nullptr;
-            mData = nullptr;
-            jniThrowNullPointerException(env);
-        } else {
-            mArray = (jarray) nullptr;
-            if (isDirect) {
-                mData = getDirectBufferPointer(mEnv, mBuffer);
-            } else {
-                mData = setIndirectData(size);
-            }
-        }
-    }
-
-    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
-
-    ~ScopedJavaNioBuffer() { reset(); }
-
-    void reset() {
-        if (mDataBase) {
-            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
-            mDataBase = nullptr;
-        }
-    }
-
-    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
-        if (this != &rhs) {
-            reset();
-
-            mEnv = rhs.mEnv;
-            mBuffer = rhs.mBuffer;
-            mDataBase = rhs.mDataBase;
-            mData = rhs.mData;
-            mArray = rhs.mArray;
-            rhs.mEnv = nullptr;
-            rhs.mData = nullptr;
-            rhs.mBuffer = nullptr;
-            rhs.mArray = nullptr;
-            rhs.mDataBase = nullptr;
-        }
-        return *this;
-    }
-
-    const void* data() const { return mData; }
-
-private:
-    /**
-     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
-     * from a java.nio.Buffer.
-     */
-    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
-        if (buffer == nullptr) {
-            return nullptr;
-        }
-
-        jint position;
-        jint limit;
-        jint elementSizeShift;
-        jlong pointer;
-        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
-        if (pointer == 0) {
-            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
-                              "Must use a native order direct Buffer");
-            return nullptr;
-        }
-        pointer += position << elementSizeShift;
-        return reinterpret_cast<void*>(pointer);
-    }
-
-    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
-        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
-    }
-
-    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
-                            jint* offset) {
-        jint position;
-        jint limit;
-        jint elementSizeShift;
-
-        jlong pointer;
-        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
-        *remaining = (limit - position) << elementSizeShift;
-        if (pointer != 0L) {
-            *array = nullptr;
-            pointer += position << elementSizeShift;
-            return reinterpret_cast<void*>(pointer);
-        }
-
-        *array = jniGetNioBufferBaseArray(env, buffer);
-        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
-        return nullptr;
-    }
-
-    /**
-     * This is a copy of
-     * static void android_glBufferData__IILjava_nio_Buffer_2I
-     * from com_google_android_gles_jni_GLImpl.cpp
-     */
-    void* setIndirectData(jint size) {
-        jint exception;
-        const char* exceptionType;
-        const char* exceptionMessage;
-        jint bufferOffset = (jint)0;
-        jint remaining;
-        void* tempData;
-
-        if (mBuffer) {
-            tempData =
-                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
-            if (remaining < size) {
-                exception = 1;
-                exceptionType = "java/lang/IllegalArgumentException";
-                exceptionMessage = "remaining() < size < needed";
-                goto exit;
-            }
-        }
-        if (mBuffer && tempData == nullptr) {
-            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
-            tempData = (void*)(mDataBase + bufferOffset);
-        }
-        return tempData;
-    exit:
-        if (mArray) {
-            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
-        }
-        if (exception) {
-            jniThrowException(mEnv, exceptionType, exceptionMessage);
-        }
-        return nullptr;
-    }
-
-    JNIEnv* mEnv;
-
-    // Java Buffer data
-    void* mData;
-    jobject mBuffer;
-
-    // Indirect Buffer Data
-    jarray mArray;
-    char* mDataBase;
-};
-
-class MeshUniformBuilder {
-public:
-    struct MeshUniform {
-        template <typename T>
-        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
-                const T& val) {
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-            } else if (sizeof(val) != fVar->sizeInBytes()) {
-                LOG_FATAL("Incorrect value size");
-            } else {
-                void* dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                memcpy(dst, &val, sizeof(val));
-            }
-        }
-
-        MeshUniform& operator=(const SkMatrix& val) {
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
-                LOG_FATAL("Incorrect value size");
-            } else {
-                float* data = reinterpret_cast<float*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                data[0] = val.get(0);
-                data[1] = val.get(3);
-                data[2] = val.get(6);
-                data[3] = val.get(1);
-                data[4] = val.get(4);
-                data[5] = val.get(7);
-                data[6] = val.get(2);
-                data[7] = val.get(5);
-                data[8] = val.get(8);
-            }
-            return *this;
-        }
-
-        template <typename T>
-        bool set(const T val[], const int count) {
-            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-                return false;
-            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
-                LOG_FATAL("Incorrect value size");
-                return false;
-            } else {
-                void* dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                memcpy(dst, val, sizeof(T) * count);
-            }
-            return true;
-        }
-
-        MeshUniformBuilder* fOwner;
-        const SkRuntimeEffect::Uniform* fVar;
-    };
-    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
-
-    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
-        fMeshSpec = sk_sp(meshSpec);
-        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
-    }
-
-    sk_sp<SkData> fUniforms;
-
-private:
-    void* writableUniformData() {
-        if (!fUniforms->unique()) {
-            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
-        }
-        return fUniforms->writable_data();
-    }
-
-    sk_sp<SkMeshSpecification> fMeshSpec;
-};
-
-struct MeshWrapper {
-    SkMesh mesh;
-    MeshUniformBuilder builder;
-};
-#endif  // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp
index 3694ce0..a5e0476 100644
--- a/libs/hwui/jni/Path.cpp
+++ b/libs/hwui/jni/Path.cpp
@@ -182,11 +182,7 @@
         SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
         SkPathDirection dir = static_cast<SkPathDirection>(dirHandle);
         AutoJavaFloatArray  afa(env, array, 8);
-#ifdef SK_SCALAR_IS_FLOAT
         const float* src = afa.ptr();
-#else
-        #error Need to convert float array to SkScalar array before calling the following function.
-#endif
         obj->addRoundRect(rect, src, dir);
     }
 
diff --git a/libs/hwui/jni/PathEffect.cpp b/libs/hwui/jni/PathEffect.cpp
index f99bef7..3dbe1a6 100644
--- a/libs/hwui/jni/PathEffect.cpp
+++ b/libs/hwui/jni/PathEffect.cpp
@@ -35,11 +35,7 @@
                                       jfloatArray intervalArray, jfloat phase) {
         AutoJavaFloatArray autoInterval(env, intervalArray);
         int         count = autoInterval.length() & ~1;  // even number
-#ifdef SK_SCALAR_IS_FLOAT
-        SkScalar*   intervals = autoInterval.ptr();
-#else
-        #error Need to convert float array to SkScalar array before calling the following function.
-#endif
+        SkScalar* intervals = autoInterval.ptr();
         SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release();
         return reinterpret_cast<jlong>(effect);
     }
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 8a0db1c..75d45e5 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -52,12 +52,7 @@
 static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
 {
     AutoJavaFloatArray  autoHSV(env, hsvArray, 3);
-#ifdef SK_SCALAR_IS_FLOAT
-    SkScalar*   hsv = autoHSV.ptr();
-#else
-    #error Need to convert float array to SkScalar array before calling the following function.
-#endif
-
+    SkScalar* hsv = autoHSV.ptr();
     return static_cast<jint>(SkHSVToColor(alpha, hsv));
 }
 
@@ -149,11 +144,7 @@
     std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
 
     AutoJavaFloatArray autoPos(env, posArray, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
     SkScalar* pos = autoPos.ptr();
-#else
-    #error Need to convert float array to SkScalar array before calling the following function.
-#endif
 
     sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
                 GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
@@ -193,11 +184,7 @@
     std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
 
     AutoJavaFloatArray autoPos(env, posArray, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
     SkScalar* pos = autoPos.ptr();
-#else
-    #error Need to convert float array to SkScalar array before calling the following function.
-#endif
 
     auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
     auto skTileMode = static_cast<SkTileMode>(tileMode);
@@ -225,11 +212,7 @@
     std::vector<SkColor4f> colors = convertColorLongs(env, colorArray);
 
     AutoJavaFloatArray autoPos(env, jpositions, colors.size());
-#ifdef SK_SCALAR_IS_FLOAT
     SkScalar* pos = autoPos.ptr();
-#else
-    #error Need to convert float array to SkScalar array before calling the following function.
-#endif
 
     sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
             GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8a4d4e1..8ba7503 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -21,7 +21,6 @@
 #else
 #define __ANDROID_API_P__ 28
 #endif
-#include <Mesh.h>
 #include <androidfw/ResourceTypes.h>
 #include <hwui/Canvas.h>
 #include <hwui/Paint.h>
@@ -446,10 +445,10 @@
 
 static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
                      jlong paintHandle) {
-    const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh;
+    const Mesh* mesh = reinterpret_cast<Mesh*>(meshHandle);
     SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
-    SkPaint* paint = reinterpret_cast<Paint*>(paintHandle);
-    get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint);
+    Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+    get_canvas(canvasHandle)->drawMesh(*mesh, SkBlender::Mode(blendMode), *paint);
 }
 
 static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index cf6702e..ca667b0 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -23,8 +23,6 @@
 
 static_assert(sizeof(SkMatrix) == 40, "Unexpected sizeof(SkMatrix), "
         "update size in Matrix.java#NATIVE_ALLOCATION_SIZE and here");
-static_assert(SK_SCALAR_IS_FLOAT, "SK_SCALAR_IS_FLOAT is false, "
-        "only float scalar is supported");
 
 class SkMatrixGlue {
 public:
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
new file mode 100644
index 0000000..04339dc
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <GrDirectContext.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect)
+            : mEnv(env), mBuffer(buffer) {
+        if (buffer == nullptr) {
+            mDataBase = nullptr;
+            mData = nullptr;
+            jniThrowNullPointerException(env);
+        } else {
+            mArray = (jarray) nullptr;
+            if (isDirect) {
+                mData = getDirectBufferPointer(mEnv, mBuffer);
+            } else {
+                mData = setIndirectData(size);
+            }
+        }
+    }
+
+    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+    ~ScopedJavaNioBuffer() { reset(); }
+
+    void reset() {
+        if (mDataBase) {
+            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+            mDataBase = nullptr;
+        }
+    }
+
+    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+        if (this != &rhs) {
+            reset();
+
+            mEnv = rhs.mEnv;
+            mBuffer = rhs.mBuffer;
+            mDataBase = rhs.mDataBase;
+            mData = rhs.mData;
+            mArray = rhs.mArray;
+            rhs.mEnv = nullptr;
+            rhs.mData = nullptr;
+            rhs.mBuffer = nullptr;
+            rhs.mArray = nullptr;
+            rhs.mDataBase = nullptr;
+        }
+        return *this;
+    }
+
+    const void* data() const { return mData; }
+
+private:
+    /**
+     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+     * from a java.nio.Buffer.
+     */
+    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+        if (buffer == nullptr) {
+            return nullptr;
+        }
+
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        if (pointer == 0) {
+            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+                              "Must use a native order direct Buffer");
+            return nullptr;
+        }
+        pointer += position << elementSizeShift;
+        return reinterpret_cast<void*>(pointer);
+    }
+
+    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+    }
+
+    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+                            jint* offset) {
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        *remaining = (limit - position) << elementSizeShift;
+        if (pointer != 0L) {
+            *array = nullptr;
+            pointer += position << elementSizeShift;
+            return reinterpret_cast<void*>(pointer);
+        }
+
+        *array = jniGetNioBufferBaseArray(env, buffer);
+        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+        return nullptr;
+    }
+
+    /**
+     * This is a copy of
+     * static void android_glBufferData__IILjava_nio_Buffer_2I
+     * from com_google_android_gles_jni_GLImpl.cpp
+     */
+    void* setIndirectData(size_t size) {
+        jint exception;
+        const char* exceptionType;
+        const char* exceptionMessage;
+        jint bufferOffset = (jint)0;
+        jint remaining;
+        void* tempData;
+
+        if (mBuffer) {
+            tempData =
+                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+            if (remaining < size) {
+                exception = 1;
+                exceptionType = "java/lang/IllegalArgumentException";
+                exceptionMessage = "remaining() < size < needed";
+                goto exit;
+            }
+        }
+        if (mBuffer && tempData == nullptr) {
+            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+            tempData = (void*)(mDataBase + bufferOffset);
+        }
+        return tempData;
+    exit:
+        if (mArray) {
+            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+        }
+        if (exception) {
+            jniThrowException(mEnv, exceptionType, exceptionMessage);
+        }
+        return nullptr;
+    }
+
+    JNIEnv* mEnv;
+
+    // Java Buffer data
+    void* mData;
+    jobject mBuffer;
+
+    // Indirect Buffer Data
+    jarray mArray;
+    char* mDataBase;
+};
+
+namespace android {
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                  jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
+                  jfloat right, jfloat bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    size_t bufferSize = vertexCount * skMeshSpec->stride();
+    auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset,
+                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto [valid, msg] = meshPtr->validate();
+    if (!valid) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+    }
+    return reinterpret_cast<jlong>(meshPtr);
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+                         jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    auto vertexBufferSize = vertexCount * skMeshSpec->stride();
+    auto indexBufferSize = indexCount * gIndexByteSize;
+    auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect);
+    auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount,
+                            vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset,
+                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto [valid, msg] = meshPtr->validate();
+    if (!valid) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+    }
+
+    return reinterpret_cast<jlong>(meshPtr);
+}
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                      const char* uniformName, const float values[], int count,
+                                      bool isColor) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+                                jint count) {
+    auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    const float values[4] = {value1, value2, value3, value4};
+    nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
+    wrapper->markDirty();
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
+                                     jfloatArray jvalues, jboolean isColor) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, jUniformName);
+    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+    nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+                              autoValues.length(), isColor);
+    wrapper->markDirty();
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                    const char* uniformName, const int values[], int count) {
+    MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                              jint value1, jint value2, jint value3, jint value4, jint count) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    const int values[4] = {value1, value2, value3, value4};
+    nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
+    wrapper->markDirty();
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                                   jintArray values) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    AutoJavaIntArray autoValues(env, values, 0);
+    nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+                            autoValues.length());
+    wrapper->markDirty();
+}
+
+static void MeshWrapper_destroy(Mesh* wrapper) {
+    delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+        {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
+        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
+         (void*)makeIndexed},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+    return 0;
+}
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index fcfc4f8..af2d3b3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -23,6 +23,7 @@
 #else
 #include "DamageAccumulator.h"
 #endif
+#include "TreeInfo.h"
 #include "VectorDrawable.h"
 #ifdef __ANDROID__
 #include "renderthread/CanvasContext.h"
@@ -102,6 +103,12 @@
         info.prepareTextures = false;
         info.canvasContext.unpinImages();
     }
+
+    auto grContext = info.canvasContext.getGrContext();
+    for (auto mesh : mMeshes) {
+        mesh->updateSkMesh(grContext);
+    }
+
 #endif
 
     bool hasBackwardProjectedNodesHere = false;
@@ -168,6 +175,7 @@
 
     mDisplayList.reset();
 
+    mMeshes.clear();
     mMutableImages.clear();
     mVectorDrawables.clear();
     mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 2a67734..7af31a4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -18,6 +18,7 @@
 
 #include <deque>
 
+#include "Mesh.h"
 #include "RecordingCanvas.h"
 #include "RenderNodeDrawable.h"
 #include "TreeInfo.h"
@@ -167,6 +168,7 @@
     std::deque<RenderNodeDrawable> mChildNodes;
     std::deque<FunctorDrawable*> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
+    std::vector<const Mesh*> mMeshes;
 
 private:
     std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e1c8877..3ca7eeb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -321,6 +321,11 @@
     return 0;
 }
 
+void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+    mDisplayList->mMeshes.push_back(&mesh);
+    mRecorder.drawMesh(mesh, blender, paint);
+}
+
 }  // namespace skiapipeline
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 3fd8fa3..a8e4580 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -81,6 +81,7 @@
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
     virtual void enableZ(bool enableZ) override;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 3d718a2..0e604ce 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -45,6 +45,8 @@
 import androidx.credentials.CreateCredentialRequest.DisplayInfo
 import androidx.credentials.CreatePublicKeyCredentialRequest
 import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.GetPasswordOption
+import androidx.credentials.GetPublicKeyCredentialOption
 
 import java.time.Instant
 
@@ -71,7 +73,7 @@
         requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
-        ) ?: testCreatePasswordRequestInfo()
+        ) ?: testCreatePasskeyRequestInfo()
 
         val originName: String? = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
@@ -120,8 +122,7 @@
                         providerDisableListUiState,
                         defaultProviderId,
                         requestDisplayInfoUiState,
-                        /** isOnPasskeyIntroStateAlready */
-                        false,
+                        isOnPasskeyIntroStateAlready = false,
                         isPasskeyFirstUse
                     )!!,
                     getCredentialUiState = null,
@@ -400,7 +401,8 @@
                 "                   \"authenticatorSelection\": {\n" +
                 "                     \"residentKey\": \"required\",\n" +
                 "                     \"requireResidentKey\": true\n" +
-                "                   }}"
+                "                   }}",
+            preferImmediatelyAvailableCredentials = true,
         )
         val credentialData = request.credentialData
         return RequestInfo.newCreateRequestInfo(
@@ -446,19 +448,28 @@
     }
 
     private fun testGetRequestInfo(): RequestInfo {
+        val passwordOption = GetPasswordOption()
+        val passkeyOption = GetPublicKeyCredentialOption(
+            "json", preferImmediatelyAvailableCredentials = false)
         return RequestInfo.newGetRequestInfo(
             Binder(),
             GetCredentialRequest.Builder(
                 Bundle()
             ).addCredentialOption(
                 CredentialOption(
-                    "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
-                    Bundle(),
-                    Bundle(), /*isSystemProviderRequired=*/
-                    false
+                    passwordOption.type,
+                    passwordOption.requestData,
+                    passwordOption.candidateQueryData,
+                    passwordOption.isSystemProviderRequired
                 )
-            )
-                .build(),
+            ).addCredentialOption(
+                CredentialOption(
+                    passkeyOption.type,
+                    passkeyOption.requestData,
+                    passkeyOption.candidateQueryData,
+                    passkeyOption.isSystemProviderRequired
+                )
+            ).build(),
             "com.google.android.youtube"
         )
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 9c9c2a3..a834994 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -50,7 +50,11 @@
 import androidx.credentials.CreateCredentialRequest
 import androidx.credentials.CreateCustomCredentialRequest
 import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CredentialOption
 import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.CreatePublicKeyCredentialRequestPrivileged
+import androidx.credentials.GetPublicKeyCredentialOption
+import androidx.credentials.GetPublicKeyCredentialOptionPrivileged
 import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 import androidx.credentials.provider.Action
 import androidx.credentials.provider.AuthenticationAction
@@ -172,10 +176,27 @@
             context: Context,
             originName: String?,
         ): com.android.credentialmanager.getflow.RequestDisplayInfo? {
+            val getCredentialRequest = requestInfo.getCredentialRequest ?: return null
+            val preferImmediatelyAvailableCredentials = getCredentialRequest.credentialOptions.any {
+                val credentialOptionJetpack = CredentialOption.createFrom(
+                    it.type,
+                    it.credentialRetrievalData,
+                    it.credentialRetrievalData,
+                    it.isSystemProviderRequired
+                )
+                if (credentialOptionJetpack is GetPublicKeyCredentialOption) {
+                    credentialOptionJetpack.preferImmediatelyAvailableCredentials
+                } else if (credentialOptionJetpack is GetPublicKeyCredentialOptionPrivileged) {
+                    credentialOptionJetpack.preferImmediatelyAvailableCredentials
+                } else {
+                    false
+                }
+            }
             return com.android.credentialmanager.getflow.RequestDisplayInfo(
                 appName = originName
                     ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
-                    ?: return null
+                    ?: return null,
+                preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
             )
         }
 
@@ -415,24 +436,25 @@
                     createCredentialRequestJetpack.password,
                     CredentialType.PASSWORD,
                     appLabel,
-                    context.getDrawable(R.drawable.ic_password)!!
+                    context.getDrawable(R.drawable.ic_password) ?: return null,
+                    preferImmediatelyAvailableCredentials = false,
                 )
                 is CreatePublicKeyCredentialRequest -> {
-                    val requestJson = createCredentialRequestJetpack.requestJson
-                    val json = JSONObject(requestJson)
-                    var name = ""
-                    var displayName = ""
-                    if (json.has("user")) {
-                        val user: JSONObject = json.getJSONObject("user")
-                        name = user.getString("name")
-                        displayName = user.getString("displayName")
-                    }
-                    RequestDisplayInfo(
-                        name,
-                        displayName,
-                        CredentialType.PASSKEY,
-                        appLabel,
-                        context.getDrawable(R.drawable.ic_passkey)!!
+                    newRequestDisplayInfoFromPasskeyJson(
+                        requestJson = createCredentialRequestJetpack.requestJson,
+                        appLabel = appLabel,
+                        context = context,
+                        preferImmediatelyAvailableCredentials =
+                        createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+                    )
+                }
+                is CreatePublicKeyCredentialRequestPrivileged -> {
+                    newRequestDisplayInfoFromPasskeyJson(
+                        requestJson = createCredentialRequestJetpack.requestJson,
+                        appLabel = appLabel,
+                        context = context,
+                        preferImmediatelyAvailableCredentials =
+                        createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -446,7 +468,8 @@
                         type = CredentialType.UNKNOWN,
                         appName = appLabel,
                         typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
-                            ?: context.getDrawable(R.drawable.ic_other_sign_in)!!
+                            ?: context.getDrawable(R.drawable.ic_other_sign_in) ?: return null,
+                        preferImmediatelyAvailableCredentials = false,
                     )
                 }
                 else -> null
@@ -613,5 +636,29 @@
                 )
             } else null
         }
+
+        private fun newRequestDisplayInfoFromPasskeyJson(
+            requestJson: String,
+            appLabel: String,
+            context: Context,
+            preferImmediatelyAvailableCredentials: Boolean,
+        ): RequestDisplayInfo? {
+            val json = JSONObject(requestJson)
+            var name = ""
+            var displayName = ""
+            if (json.has("user")) {
+                val user: JSONObject = json.getJSONObject("user")
+                name = user.getString("name")
+                displayName = user.getString("displayName")
+            }
+            return RequestDisplayInfo(
+                name,
+                displayName,
+                CredentialType.PASSKEY,
+                appLabel,
+                context.getDrawable(R.drawable.ic_passkey) ?: return null,
+                preferImmediatelyAvailableCredentials,
+            )
+        }
     }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index ce4e936..192fa15 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -38,7 +38,9 @@
 )
 
 internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
-    return state.sortedCreateOptionsPairs.isNotEmpty()
+    return state.sortedCreateOptionsPairs.isNotEmpty() ||
+        (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials &&
+            state.remoteEntry != null)
 }
 
 open class ProviderInfo(
@@ -104,6 +106,7 @@
   val type: CredentialType,
   val appName: String,
   val typeIcon: Drawable,
+  val preferImmediatelyAvailableCredentials: Boolean,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index d1f0f81..9727d3f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -37,7 +37,8 @@
 internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean {
     return state.providerDisplayInfo.sortedUserNameToCredentialEntryList.isNotEmpty() ||
         state.providerDisplayInfo.authenticationEntryList.isNotEmpty() ||
-        state.providerDisplayInfo.remoteEntry != null
+        (state.providerDisplayInfo.remoteEntry != null &&
+            !state.requestDisplayInfo.preferImmediatelyAvailableCredentials)
 }
 
 data class ProviderInfo(
@@ -146,6 +147,7 @@
 
 data class RequestDisplayInfo(
     val appName: String,
+    val preferImmediatelyAvailableCredentials: Boolean,
 )
 
 /**
@@ -246,7 +248,6 @@
 private fun toGetScreenState(
     providerDisplayInfo: ProviderDisplayInfo
 ): GetScreenState {
-
     return if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
         providerDisplayInfo.remoteEntry == null &&
         providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty })
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 724588f..3fdb1d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -77,9 +77,8 @@
         return true
     }
 
-    fun isEnabled(): Boolean {
-        return getPageProvider(sppName)?.isEnabled(arguments) ?: false
-    }
+    fun isEnabled(): Boolean =
+        SpaEnvironment.IS_DEBUG || getPageProvider(sppName)?.isEnabled(arguments) ?: false
 
     fun getTitle(): String {
         return getPageProvider(sppName)?.getTitle(arguments) ?: ""
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 02962a5..2d956d5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -88,4 +88,13 @@
     open val sliceProviderAuthorities: String? = null
 
     // TODO: add other environment setup here.
+    companion object {
+        /**
+         * Whether debug mode is on or off.
+         *
+         * If set to true, this will also enable all the pages under development (allows browsing
+         * and searching).
+         */
+        const val IS_DEBUG = false
+    }
 }
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 1ac20471..346462d 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -48,6 +48,7 @@
         "test/**/*.java",
         "src/android/provider/settings/backup/*",
         "src/android/provider/settings/validators/*",
+        "src/com/android/providers/settings/GenerationRegistry.java",
         "src/com/android/providers/settings/SettingsBackupAgent.java",
         "src/com/android/providers/settings/SettingsState.java",
         "src/com/android/providers/settings/SettingsHelper.java",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 5617331..7f3b0ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -17,19 +17,19 @@
 package com.android.providers.settings;
 
 import android.os.Bundle;
-import android.os.UserManager;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.MemoryIntArray;
 import android.util.Slog;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 
 /**
  * This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates a shared memory region which
+ * on a per user basis and updates shared memory regions which
  * client processes can read to determine if their local caches are
  * stale.
  */
@@ -40,138 +40,187 @@
 
     private final Object mLock;
 
+    // Key -> backingStore mapping
     @GuardedBy("mLock")
-    private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
+    private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
+
+    // Key -> (String->Index map) mapping
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    // Maximum number of backing stores allowed
+    static final int NUM_MAX_BACKING_STORE = 8;
 
     @GuardedBy("mLock")
-    private MemoryIntArray mBackingStore;
+    private int mNumBackingStore = 0;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    // Maximum size of an individual backing store
+    static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
 
     public GenerationRegistry(Object lock) {
         mLock = lock;
     }
 
-    public void incrementGeneration(int key) {
+    /**
+     *  Increment the generation number if the setting is already cached in the backing stores.
+     *  Otherwise, do nothing.
+     */
+    public void incrementGeneration(int key, String name) {
+        final boolean isConfig =
+                (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
+        // Only store the prefix if the mutated setting is a config
+        final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
         synchronized (mLock) {
-            MemoryIntArray backingStore = getBackingStoreLocked();
-            if (backingStore != null) {
-                try {
-                    final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
-                    if (index >= 0) {
-                        final int generation = backingStore.get(index) + 1;
-                        backingStore.set(index, generation);
-                    }
-                } catch (IOException e) {
-                    Slog.e(LOG_TAG, "Error updating generation id", e);
-                    destroyBackingStore();
+            final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                    /* createIfNotExist= */ false);
+            if (backingStore == null) {
+                return;
+            }
+            try {
+                final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+                        backingStore, /* createIfNotExist= */ false);
+                if (index < 0) {
+                    return;
                 }
+                final int generation = backingStore.get(index) + 1;
+                backingStore.set(index, generation);
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
+                            + " key:" + SettingsState.keyToString(key)
+                            + " at index:" + index);
+                }
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "Error updating generation id", e);
+                destroyBackingStoreLocked(key);
             }
         }
     }
 
-    public void addGenerationData(Bundle bundle, int key) {
+    /**
+     *  Return the backing store's reference, the index and the current generation number
+     *  of a cached setting. If it was not in the backing store, first create the entry in it before
+     *  returning the result.
+     */
+    public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
         synchronized (mLock) {
-            MemoryIntArray backingStore = getBackingStoreLocked();
+            final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                    /* createIfNotExist= */ true);
+            if (backingStore == null) {
+                // Error accessing existing backing store or no new backing store is available
+                return;
+            }
             try {
-                if (backingStore != null) {
-                    final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
-                    if (index >= 0) {
-                        bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
-                                backingStore);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
-                        bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
-                                backingStore.get(index));
-                        if (DEBUG) {
-                            Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
-                                    + SettingsProvider.keyToString(key));
-                        }
-                    }
+                final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+                        backingStore, /* createIfNotExist= */ true);
+                if (index < 0) {
+                    // Should not happen unless having error accessing the backing store
+                    return;
+                }
+                bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+                        backingStore);
+                bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+                bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+                        backingStore.get(index));
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Exported index:" + index
+                            + " for setting:" + indexMapKey
+                            + " key:" + SettingsState.keyToString(key));
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Error adding generation data", e);
-                destroyBackingStore();
+                destroyBackingStoreLocked(key);
             }
         }
     }
 
     public void onUserRemoved(int userId) {
+        final int secureKey = SettingsState.makeKey(
+                SettingsState.SETTINGS_TYPE_SECURE, userId);
+        final int systemKey = SettingsState.makeKey(
+                SettingsState.SETTINGS_TYPE_SYSTEM, userId);
         synchronized (mLock) {
-            MemoryIntArray backingStore = getBackingStoreLocked();
-            if (backingStore != null && mKeyToIndexMap.size() > 0) {
-                try {
-                    final int secureKey = SettingsProvider.makeKey(
-                            SettingsProvider.SETTINGS_TYPE_SECURE, userId);
-                    resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
-
-                    final int systemKey = SettingsProvider.makeKey(
-                            SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
-                    resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
-                } catch (IOException e) {
-                    Slog.e(LOG_TAG, "Error cleaning up for user", e);
-                    destroyBackingStore();
-                }
+            if (mKeyToIndexMapMap.containsKey(secureKey)) {
+                destroyBackingStoreLocked(secureKey);
+                mKeyToIndexMapMap.remove(secureKey);
+                mNumBackingStore = mNumBackingStore - 1;
+            }
+            if (mKeyToIndexMapMap.containsKey(systemKey)) {
+                destroyBackingStoreLocked(systemKey);
+                mKeyToIndexMapMap.remove(systemKey);
+                mNumBackingStore = mNumBackingStore - 1;
             }
         }
     }
 
     @GuardedBy("mLock")
-    private MemoryIntArray getBackingStoreLocked() {
-        if (mBackingStore == null) {
-            // One for the config table, one for the global table, two for system
-            // and secure tables for a managed profile (managed profile is not
-            // included in the max user count), ten for partially deleted users if
-            // users are quickly removed, and twice max user count for system and
-            // secure.
-            final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
+    private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
+        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+        if (!createIfNotExist) {
+            return backingStore;
+        }
+        if (backingStore == null) {
             try {
-                mBackingStore = new MemoryIntArray(size);
+                if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
+                    Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+                    return null;
+                }
+                backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
+                mKeyToBackingStoreMap.put(key, backingStore);
+                mNumBackingStore += 1;
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
+                    Slog.e(LOG_TAG, "Created backing store for "
+                            + SettingsState.keyToString(key) + " on user: "
+                            + SettingsState.getUserIdFromKey(key));
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Error creating generation tracker", e);
             }
         }
-        return mBackingStore;
+        return backingStore;
     }
 
-    private void destroyBackingStore() {
-        if (mBackingStore != null) {
+    @GuardedBy("mLock")
+    private void destroyBackingStoreLocked(int key) {
+        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+        if (backingStore != null) {
             try {
-                mBackingStore.close();
+                backingStore.close();
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
+                    Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
                 }
             } catch (IOException e) {
                 Slog.e(LOG_TAG, "Cannot close generation memory array", e);
             }
-            mBackingStore = null;
+            mKeyToBackingStoreMap.remove(key);
         }
     }
 
-    private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
-            MemoryIntArray backingStore) throws IOException {
-        final int index = keyToIndexMap.get(key, -1);
-        if (index >= 0) {
-            keyToIndexMap.delete(key);
-            backingStore.set(index, 0);
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
-                        + SettingsProvider.keyToString(key));
+    private static int getKeyIndexLocked(int key, String indexMapKey,
+            ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
+            MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
+        ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
+        if (nameToIndexMap == null) {
+            if (!createIfNotExist) {
+                return -1;
             }
+            nameToIndexMap = new ArrayMap<>();
+            keyToIndexMapMap.put(key, nameToIndexMap);
         }
-    }
-
-    private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
-            MemoryIntArray backingStore) throws IOException {
-        int index = keyToIndexMap.get(key, -1);
+        int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
         if (index < 0) {
+            if (!createIfNotExist) {
+                return -1;
+            }
             index = findNextEmptyIndex(backingStore);
             if (index >= 0) {
                 backingStore.set(index, 1);
-                keyToIndexMap.append(key, index);
+                nameToIndexMap.put(indexMapKey, index);
                 if (DEBUG) {
-                    Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
-                            + SettingsProvider.keyToString(key));
+                    Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
+                            + " of type:" + SettingsState.keyToString(key)
+                            + " on user:" + SettingsState.getUserIdFromKey(key));
                 }
             } else {
                 Slog.e(LOG_TAG, "Could not allocate generation index");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 683c08e..ba275eb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
 import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.makeKey;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -375,10 +376,6 @@
     @GuardedBy("mLock")
     private boolean mSyncConfigDisabledUntilReboot;
 
-    public static int makeKey(int type, int userId) {
-        return SettingsState.makeKey(type, userId);
-    }
-
     public static int getTypeFromKey(int key) {
         return SettingsState.getTypeFromKey(key);
     }
@@ -387,9 +384,6 @@
         return SettingsState.getUserIdFromKey(key);
     }
 
-    public static String keyToString(int key) {
-        return SettingsState.keyToString(key);
-    }
     @ChangeId
     @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
     private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -428,22 +422,26 @@
         switch (method) {
             case Settings.CALL_METHOD_GET_CONFIG: {
                 Setting setting = getConfigSetting(name);
-                return packageValueForCallResult(setting, isTrackingGeneration(args));
+                return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId,
+                        setting, isTrackingGeneration(args));
             }
 
             case Settings.CALL_METHOD_GET_GLOBAL: {
                 Setting setting = getGlobalSetting(name);
-                return packageValueForCallResult(setting, isTrackingGeneration(args));
+                return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId,
+                        setting, isTrackingGeneration(args));
             }
 
             case Settings.CALL_METHOD_GET_SECURE: {
                 Setting setting = getSecureSetting(name, requestingUserId);
-                return packageValueForCallResult(setting, isTrackingGeneration(args));
+                return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId,
+                        setting, isTrackingGeneration(args));
             }
 
             case Settings.CALL_METHOD_GET_SYSTEM: {
                 Setting setting = getSystemSetting(name, requestingUserId);
-                return packageValueForCallResult(setting, isTrackingGeneration(args));
+                return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId,
+                        setting, isTrackingGeneration(args));
             }
 
             case Settings.CALL_METHOD_PUT_CONFIG: {
@@ -553,7 +551,7 @@
 
             case Settings.CALL_METHOD_LIST_CONFIG: {
                 String prefix = getSettingPrefix(args);
-                Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
+                Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
                         isTrackingGeneration(args));
                 reportDeviceConfigAccess(prefix);
                 return result;
@@ -1319,6 +1317,7 @@
         return false;
     }
 
+    @NonNull
     private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2316,7 +2315,8 @@
                 "get/set setting for user", null);
     }
 
-    private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
+    private Bundle packageValueForCallResult(int type, @NonNull String name, int userId,
+            @Nullable Setting setting, boolean trackingGeneration) {
         if (!trackingGeneration) {
             if (setting == null || setting.isNull()) {
                 return NULL_SETTING_BUNDLE;
@@ -2325,21 +2325,40 @@
         }
         Bundle result = new Bundle();
         result.putString(Settings.NameValueTable.VALUE,
-                !setting.isNull() ? setting.getValue() : null);
+                (setting != null && !setting.isNull()) ? setting.getValue() : null);
 
-        mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
+        if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) {
+            // Don't track generation for non-existent settings unless the name is predefined
+            synchronized (mLock) {
+                mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
+                        SettingsState.makeKey(type, userId), name);
+            }
+        }
         return result;
     }
 
-    private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
-            boolean trackingGeneration) {
+    private boolean isSettingPreDefined(String name, int type) {
+        if (type == SETTINGS_TYPE_GLOBAL) {
+            return sAllGlobalSettings.contains(name);
+        } else if (type == SETTINGS_TYPE_SECURE) {
+            return sAllSecureSettings.contains(name);
+        } else if (type == SETTINGS_TYPE_SYSTEM) {
+            return sAllSystemSettings.contains(name);
+        } else {
+            return false;
+        }
+    }
+
+    private Bundle packageValuesForCallResult(String prefix,
+            @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
         Bundle result = new Bundle();
         result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
         if (trackingGeneration) {
+            // Track generation even if the namespace is empty because this is for system apps
             synchronized (mLock) {
                 mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
-                        mSettingsRegistry.getSettingsLocked(
-                                SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
+                        mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
+                                UserHandle.USER_SYSTEM).mKey, prefix);
             }
         }
 
@@ -3449,7 +3468,7 @@
 
         private void notifyForSettingsChange(int key, String name) {
             // Increment the generation first, so observers always see the new value
-            mGenerationRegistry.incrementGeneration(key);
+            mGenerationRegistry.incrementGeneration(key, name);
 
             if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
                 final long token = Binder.clearCallingIdentity();
@@ -3487,7 +3506,7 @@
                 List<String> changedSettings) {
 
             // Increment the generation first, so observers always see the new value
-            mGenerationRegistry.incrementGeneration(key);
+            mGenerationRegistry.incrementGeneration(key, prefix);
 
             StringBuilder stringBuilder = new StringBuilder(prefix);
             for (int i = 0; i < changedSettings.size(); ++i) {
@@ -3513,7 +3532,7 @@
                     if (profileId != userId) {
                         final int key = makeKey(type, profileId);
                         // Increment the generation first, so observers always see the new value
-                        mGenerationRegistry.incrementGeneration(key);
+                        mGenerationRegistry.incrementGeneration(key, name);
                         mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
                                 profileId, 0, uri).sendToTarget();
                     }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
new file mode 100644
index 0000000..d34fe694
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 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.providers.settings;
+
+import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY;
+import static android.provider.Settings.CALL_METHOD_GENERATION_KEY;
+import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.util.MemoryIntArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class GenerationRegistryTest {
+    @Test
+    public void testGenerationsWithRegularSetting() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        // IncrementGeneration should have no effect on a non-cached setting.
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        // Default index is 0 and generation is only 1 despite early calls of incrementGeneration
+        checkBundle(b, 0, 1, false);
+
+        generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        // Index is still 0 and generation is now 2; also check direct array access
+        assertThat(getArray(b).get(0)).isEqualTo(2);
+
+        final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0);
+        final String testSystemSetting = "test_system_setting";
+        generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+        // Default index is 0 and generation is 1 for another backingStore (system)
+        checkBundle(b, 0, 1, false);
+
+        final String testSystemSetting2 = "test_system_setting2";
+        generationRegistry.addGenerationData(b, systemKey, testSystemSetting2);
+        // Second system setting index is 1 and default generation is 1
+        checkBundle(b, 1, 1, false);
+
+        generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+        generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+        generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+        // First system setting generation now incremented to 3
+        checkBundle(b, 0, 3, false);
+
+        final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10);
+        generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+        // User 10 has a new set of backingStores
+        checkBundle(b, 0, 1, false);
+
+        // Check user removal
+        generationRegistry.onUserRemoved(10);
+        generationRegistry.incrementGeneration(systemKey2, testSystemSetting);
+
+        // Removed user should not affect existing caches
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        assertThat(getArray(b).get(0)).isEqualTo(2);
+
+        // IncrementGeneration should have no effect for a non-cached user
+        b.clear();
+        checkBundle(b, -1, -1, true);
+        // AddGeneration should create new backing store for the non-cached user
+        generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+        checkBundle(b, 0, 1, false);
+    }
+
+    @Test
+    public void testGenerationsWithConfigSetting() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final String prefix = "test_namespace/";
+        final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+        Bundle b = new Bundle();
+        generationRegistry.addGenerationData(b, configKey, prefix);
+        checkBundle(b, 0, 1, false);
+
+        final String setting = "test_namespace/test_setting";
+        // Check that the generation of the prefix is incremented correctly
+        generationRegistry.incrementGeneration(configKey, setting);
+        generationRegistry.addGenerationData(b, configKey, prefix);
+        checkBundle(b, 0, 2, false);
+    }
+
+    @Test
+    public void testMaxNumBackingStores() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
+            b.clear();
+            final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
+            generationRegistry.addGenerationData(b, key, testSecureSetting);
+            checkBundle(b, 0, 1, false);
+        }
+        b.clear();
+        final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
+                GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
+        generationRegistry.addGenerationData(b, key, testSecureSetting);
+        // Should fail to add generation because the number of backing stores has reached limit
+        checkBundle(b, -1, -1, true);
+        // Remove one user should free up a backing store
+        generationRegistry.onUserRemoved(0);
+        generationRegistry.addGenerationData(b, key, testSecureSetting);
+        checkBundle(b, 0, 1, false);
+    }
+
+    @Test
+    public void testMaxSizeBackingStore() throws IOException {
+        final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+        final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+        final String testSecureSetting = "test_secure_setting";
+        Bundle b = new Bundle();
+        for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) {
+            generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i);
+            checkBundle(b, i, 1, false);
+        }
+        b.clear();
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+        // Should fail to increase index because the number of entries in the backing store has
+        // reached the limit
+        checkBundle(b, -1, -1, true);
+        // Shouldn't affect other cached entries
+        generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0");
+        checkBundle(b, 0, 1, false);
+    }
+
+    private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
+            throws IOException {
+        final MemoryIntArray array = getArray(b);
+        if (isNull) {
+            assertThat(array).isNull();
+        } else {
+            assertThat(array).isNotNull();
+        }
+        final int index = b.getInt(
+                CALL_METHOD_GENERATION_INDEX_KEY, -1);
+        assertThat(index).isEqualTo(expectedIndex);
+        final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1);
+        assertThat(generation).isEqualTo(expectedGeneration);
+        if (!isNull) {
+            // Read into the result array with the result index should match the result generation
+            assertThat(array.get(index)).isEqualTo(generation);
+        }
+    }
+
+    private MemoryIntArray getArray(Bundle b) {
+        return b.getParcelable(
+                CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class);
+    }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d46961..b236ac5 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -449,6 +449,7 @@
                 enabled: true,
                 optimize: true,
                 shrink: true,
+                shrink_resources: true,
                 proguard_compatibility: false,
                 proguard_flags_files: ["proguard.flags"],
             },
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 7d39c4a..dd60647 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
@@ -23,6 +23,7 @@
 import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.SystemProperties;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicyConstants;
 
@@ -115,6 +116,9 @@
     public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
     // Device dreaming state
     public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
+    // Whether the back gesture is allowed (or ignored) by the Shade
+    public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
+            "persist.wm.debug.shade_allow_back_gesture", false);
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -243,9 +247,14 @@
             sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
         }
         // Disable when in immersive, or the notifications are interactive
-        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN
-                | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
-                | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+
+        // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED.
+        // To allow Shade to respond to Back, we're bypassing this check (behind a flag).
+        if (!ALLOW_BACK_GESTURE_IN_SHADE) {
+            disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+        }
+
         return (sysuiStateFlags & disableFlags) != 0;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd67115..f038c69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -29,6 +29,7 @@
 import static android.hardware.biometrics.BiometricSourceType.FACE;
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -139,9 +140,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
+import com.android.settingslib.Utils;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
@@ -152,6 +153,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -823,6 +825,19 @@
         }
     }
 
+    private void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
+        Assert.isMainThread();
+        Trace.beginSection("KeyGuardUpdateMonitor#onBiometricDetected");
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricDetected(userId, biometricSourceType, isStrongBiometric);
+            }
+        }
+        Trace.endSection();
+    }
+
     @VisibleForTesting
     protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
         Assert.isMainThread();
@@ -899,6 +914,20 @@
         }
     }
 
+    private void handleBiometricDetected(int authUserId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
+        Trace.beginSection("KeyGuardUpdateMonitor#handlerBiometricDetected");
+        onBiometricDetected(authUserId, biometricSourceType, isStrongBiometric);
+        if (biometricSourceType == FINGERPRINT) {
+            mLogger.logFingerprintDetected(authUserId, isStrongBiometric);
+        } else if (biometricSourceType == FACE) {
+            mLogger.logFaceDetected(authUserId, isStrongBiometric);
+            setFaceRunningState(BIOMETRIC_STATE_STOPPED);
+        }
+
+        Trace.endSection();
+    }
+
     private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
         if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -950,8 +979,14 @@
 
     private void onFingerprintCancelNotReceived() {
         mLogger.e("Fp cancellation not received, transitioning to STOPPED");
+        final boolean wasCancellingRestarting = mFingerprintRunningState
+                == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+        if (wasCancellingRestarting) {
+            KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+        } else {
+            KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+        }
     }
 
     private void handleFingerprintError(int msgId, String errString) {
@@ -1038,6 +1073,12 @@
                     () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE),
                     getBiometricLockoutDelay());
         } else {
+            boolean temporaryLockoutReset = wasLockout && !mFingerprintLockedOut;
+            if (temporaryLockoutReset) {
+                mLogger.d("temporaryLockoutReset - stopListeningForFingerprint() to stop"
+                        + " detectFingerprint");
+                stopListeningForFingerprint();
+            }
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
 
@@ -1747,10 +1788,16 @@
                 }
             };
 
+    private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback =
+            (sensorId, userId, isStrongBiometric) -> {
+                // Trigger the fingerprint detected path so the bouncer can be shown
+                handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric);
+            };
+
     private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
             = (sensorId, userId, isStrongBiometric) -> {
-                // Trigger the face success path so the bouncer can be shown
-                handleFaceAuthenticated(userId, isStrongBiometric);
+                // Trigger the face detected path so the bouncer can be shown
+                handleBiometricDetected(userId, FACE, isStrongBiometric);
             };
 
     @VisibleForTesting
@@ -2783,8 +2830,7 @@
 
         boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
                 && shouldListenBouncerState && shouldListenUdfpsState
-                && shouldListenSideFpsState
-                && !isFingerprintLockedOut();
+                && shouldListenSideFpsState;
         logListenerModelData(
                 new KeyguardFingerprintListenModel(
                     System.currentTimeMillis(),
@@ -2843,8 +2889,7 @@
         final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
                 && mFaceSensorProperties.get(0).supportsFaceDetection
                 && canBypass && !mPrimaryBouncerIsOrWillBeShowing
-                && !isUserInLockdown(user)
-                && !isFingerprintLockedOut();
+                && !isUserInLockdown(user);
         final boolean faceAuthAllowedOrDetectionIsNeeded = faceAuthAllowed || supportsDetect;
 
         // If the face or fp has recently been authenticated do not attempt to authenticate again.
@@ -2940,11 +2985,7 @@
                 mLogger.v("startListeningForFingerprint - detect");
                 mFpm.detectFingerprint(
                         mFingerprintCancelSignal,
-                        (sensorId, user, isStrongBiometric) -> {
-                            mLogger.d("fingerprint detected");
-                            // Trigger the fingerprint success path so the bouncer can be shown
-                            handleFingerprintAuthenticated(user, isStrongBiometric);
-                        },
+                        mFingerprintDetectionCallback,
                         new FingerprintAuthenticateOptions.Builder()
                                 .setUserId(userId)
                                 .build());
@@ -2984,6 +3025,14 @@
         if (unlockPossible) {
             mFaceCancelSignal = new CancellationSignal();
 
+            final FaceAuthenticateOptions faceAuthenticateOptions =
+                    new SysUiFaceAuthenticateOptions(
+                            userId,
+                            faceAuthUiEvent,
+                            faceAuthUiEvent == FACE_AUTH_UPDATED_STARTED_WAKING_UP
+                                    ? faceAuthUiEvent.getExtraInfo()
+                                    : WAKE_REASON_UNKNOWN
+                    ).toFaceAuthenticateOptions();
             // This would need to be updated for multi-sensor devices
             final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
                     && mFaceSensorProperties.get(0).supportsFaceDetection;
@@ -2994,9 +3043,7 @@
                     // Run face detection. (If a face is detected, show the bouncer.)
                     mLogger.v("startListeningForFace - detect");
                     mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback,
-                            new FaceAuthenticateOptions.Builder()
-                                    .setUserId(userId)
-                                    .build());
+                            faceAuthenticateOptions);
                 } else {
                     // Don't run face detection. Instead, inform the user
                     // face auth is unavailable and how to proceed.
@@ -3015,7 +3062,8 @@
                 final boolean isBypassEnabled = mKeyguardBypassController != null
                         && mKeyguardBypassController.isBypassEnabled();
                 mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
-                        mFaceAuthenticationCallback, null /* handler */, userId);
+                        mFaceAuthenticationCallback, null /* handler */,
+                        faceAuthenticateOptions);
             }
             setFaceRunningState(BIOMETRIC_STATE_RUNNING);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 38f3e50..0d4889a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -214,7 +214,7 @@
     public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { }
 
     /**
-     * Called when a biometric is recognized.
+     * Called when a biometric is authenticated.
      * @param userId the user id for which the biometric sample was authenticated
      * @param biometricSourceType
      */
@@ -222,6 +222,14 @@
             boolean isStrongBiometric) { }
 
     /**
+     * Called when a biometric is detected but not successfully authenticated.
+     * @param userId the user id for which the biometric sample was detected
+     * @param biometricSourceType
+     */
+    public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) { }
+
+    /**
      * Called when biometric authentication provides help string (e.g. "Try again")
      * @param msgId
      * @param helpString
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index c414c08..e53f6ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -167,6 +167,20 @@
         }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
     }
 
+    fun logFaceDetected(userId: Int, isStrongBiometric: Boolean) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = userId
+            bool1 = isStrongBiometric
+        }, {"Face detected: userId: $int1, isStrongBiometric: $bool1"})
+    }
+
+    fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = userId
+            bool1 = isStrongBiometric
+        }, {"Fingerprint detected: userId: $int1, isStrongBiometric: $bool1"})
+    }
+
     fun logFingerprintError(msgId: Int, originalErrMsg: String) {
         logBuffer.log(TAG, DEBUG, {
             str1 = originalErrMsg
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index e698faf..08efd89 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,6 +85,8 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -98,8 +100,6 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import kotlin.Unit;
-
 /**
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
  * appropriate biometric UI (e.g. BiometricDialogView).
@@ -872,7 +872,8 @@
     }
 
     /**
-     * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}.
+     * Stores the callback received from
+     * {@link com.android.server.display.mode.DisplayModeDirector}.
      *
      * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback}
      * and registers it with this class by calling
diff --git a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
new file mode 100644
index 0000000..1390b4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+import android.content.ComponentName
+
+/** Returns true if the admin of [userId] disallows keyguard shortcuts. */
+fun DevicePolicyManager.areKeyguardShortcutsDisabled(
+    admin: ComponentName? = null,
+    userId: Int
+): Boolean {
+    val flags = getKeyguardDisabledFeatures(admin, userId)
+    return flags and KEYGUARD_DISABLE_SHORTCUTS_ALL == KEYGUARD_DISABLE_SHORTCUTS_ALL ||
+        flags and KEYGUARD_DISABLE_FEATURES_ALL == KEYGUARD_DISABLE_FEATURES_ALL
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2f44560..cbbd3e6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -64,9 +64,6 @@
     // TODO(b/259130119): Tracking Bug
     val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update")
 
-    // TODO(b/265804648): Tracking Bug
-    @JvmField val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi")
-
     // TODO(b/254512538): Tracking Bug
     val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
 
@@ -486,9 +483,9 @@
     val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
         unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
 
-    // TODO(b/255697805): Tracking Bug
+    // TODO(b/270987164): Tracking Bug
     @JvmField
-    val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false)
+    val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = true)
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index dfbe1c2..9b5f7f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -410,16 +411,10 @@
         )
     }
 
-    private suspend fun isFeatureDisabledByDevicePolicy(): Boolean {
-        val flags =
-            withContext(backgroundDispatcher) {
-                devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
-            }
-        val flagsToCheck =
-            DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or
-                DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
-        return flagsToCheck and flags != 0
-    }
+    private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
+        withContext(backgroundDispatcher) {
+            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        }
 
     companion object {
         private const val TAG = "KeyguardQuickAffordanceInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
new file mode 100644
index 0000000..a79513e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.hardware.face.FaceAuthenticateOptions
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ASSISTANT_VISIBLE
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_UDFPS_POINTER_DOWN
+import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_UNKNOWN
+import android.hardware.face.FaceAuthenticateOptions.AuthenticateReason
+import android.os.PowerManager
+import android.os.PowerManager.WAKE_REASON_UNKNOWN
+import android.util.Log
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+
+/**
+ * Wrapper for [FaceAuthenticateOptions] to convert SystemUI values to their corresponding value in
+ * [FaceAuthenticateOptions].
+ */
+data class SysUiFaceAuthenticateOptions(
+        val userId: Int,
+        private val faceAuthUiEvent: UiEventLogger.UiEventEnum,
+        @PowerManager.WakeReason val wakeReason: Int = WAKE_REASON_UNKNOWN
+) {
+    val authenticateReason = setAuthenticateReason(faceAuthUiEvent)
+
+    /**
+     * The [FaceAuthUiEvent] for this operation. This method converts the UiEvent to the framework
+     * [AuthenticateReason].
+     */
+    @AuthenticateReason
+    fun setAuthenticateReason(uiEvent: UiEventLogger.UiEventEnum): Int {
+        return when (uiEvent) {
+            FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP -> {
+                AUTHENTICATE_REASON_STARTED_WAKING_UP
+            }
+            FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+            FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN -> {
+                AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN
+            }
+            FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED -> {
+                AUTHENTICATE_REASON_ASSISTANT_VISIBLE
+            }
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN -> {
+                AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+            }
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED -> {
+                AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED
+            }
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED -> {
+                AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED
+            }
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED -> {
+                AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED
+            }
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER -> {
+                AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER
+            }
+            FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN -> {
+                AUTHENTICATE_REASON_UDFPS_POINTER_DOWN
+            }
+            else -> {
+                Log.e("FaceAuthenticateOptions", " unmapped FaceAuthUiEvent $uiEvent")
+                AUTHENTICATE_REASON_UNKNOWN
+            }
+        }
+    }
+
+    /** Builds the instance. */
+    fun toFaceAuthenticateOptions(): FaceAuthenticateOptions {
+        return FaceAuthenticateOptions.Builder()
+            .setUserId(userId)
+            .setAuthenticateReason(authenticateReason)
+            .setWakeReason(wakeReason)
+            .build()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index f7b7db4..c65f0aa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.app.admin.DevicePolicyManager
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.content.Context
@@ -27,7 +28,9 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
@@ -54,6 +57,8 @@
     private val optionalUserManager: Optional<UserManager>,
     private val optionalKeyguardManager: Optional<KeyguardManager>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val userTracker: UserTracker,
 ) {
 
     @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
@@ -107,11 +112,23 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
+        val isKeyguardLocked = keyguardManager.isKeyguardLocked
+        // KeyguardQuickAffordanceInteractor blocks the quick affordance from showing in the
+        // keyguard if it is not allowed by the admin policy. Here we block any other way to show
+        // note task when the screen is locked.
+        if (
+            isKeyguardLocked &&
+                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        ) {
+            logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
+            return
+        }
+
         val info =
             resolver.resolveInfo(
                 entryPoint = entryPoint,
                 isInMultiWindowMode = isInMultiWindowMode,
-                isKeyguardLocked = keyguardManager.isKeyguardLocked,
+                isKeyguardLocked = isKeyguardLocked,
             )
                 ?: return
 
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index b36f0d7..10e2afe 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -27,6 +27,7 @@
 import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Xfermode;
 import android.graphics.drawable.Drawable;
 import android.view.animation.DecelerateInterpolator;
@@ -41,7 +42,11 @@
 public class ScrimDrawable extends Drawable {
     private static final String TAG = "ScrimDrawable";
 
+    private boolean mShouldUseLargeScreenSize;
     private final Paint mPaint;
+    private final Path mPath = new Path();
+    private final RectF mBoundsRectF = new RectF();
+
     private int mAlpha = 255;
     private int mMainColor;
     private ValueAnimator mColorAnimation;
@@ -49,11 +54,13 @@
     private float mCornerRadius;
     private ConcaveInfo mConcaveInfo;
     private int mBottomEdgePosition;
+    private float mBottomEdgeRadius = -1;
     private boolean mCornerRadiusEnabled;
 
     public ScrimDrawable() {
         mPaint = new Paint();
         mPaint.setStyle(Paint.Style.FILL);
+        mShouldUseLargeScreenSize = false;
     }
 
     /**
@@ -133,6 +140,10 @@
         return PixelFormat.TRANSLUCENT;
     }
 
+    public void setShouldUseLargeScreenSize(boolean v) {
+        mShouldUseLargeScreenSize = v;
+    }
+
     /**
      * Corner radius used by either concave or convex corners.
      */
@@ -191,6 +202,10 @@
         invalidateSelf();
     }
 
+    public void setBottomEdgeRadius(float radius) {
+        mBottomEdgeRadius = radius;
+    }
+
     @Override
     public void draw(@NonNull Canvas canvas) {
         mPaint.setColor(mMainColor);
@@ -198,9 +213,46 @@
         if (mConcaveInfo != null) {
             drawConcave(canvas);
         } else if (mCornerRadiusEnabled && mCornerRadius > 0) {
-            canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
-                    getBounds().bottom,
-                    /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
+            float topEdgeRadius = mCornerRadius;
+            float bottomEdgeRadius = mBottomEdgeRadius == -1.0 ? mCornerRadius : mBottomEdgeRadius;
+
+            mBoundsRectF.set(getBounds());
+
+            // When the back gesture causes the notification scrim to be scaled down,
+            // this offset "reveals" the rounded bottom edge as it "pulls away".
+            // We must *not* make this adjustment on largescreen shades (where the corner is sharp).
+            if (!mShouldUseLargeScreenSize && mBottomEdgeRadius != -1) {
+                mBoundsRectF.bottom -= bottomEdgeRadius;
+            }
+
+            // We need a box with rounded corners but its lower corners are not rounded on large
+            // screen devices in "portrait" orientation.
+            // Thus, we cannot draw a symmetric rounded rectangle via canvas.drawRoundRect()
+            // and must build a box with different corner radii at the top and at the bottom.
+            // Additionally, when the scrim is pushed to the very bottom of the screen, do not draw
+            // anything (drawing a rounded box with these specifications is not possible).
+            // TODO(b/271030611) perhaps this could be accomplished via Path.addRoundRect instead?
+            if (mBoundsRectF.bottom - mBoundsRectF.top > bottomEdgeRadius) {
+                mPath.reset();
+                mPath.moveTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius);
+                mPath.cubicTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius,
+                        mBoundsRectF.right, mBoundsRectF.top,
+                        mBoundsRectF.right - topEdgeRadius, mBoundsRectF.top);
+                mPath.lineTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top);
+                mPath.cubicTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top,
+                        mBoundsRectF.left, mBoundsRectF.top,
+                        mBoundsRectF.left, mBoundsRectF.top + topEdgeRadius);
+                mPath.lineTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius);
+                mPath.cubicTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius,
+                        mBoundsRectF.left, mBoundsRectF.bottom,
+                        mBoundsRectF.left + bottomEdgeRadius, mBoundsRectF.bottom);
+                mPath.lineTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom);
+                mPath.cubicTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom,
+                        mBoundsRectF.right, mBoundsRectF.bottom,
+                        mBoundsRectF.right, mBoundsRectF.bottom - bottomEdgeRadius);
+                mPath.close();
+                canvas.drawPath(mPath, mPaint);
+            }
         } else {
             canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
                     getBounds().bottom, mPaint);
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index f68e042..fc89a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
@@ -37,6 +38,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.concurrent.Executor;
 
@@ -102,6 +104,13 @@
     @Override
     protected void onDraw(Canvas canvas) {
         if (mDrawable.getAlpha() > 0) {
+            Resources res = getResources();
+            // Scrim behind notification shade has sharp (not rounded) corners on large screens
+            // which scrim itself cannot know, so we set it here.
+            if (mDrawable instanceof ScrimDrawable) {
+                ((ScrimDrawable) mDrawable).setShouldUseLargeScreenSize(
+                        LargeScreenUtils.shouldUseLargeScreenShadeHeader(res));
+            }
             mDrawable.draw(canvas);
         }
     }
@@ -170,6 +179,15 @@
         });
     }
 
+    /**
+     * Set corner radius of the bottom edge of the Notification scrim.
+     */
+    public void setBottomEdgeRadius(float radius) {
+        if (mDrawable instanceof ScrimDrawable) {
+            ((ScrimDrawable) mDrawable).setBottomEdgeRadius(radius);
+        }
+    }
+
     @VisibleForTesting
     Drawable getDrawable() {
         return mDrawable;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95206e5..e75320a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -293,7 +293,19 @@
      * custom clock animation is in use.
      */
     private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
+    /**
+     * Whether the Shade should animate to reflect Back gesture progress.
+     * To minimize latency at runtime, we cache this, else we'd be reading it every time
+     * updateQsExpansion() is called... and it's called very often.
+     *
+     * Whenever we change this flag, SysUI is restarted, so it's never going to be "stale".
+     */
 
+    public final boolean mAnimateBack;
+    /**
+     * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
+     */
+    public static final float SHADE_BACK_ANIM_MIN_SCALE = 0.9f;
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final Resources mResources;
     private final KeyguardStateController mKeyguardStateController;
@@ -361,6 +373,8 @@
     private CentralSurfaces mCentralSurfaces;
     private HeadsUpManagerPhone mHeadsUpManager;
     private float mExpandedHeight = 0;
+    /** The current squish amount for the predictive back animation */
+    private float mCurrentBackProgress = 0.0f;
     private boolean mTracking;
     private boolean mHintAnimationRunning;
     private KeyguardBottomAreaView mKeyguardBottomArea;
@@ -815,6 +829,7 @@
         mShadeHeaderController = shadeHeaderController;
         mLayoutInflater = layoutInflater;
         mFeatureFlags = featureFlags;
+        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -1951,6 +1966,14 @@
             if (mFixedDuration != NO_FIXED_DURATION) {
                 animator.setDuration(mFixedDuration);
             }
+
+            // Reset Predictive Back animation's transform after Shade is completely hidden.
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    resetBackTransformation();
+                }
+            });
         }
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
@@ -2175,6 +2198,53 @@
         }
     }
 
+    /**
+     * When the back gesture triggers a fully-expanded shade --> QQS shade collapse transition,
+     * the expansionFraction goes down from 1.0 --> 0.0 (collapsing), so the current "squish" amount
+     * (mCurrentBackProgress) must be un-applied from various UI elements in tandem, such that,
+     * as the shade ends up in its half-expanded state (with QQS above), it is back at 100% scale.
+     * Without this, the shade would collapse, and stay squished.
+     */
+    public void adjustBackAnimationScale(float expansionFraction) {
+        if (expansionFraction > 0.0f) { // collapsing
+            float animatedFraction = expansionFraction * mCurrentBackProgress;
+            applyBackScaling(animatedFraction);
+        } else {
+            // collapsed! reset, so that if we re-expand shade, it won't start off "squished"
+            mCurrentBackProgress = 0;
+        }
+    }
+
+    //TODO(b/270981268): allow cancelling back animation mid-flight
+    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+    public void onBackPressed() {
+        closeQsIfPossible();
+    }
+    /** Sets back progress. */
+    public void onBackProgressed(float progressFraction) {
+        // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out)
+        mCurrentBackProgress = progressFraction;
+        applyBackScaling(progressFraction);
+    }
+
+    /** Resets back progress. */
+    public void resetBackTransformation() {
+        mCurrentBackProgress = 0.0f;
+        applyBackScaling(0.0f);
+    }
+
+    /** Scales multiple elements in tandem to achieve the illusion of the QS+Shade shrinking
+     *  as a single visual element (used by the Predictive Back Gesture preview animation).
+     *  fraction = 0 implies "no scaling", and 1 means "scale down to minimum size (90%)".
+     */
+    public void applyBackScaling(float fraction) {
+        if (mNotificationContainerParent == null) {
+            return;
+        }
+        float scale = MathUtils.lerp(1.0f, SHADE_BACK_ANIM_MIN_SCALE, fraction);
+        mNotificationContainerParent.applyBackScaling(scale, mSplitShadeEnabled);
+        mScrimController.applyBackScaling(scale);
+    }
     /** */
     public float getLockscreenShadeDragProgress() {
         // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
@@ -4851,6 +4921,11 @@
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
+                    if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack) {
+                        // Cache the gesture insets now, so we can quickly query them during
+                        // ACTION_MOVE and decide whether to intercept events for back gesture anim.
+                        mQsController.updateGestureInsetsCache();
+                    }
                     mShadeLog.logMotionEvent(event, "onTouch: down action");
                     startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                     mMinExpandHeight = 0.0f;
@@ -4900,6 +4975,12 @@
                     }
                     break;
                 case MotionEvent.ACTION_MOVE:
+                    // If the shade is half-collapsed, a horizontal swipe inwards from L/R edge
+                    // must be routed to the back gesture (which shows a preview animation).
+                    if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack
+                            && mQsController.shouldBackBypassQuickSettings(x)) {
+                        return false;
+                    }
                     if (isFullyCollapsed()) {
                         // If panel is fully collapsed, reset haptic effect before adding movement.
                         mHasVibratedOnOpen = false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index f73dde6..7dff6ea 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.WindowInsets;
@@ -55,6 +56,13 @@
     private QS mQs;
     private View mQSContainer;
 
+    /**
+     *  These are used to compute the bounding box containing the shade and the notification scrim,
+     *  which is then used to drive the Back gesture animation.
+     */
+    private final Rect mUpperRect = new Rect();
+    private final Rect mBoundingBoxRect = new Rect();
+
     @Nullable
     private Consumer<Configuration> mConfigurationChangedListener;
 
@@ -172,4 +180,37 @@
     public void applyConstraints(ConstraintSet constraintSet) {
         constraintSet.applyTo(this);
     }
+
+    /**
+     *  Scale multiple elements in tandem, for the predictive back animation.
+     *  This is how the Shade responds to the Back gesture (by scaling).
+     *  Without the common center, individual elements will scale about their respective centers.
+     *  Scaling the entire NotificationsQuickSettingsContainer will also resize the shade header
+     *  (which we don't want).
+     */
+    public void applyBackScaling(float scale, boolean usingSplitShade) {
+        if (mStackScroller == null || mQSContainer == null) {
+            return;
+        }
+
+        mQSContainer.getBoundsOnScreen(mUpperRect);
+        mStackScroller.getBoundsOnScreen(mBoundingBoxRect);
+        mBoundingBoxRect.union(mUpperRect);
+
+        float cx = mBoundingBoxRect.centerX();
+        float cy = mBoundingBoxRect.centerY();
+
+        mQSContainer.setPivotX(cx);
+        mQSContainer.setPivotY(cy);
+        mQSContainer.setScaleX(scale);
+        mQSContainer.setScaleY(scale);
+
+        // When in large-screen split-shade mode, the notification stack scroller scales correctly
+        // only if the pivot point is at the left edge of the screen (because of its dimensions).
+        // When not in large-screen split-shade mode, we can scale correctly via the (cx,cy) above.
+        mStackScroller.setPivotX(usingSplitShade ? 0.0f : cx);
+        mStackScroller.setPivotY(cy);
+        mStackScroller.setScaleX(scale);
+        mStackScroller.setScaleY(scale);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 099ad94..6857f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -31,6 +31,7 @@
 import android.animation.ValueAnimator;
 import android.app.Fragment;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.util.Log;
@@ -40,6 +41,9 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
@@ -63,6 +67,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -83,10 +88,10 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.LargeScreenUtils;
 
-import javax.inject.Inject;
-
 import dagger.Lazy;
 
+import javax.inject.Inject;
+
 /** Handles QuickSettings touch handling, expansion and animation state
  * TODO (b/264460656) make this dumpable
  */
@@ -223,6 +228,13 @@
     private boolean mAnimatorExpand;
 
     /**
+     * The gesture inset currently in effect -- used to decide whether a back gesture should
+     * receive a horizontal swipe inwards from the left/right vertical edge of the screen.
+     * We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
+     */
+    private Insets mCachedGestureInsets;
+
+    /**
      * The amount of progress we are currently in if we're transitioning to the full shade.
      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
      * shade. This value can also go beyond 1.1 when we're overshooting!
@@ -406,6 +418,7 @@
         mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
 
         mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
+        updateGestureInsetsCache();
     }
 
     // TODO (b/265054088): move this and others to a CoreStartable
@@ -469,6 +482,26 @@
                 || touchX > mQsFrame.getX() + mQsFrame.getWidth();
     }
 
+    /**
+     *  Computes (and caches) the gesture insets for the current window. Intended to be called
+     *  on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
+     */
+    public void updateGestureInsetsCache() {
+        WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
+        WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
+        mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
+                WindowInsets.Type.systemGestures());
+    }
+
+    /**
+     *  Returns whether x coordinate lies in the vertical edges of the screen
+     *  (the only place where a back gesture can be initiated).
+     */
+    public boolean shouldBackBypassQuickSettings(float touchX) {
+        return (touchX < mCachedGestureInsets.left)
+                || (touchX > mKeyguardStatusBar.getWidth() - mCachedGestureInsets.right);
+    }
+
     /** Returns whether touch is within QS area */
     private boolean isTouchInQsArea(float x, float y) {
         if (isSplitShadeAndTouchXOutsideQs(x)) {
@@ -926,6 +959,10 @@
                 getHeaderTranslation(),
                 squishiness
         );
+        if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE
+                && mPanelViewControllerLazy.get().mAnimateBack) {
+            mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction);
+        }
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
         int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -1113,6 +1150,7 @@
             float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
+            mScrimController.setNotificationBottomRadius(radius);
         }
         if (isQsFragmentCreated()) {
             float qsTranslation = 0;
@@ -1505,18 +1543,31 @@
     }
 
     private void handleDown(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
-                && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
-            mFalsingCollector.onQsDown();
-            mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
-            mTracking = true;
-            onExpansionStarted();
-            mInitialHeightOnTouch = mExpansionHeight;
-            mInitialTouchY = event.getY();
-            mInitialTouchX = event.getX();
-            // TODO (b/265193930): remove dependency on NPVC
-            // If we interrupt an expansion gesture here, make sure to update the state correctly.
-            mPanelViewControllerLazy.get().notifyExpandingFinished();
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            // When the shade is fully-expanded, an inward swipe from the L/R edge should first
+            // allow the back gesture's animation to preview the shade animation (if enabled).
+            // (swipes starting closer to the center of the screen will not be affected)
+            if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE
+                    && mPanelViewControllerLazy.get().mAnimateBack) {
+                updateGestureInsetsCache();
+                if (shouldBackBypassQuickSettings(event.getX())) {
+                    return;
+                }
+            }
+            if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
+                mFalsingCollector.onQsDown();
+                mShadeLog.logMotionEvent(event,
+                        "handleQsDown: down action, QS tracking enabled");
+                mTracking = true;
+                onExpansionStarted();
+                mInitialHeightOnTouch = mExpansionHeight;
+                mInitialTouchY = event.getY();
+                mInitialTouchX = event.getX();
+                // TODO (b/265193930): remove dependency on NPVC
+                // If we interrupt an expansion gesture here, make sure to update the state
+                // correctly.
+                mPanelViewControllerLazy.get().notifyExpandingFinished();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index fc89be2..00d8c42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -28,10 +28,6 @@
     val featureFlags: FeatureFlags,
     val sysPropFlags: FlagResolver,
 ) {
-    init {
-        featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() }
-    }
-
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
 
@@ -40,8 +36,6 @@
 
     fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
 
-    fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI)
-
     fun forceDemoteFsi(): Boolean =
             sysPropFlags.isEnabled(NotificationFlags.FSI_FORCE_DEMOTE)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index ae19feb..9001470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -35,10 +35,6 @@
          */
         NO_FSI_SHOW_STICKY_HUN(false),
         /**
-         * Full screen intents are disabled.
-         */
-        NO_FSI_DISABLED(false),
-        /**
          * No full screen intent included, so there is nothing to show.
          */
         NO_FULL_SCREEN_INTENT(false),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 0163dbe..9f45b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -244,10 +244,6 @@
 
     @Override
     public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
-        if (mFlags.disableFsi()) {
-            return FullScreenIntentDecision.NO_FSI_DISABLED;
-        }
-
         if (entry.getSbn().getNotification().fullScreenIntent == null) {
             if (entry.isStickyAndNotDemoted()) {
                 return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN;
@@ -343,9 +339,6 @@
             case NO_FSI_SHOW_STICKY_HUN:
                 mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN");
                 return;
-            case NO_FSI_DISABLED:
-                mLogger.logNoFullscreen(entry, "Disabled");
-                return;
             case NO_FULL_SCREEN_INTENT:
                 return;
             case NO_FSI_SUPPRESSED_BY_DND:
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 9f38361..7855cdf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -374,6 +374,17 @@
     }
 
     @Override
+    public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
+        Trace.beginSection("BiometricUnlockController#onBiometricDetected");
+        if (mUpdateMonitor.isGoingToSleep()) {
+            Trace.endSection();
+            return;
+        }
+        startWakeAndUnlock(MODE_SHOW_BOUNCER);
+    }
+
+    @Override
     public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
             boolean isStrongBiometric) {
         Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index ae97407..b166446 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -104,6 +104,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.DateTimeView;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
@@ -508,6 +510,7 @@
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSliderController.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
+    private final boolean mAnimateBack;
     private final FragmentService mFragmentService;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final WallpaperController mWallpaperController;
@@ -654,6 +657,7 @@
 
     private final InteractionJankMonitor mJankMonitor;
 
+    /** Existing callback that handles back gesture invoked for the Shade. */
     private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
         if (DEBUG) {
             Log.d(TAG, "mOnBackInvokedCallback() called");
@@ -661,6 +665,33 @@
         onBackPressed();
     };
 
+    private boolean shouldBackBeHandled() {
+        return (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
+                && !isBouncerShowingOverDream());
+    }
+
+    /**
+     *  New callback that handles back gesture invoked, cancel, progress
+     *  and provides feedback via Shade animation.
+     *  (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag)
+     */
+    private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
+        @Override
+        public void onBackInvoked() {
+            onBackPressed();
+        }
+
+        @Override
+        public void onBackProgressed(BackEvent event) {
+            if (shouldBackBeHandled()) {
+                if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+                    float fraction = event.getProgress();
+                    mNotificationPanelViewController.onBackProgressed(fraction);
+                }
+            }
+        }
+    };
+
     /**
      * Public constructor for CentralSurfaces.
      *
@@ -882,6 +913,8 @@
         if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
             mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
         }
+        // Based on teamfood flag, enable predictive back animation for the Shade.
+        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
     }
 
     private void initBubbles(Bubbles bubbles) {
@@ -2706,7 +2739,8 @@
                 if (viewRootImpl != null) {
                     viewRootImpl.getOnBackInvokedDispatcher()
                             .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                                    mOnBackInvokedCallback);
+                                    mAnimateBack ? mOnBackAnimationCallback
+                                            : mOnBackInvokedCallback);
                     mIsBackCallbackRegistered = true;
                     if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered");
                 }
@@ -2721,7 +2755,9 @@
                 ViewRootImpl viewRootImpl = getViewRootImpl();
                 if (viewRootImpl != null) {
                     viewRootImpl.getOnBackInvokedDispatcher()
-                            .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+                            .unregisterOnBackInvokedCallback(
+                                    mAnimateBack ? mOnBackAnimationCallback
+                                            : mOnBackInvokedCallback);
                     mIsBackCallbackRegistered = false;
                     if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered");
                 }
@@ -3253,9 +3289,10 @@
         if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
             return true;
         }
-        if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
-                && !isBouncerShowingOverDream()) {
+        if (shouldBackBeHandled()) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+                // this is the Shade dismiss animation, so make sure QQS closes when it ends.
+                mNotificationPanelViewController.onBackPressed();
                 mShadeController.animateCollapseShade();
             }
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8e0ec284..fb8bf52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -345,9 +345,16 @@
         }
     }
 
-    /**
-     * Sets corner radius of scrims.
-     */
+    // TODO(b/270984686) recompute scrim height accurately, based on shade contents.
+    /** Set corner radius of the bottom edge of the Notification scrim. */
+    public void setNotificationBottomRadius(float radius) {
+        if (mNotificationsScrim == null) {
+            return;
+        }
+        mNotificationsScrim.setBottomEdgeRadius(radius);
+    }
+
+    /** Sets corner radius of scrims. */
     public void setScrimCornerRadius(int radius) {
         if (mScrimBehind == null || mNotificationsScrim == null) {
             return;
@@ -511,6 +518,12 @@
         scheduleUpdate();
     }
 
+    /** This is used by the predictive back gesture animation to scale the Shade. */
+    public void applyBackScaling(float scale) {
+        mNotificationsScrim.setScaleX(scale);
+        mNotificationsScrim.setScaleY(scale);
+    }
+
     public void onTrackingStarted() {
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
         if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 462504e..4e27ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -82,6 +82,8 @@
     fun startListener() {
         handler.post {
             if (hasStarted) return@post
+            logDebug { "Listener has started." }
+
             hasStarted = true
             isInUsiSession =
                 inputManager.hasInputDevice {
@@ -116,6 +118,10 @@
 
         val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
         if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+        logDebug {
+            "Stylus InputDevice added: $deviceId ${device.name}, " +
+                "External: ${device.isExternal}"
+        }
 
         if (!device.isExternal) {
             registerBatteryListener(deviceId)
@@ -137,6 +143,7 @@
 
         val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
         if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+        logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" }
 
         val currAddress: String? = device.bluetoothAddress
         val prevAddress: String? = inputDeviceAddressMap[deviceId]
@@ -157,6 +164,8 @@
         if (!hasStarted) return
 
         if (!inputDeviceAddressMap.contains(deviceId)) return
+        logDebug { "Stylus InputDevice removed: $deviceId" }
+
         unregisterBatteryListener(deviceId)
 
         val btAddress: String? = inputDeviceAddressMap[deviceId]
@@ -180,6 +189,11 @@
 
             val isCharging = String(value) == "true"
 
+            logDebug {
+                "Charging state metadata changed for device $inputDeviceId " +
+                    "${device.address}: $isCharging"
+            }
+
             executeStylusBatteryCallbacks { cb ->
                 cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging)
             }
@@ -194,13 +208,10 @@
         handler.post {
             if (!hasStarted) return@post
 
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "onBatteryStateChanged for $deviceId. " +
-                        "batteryState present: ${batteryState.isPresent}, " +
-                        "capacity: ${batteryState.capacity}"
-                )
+            logDebug {
+                "Battery state changed for $deviceId. " +
+                    "batteryState present: ${batteryState.isPresent}, " +
+                    "capacity: ${batteryState.capacity}"
             }
 
             val batteryStateValid = isBatteryStateValid(batteryState)
@@ -216,7 +227,7 @@
     }
 
     private fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
-        trackAndLogBluetoothSession(deviceId, true)
+        trackAndLogBluetoothSession(deviceId, btAddress, true)
         val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
         try {
             bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
@@ -226,7 +237,7 @@
     }
 
     private fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {
-        trackAndLogBluetoothSession(deviceId, false)
+        trackAndLogBluetoothSession(deviceId, btAddress, false)
         val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
         try {
             bluetoothAdapter.removeOnMetadataChangedListener(device, this)
@@ -245,6 +256,7 @@
         if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
         if (InputSettings.isStylusEverUsed(context)) return
 
+        logDebug { "Stylus used for the first time." }
         InputSettings.setStylusEverUsed(context, true)
         executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
     }
@@ -259,12 +271,7 @@
         // TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus
         //  is sent after the actual valid callback
         if (batteryStateValid && usiSessionId == null) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "USI battery newly present, entering new USI session. Device ID: $deviceId"
-                )
-            }
+            logDebug { "USI battery newly present, entering new USI session: $deviceId" }
             usiSessionId = instanceIdSequence.newInstanceId()
             uiEventLogger.logWithInstanceId(
                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
@@ -273,9 +280,7 @@
                 usiSessionId
             )
         } else if (!batteryStateValid && usiSessionId != null) {
-            if (DEBUG) {
-                Log.d(TAG, "USI battery newly absent, exiting USI session Device ID: $deviceId")
-            }
+            logDebug { "USI battery newly absent, exiting USI session: $deviceId" }
             uiEventLogger.logWithInstanceId(
                 StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                 0,
@@ -286,8 +291,17 @@
         }
     }
 
-    private fun trackAndLogBluetoothSession(deviceId: Int, bluetoothConnected: Boolean) {
-        if (bluetoothConnected) {
+    private fun trackAndLogBluetoothSession(
+        deviceId: Int,
+        btAddress: String,
+        btConnected: Boolean
+    ) {
+        logDebug {
+            "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" +
+                " $deviceId $btAddress"
+        }
+
+        if (btConnected) {
             inputDeviceBtSessionIdMap[deviceId] = instanceIdSequence.newInstanceId()
             uiEventLogger.logWithInstanceId(
                 StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED,
@@ -383,7 +397,13 @@
     }
 
     companion object {
-        private val TAG = StylusManager::class.simpleName.orEmpty()
-        private val DEBUG = false
+        val TAG = StylusManager::class.simpleName.orEmpty()
+        const val DEBUG = false
+    }
+}
+
+private inline fun logDebug(message: () -> String) {
+    if (StylusManager.DEBUG) {
+        Log.d(StylusManager.TAG, message())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 09b738f..f510768 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,9 +18,10 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN;
+import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -30,7 +31,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
-import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -82,6 +82,7 @@
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -633,20 +634,48 @@
 
     @Test
     public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
-        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
-        // will trigger updateBiometricListeningState();
-        clearInvocations(mFingerprintManager);
-        mKeyguardUpdateMonitor.resetBiometricListeningState();
-
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
-        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
-        mTestableLooper.processAllMessages();
+        givenDetectFingerprintWithClearingFingerprintManagerInvocations();
 
         verifyFingerprintAuthenticateNeverCalled();
         verifyFingerprintDetectCall();
     }
 
     @Test
+    public void whenDetectFingerprint_biometricDetectCallback() {
+        ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
+                ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
+
+        givenDetectFingerprintWithClearingFingerprintManagerInvocations();
+        verify(mFingerprintManager).detectFingerprint(
+                any(), fpDetectCallbackCaptor.capture(), any());
+        fpDetectCallbackCaptor.getValue().onFingerprintDetected(0, 0, true);
+
+        // THEN verify keyguardUpdateMonitorCallback receives a detect callback
+        // and NO authenticate callbacks
+        verify(mTestCallback).onBiometricDetected(
+                eq(0), eq(BiometricSourceType.FINGERPRINT), eq(true));
+        verify(mTestCallback, never()).onBiometricAuthenticated(
+                anyInt(), any(), anyBoolean());
+    }
+
+    @Test
+    public void whenDetectFace_biometricDetectCallback() {
+        ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor =
+                ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class);
+
+        givenDetectFace();
+        verify(mFaceManager).detectFace(any(), faceDetectCallbackCaptor.capture(), any());
+        faceDetectCallbackCaptor.getValue().onFaceDetected(0, 0, false);
+
+        // THEN verify keyguardUpdateMonitorCallback receives a detect callback
+        // and NO authenticate callbacks
+        verify(mTestCallback).onBiometricDetected(
+                eq(0), eq(BiometricSourceType.FACE), eq(false));
+        verify(mTestCallback, never()).onBiometricAuthenticated(
+                anyInt(), any(), anyBoolean());
+    }
+
+    @Test
     public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
         // GIVEN unlocking with biometric is allowed
         strongAuthNotRequired();
@@ -674,7 +703,7 @@
         strongAuthNotRequired();
 
         // WHEN fingerprint is locked out
-        fingerprintErrorLockedOut();
+        fingerprintErrorTemporaryLockedOut();
 
         // THEN unlocking with face is not allowed
         Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -697,7 +726,7 @@
         strongAuthNotRequired();
 
         // WHEN fingerprint is locked out
-        fingerprintErrorLockedOut();
+        fingerprintErrorTemporaryLockedOut();
 
         // THEN unlocking with fingerprint is not allowed
         Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -721,7 +750,7 @@
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
 
         // WHEN fingerprint is locked out
-        fingerprintErrorLockedOut();
+        fingerprintErrorTemporaryLockedOut();
 
         // THEN user is NOT considered as "having trust" and bouncer cannot be skipped
         Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
@@ -781,11 +810,6 @@
 
     @Test
     public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
-        // GIVEN mocked keyguardUpdateMonitorCallback
-        KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback =
-                mock(KeyguardUpdateMonitorCallback.class);
-        mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
-
         // GIVEN bypass is enabled, face detection is supported
         lockscreenBypassIsAllowed();
         supportsFaceDetection();
@@ -804,22 +828,13 @@
         verifyFaceAuthenticateNeverCalled();
 
         // THEN biometric help message sent to callback
-        verify(keyguardUpdateMonitorCallback).onBiometricHelp(
+        verify(mTestCallback).onBiometricHelp(
                 eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
     }
 
     @Test
     public void faceDetect_whenStrongAuthRequiredAndBypass() {
-        // GIVEN bypass is enabled, face detection is supported and strong auth is required
-        lockscreenBypassIsAllowed();
-        supportsFaceDetection();
-        strongAuthRequiredEncrypted();
-        keyguardIsVisible();
-        // fingerprint is NOT running, UDFPS is NOT supported
-
-        // WHEN the device wakes up
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
+        givenDetectFace();
 
         // FACE detect is triggered, not authenticate
         verifyFaceDetectCall();
@@ -836,39 +851,6 @@
     }
 
     @Test
-    public void noFaceRun_whenFpLockout() {
-        // GIVEN bypass is enabled, face detection is supported and strong auth is required
-        lockscreenBypassIsAllowed();
-        supportsFaceDetection();
-        strongAuthRequiredEncrypted();
-        keyguardIsVisible();
-        // fingerprint is NOT running, UDFPS is NOT supported
-
-        // GIVEN fp is locked out
-        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), anyInt()))
-                .thenReturn(BIOMETRIC_LOCKOUT_TIMED);
-        mKeyguardUpdateMonitor.handleUserSwitchComplete(0);
-        assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(true);
-
-        // WHEN the device wakes up
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-
-        // FACE detect is NOT triggered and face authenticate is NOT triggered
-        verifyFaceDetectNeverCalled();
-        verifyFaceAuthenticateNeverCalled();
-
-        // WHEN bouncer becomes visible
-        setKeyguardBouncerVisibility(true);
-        clearInvocations(mFaceManager);
-
-        // THEN face scanning is not run
-        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
-        verifyFaceAuthenticateNeverCalled();
-        verifyFaceDetectNeverCalled();
-    }
-
-    @Test
     public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
         // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
         lockscreenBypassIsAllowed();
@@ -1186,8 +1168,7 @@
         // Fingerprint should be cancelled on lockout if going to lockout state, else
         // restarted if it's not
         assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
-                .isEqualTo(fpLocked
-                        ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING);
+                .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
     }
 
     @Test
@@ -1646,7 +1627,7 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
         // Fingerprint is locked out.
-        fingerprintErrorLockedOut();
+        fingerprintErrorTemporaryLockedOut();
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
@@ -2499,7 +2480,69 @@
                 eq(false));
     }
 
+    @Test
+    public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() {
+        ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor =
+                ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class);
+        verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture());
+
+        // GIVEN device is locked out
+        fingerprintErrorTemporaryLockedOut();
+
+        // GIVEN FP detection is running
+        givenDetectFingerprintWithClearingFingerprintManagerInvocations();
+        verifyFingerprintDetectCall();
+        verifyFingerprintAuthenticateNeverCalled();
+
+        // WHEN temporary lockout resets
+        fpLockoutResetCallbackCaptor.getValue().onLockoutReset(0);
+        mTestableLooper.processAllMessages();
+
+        // THEN fingerprint detect state should cancel & then restart (for authenticate call)
+        assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+                .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
+    }
+
+    @Test
+    public void faceAuthenticateOptions_bouncerAuthenticateReason() {
+        // GIVEN the bouncer is fully visible
+        bouncerFullyVisible();
+
+        // WHEN authenticate is called
+        ArgumentCaptor<FaceAuthenticateOptions> captor =
+                ArgumentCaptor.forClass(FaceAuthenticateOptions.class);
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture());
+
+        // THEN the authenticate reason is attributed to the bouncer
+        assertThat(captor.getValue().getAuthenticateReason())
+                .isEqualTo(AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN);
+    }
+
+    @Test
+    public void faceAuthenticateOptions_wakingUpAuthenticateReason_powerButtonWakeReason() {
+        // GIVEN keyguard is visible
+        keyguardIsVisible();
+
+        // WHEN device wakes up from the power button
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+
+        // THEN face auth is triggered
+        ArgumentCaptor<FaceAuthenticateOptions> captor =
+                ArgumentCaptor.forClass(FaceAuthenticateOptions.class);
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture());
+
+        // THEN the authenticate reason is attributed to the waking
+        assertThat(captor.getValue().getAuthenticateReason())
+                .isEqualTo(AUTHENTICATE_REASON_STARTED_WAKING_UP);
+
+        // THEN the wake reason is attributed to the power button
+        assertThat(captor.getValue().getWakeReason())
+                .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
+        verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyInt(), anyInt());
     }
@@ -2518,11 +2561,12 @@
     }
 
     private void verifyFaceAuthenticateNeverCalled() {
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
     }
 
     private void verifyFaceAuthenticateCall() {
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), any());
     }
 
     private void verifyFaceDetectNeverCalled() {
@@ -2602,7 +2646,7 @@
         mKeyguardUpdateMonitor.setSwitchingUser(true);
     }
 
-    private void fingerprintErrorLockedOut() {
+    private void fingerprintErrorTemporaryLockedOut() {
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
     }
@@ -2630,7 +2674,7 @@
                 any(),
                 mAuthenticationCallbackCaptor.capture(),
                 any(),
-                anyInt());
+                any());
         mAuthenticationCallbackCaptor.getValue()
                 .onAuthenticationSucceeded(
                         new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false));
@@ -2741,6 +2785,30 @@
         receiver.setPendingResult(pendingResult);
     }
 
+    private void givenDetectFingerprintWithClearingFingerprintManagerInvocations() {
+        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+        // will trigger updateBiometricListeningState();
+        clearInvocations(mFingerprintManager);
+        mKeyguardUpdateMonitor.resetBiometricListeningState();
+
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+        mTestableLooper.processAllMessages();
+    }
+
+    private void givenDetectFace() {
+        // GIVEN bypass is enabled, face detection is supported and strong auth is required
+        lockscreenBypassIsAllowed();
+        supportsFaceDetection();
+        strongAuthRequiredEncrypted();
+        keyguardIsVisible();
+        // fingerprint is NOT running, UDFPS is NOT supported
+
+        // WHEN the device wakes up
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+    }
+
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
new file mode 100644
index 0000000..8203291
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class DevicePolicyManagerExtTest : SysuiTestCase() {
+
+    @Mock lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(userTracker.userId).thenReturn(CURRENT_USER_ID)
+    }
+
+    // region areKeyguardShortcutsDisabled
+    @Test
+    fun areKeyguardShortcutsDisabled_noDisabledKeyguardFeature_shouldReturnFalse() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isFalse()
+    }
+
+    @Test
+    fun areKeyguardShortcutsDisabled_otherDisabledKeyguardFeatures_shouldReturnFalse() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_SECURE_CAMERA or KEYGUARD_DISABLE_FACE)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isFalse()
+    }
+
+    @Test
+    fun areKeyguardShortcutsDisabled_disabledShortcutsKeyguardFeature_shouldReturnTrue() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isTrue()
+    }
+
+    @Test
+    fun areKeyguardShortcutsDisabled_disabledAllKeyguardFeatures_shouldReturnTrue() {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
+            .thenReturn(KEYGUARD_DISABLE_FEATURES_ALL)
+
+        assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
+            .isTrue()
+    }
+    // endregion
+
+    private companion object {
+        const val CURRENT_USER_ID = 123
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 0f0b7d7..b872389 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,16 +16,18 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.app.admin.DevicePolicyManager
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
@@ -38,6 +40,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
@@ -55,6 +58,8 @@
     @Mock lateinit var keyguardManager: KeyguardManager
     @Mock lateinit var userManager: UserManager
     @Mock lateinit var eventLogger: NoteTaskEventLogger
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
     private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
 
@@ -65,6 +70,13 @@
         whenever(context.packageManager).thenReturn(packageManager)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo)
         whenever(userManager.isUserUnlocked).thenReturn(true)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
     }
 
     private fun createNoteTaskController(
@@ -81,6 +93,8 @@
             optionalUserManager = Optional.ofNullable(userManager),
             optionalKeyguardManager = Optional.ofNullable(keyguardManager),
             isEnabled = isEnabled,
+            devicePolicyManager = devicePolicyManager,
+            userTracker = userTracker,
         )
 
     // region onBubbleExpandChanged
@@ -384,6 +398,102 @@
     }
     // endregion
 
+    // region keyguard policy
+    @Test
+    fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+            )
+
+        verifyZeroInteractions(context, bubbles, eventLogger)
+    }
+
+    @Test
+    fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+            )
+
+        verifyZeroInteractions(context, bubbles, eventLogger)
+    }
+
+    @Test
+    fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
+
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+            )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+    }
+
+    @Test
+    fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+        whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(
+                    /* admin= */ eq(null),
+                    /* userHandle= */ anyInt()
+                )
+            )
+            .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE
+            )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+    }
+    // endregion
+
     private companion object {
         const val NOTES_PACKAGE_NAME = "com.android.note.app"
         const val NOTES_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index abcde3d..d86ff67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -78,6 +78,25 @@
         DejankUtils.setImmediate(true);
     }
 
+    /**
+     * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f).
+     */
+    @Test
+    public void testBackGesture_min_scrimAtMaxScale() {
+        mNotificationPanelViewController.onBackProgressed(0.0f);
+        verify(mScrimController).applyBackScaling(1.0f);
+    }
+
+    /**
+     * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum.
+     */
+    @Test
+    public void testBackGesture_max_scrimAtMinScale() {
+        mNotificationPanelViewController.onBackProgressed(1.0f);
+        verify(mScrimController).applyBackScaling(
+                NotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
+    }
+
     @Test
     public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() {
         mStatusBarStateController.setState(KEYGUARD);
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 ec294b1..68d67ca 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
@@ -517,6 +517,26 @@
         verify(mVibratorHelper).vibrateAuthError(anyString());
     }
 
+    @Test
+    public void onFingerprintDetect_showBouncer() {
+        // WHEN fingerprint detect occurs
+        mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
+
+        // THEN shows primary bouncer
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+    }
+
+    @Test
+    public void onFaceDetect_showBouncer() {
+        // WHEN face detect occurs
+        mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
+                BiometricSourceType.FACE, false /* isStrongBiometric */);
+
+        // THEN shows primary bouncer
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+    }
+
     private void givenFingerprintModeUnlockCollapsing() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index db4bb45..a168432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -77,6 +77,8 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewRootImpl;
 import android.view.WindowManager;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -182,6 +184,8 @@
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -194,8 +198,6 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 
-import dagger.Lazy;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -333,6 +335,10 @@
         mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
         // Set default value to avoid IllegalStateException.
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
+        // For the Shade to respond to Back gesture, we must enable the event routing
+        mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
+        // For the Shade to animate during the Back gesture, we must enable the animation flag.
+        mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
 
         IThermalService thermalService = mock(IThermalService.class);
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -855,6 +861,50 @@
         verify(mShadeController).animateCollapseShade();
     }
 
+    /**
+     * When back progress is at 100%, the onBackProgressed animation driver inside
+     * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in).
+     */
+    @Test
+    public void testPredictiveBackAnimation_progressMaxScalesPanel() {
+        mCentralSurfaces.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
+        mCentralSurfaces.handleVisibleToUserChanged(true);
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                mOnBackInvokedCallback.capture());
+
+        OnBackAnimationCallback onBackAnimationCallback =
+                (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+        when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+
+        BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
+        onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
+        verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f));
+    }
+
+    /**
+     * When back progress is at 0%, the onBackProgressed animation driver inside
+     * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in).
+     */
+    @Test
+    public void testPredictiveBackAnimation_progressMinScalesPanel() {
+        mCentralSurfaces.setNotificationShadeWindowViewController(
+                mNotificationShadeWindowViewController);
+        mCentralSurfaces.handleVisibleToUserChanged(true);
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+                mOnBackInvokedCallback.capture());
+
+        OnBackAnimationCallback onBackAnimationCallback =
+                (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+        when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
+
+        BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
+        onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
+        verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f));
+    }
+
     @Test
     public void testPanelOpenForHeadsUp() {
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 7804ebf..864fe0f 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -22,8 +22,11 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.SensorDirectChannel;
 import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.SharedMemory;
 import android.util.ArrayMap;
 import android.util.Slog;
 
@@ -36,6 +39,7 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
 public class SensorController {
@@ -47,6 +51,8 @@
     private static final int UNKNOWN_ERROR = (-2147483647 - 1); // INT32_MIN value
     private static final int BAD_VALUE = -22;
 
+    private static AtomicInteger sNextDirectChannelHandle = new AtomicInteger(1);
+
     private final Object mLock;
     private final int mVirtualDeviceId;
     @GuardedBy("mLock")
@@ -57,8 +63,6 @@
     private final SensorManagerInternal mSensorManagerInternal;
     private final VirtualDeviceManagerInternal mVdmInternal;
 
-
-
     public SensorController(@NonNull Object lock, int virtualDeviceId,
             @Nullable IVirtualSensorCallback virtualSensorCallback) {
         mLock = lock;
@@ -97,7 +101,7 @@
             throws SensorCreationException {
         final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
                 config.getType(), config.getName(),
-                config.getVendor() == null ? "" : config.getVendor(),
+                config.getVendor() == null ? "" : config.getVendor(), config.getFlags(),
                 mRuntimeSensorCallback);
         if (handle <= 0) {
             throw new SensorCreationException("Received an invalid virtual sensor handle.");
@@ -212,6 +216,66 @@
             }
             return OK;
         }
+
+        @Override
+        public int onDirectChannelCreated(ParcelFileDescriptor fd) {
+            if (mCallback == null) {
+                Slog.e(TAG, "No sensor callback for virtual deviceId " + mVirtualDeviceId);
+                return BAD_VALUE;
+            } else if (fd == null) {
+                Slog.e(TAG, "Received invalid ParcelFileDescriptor");
+                return BAD_VALUE;
+            }
+            final int channelHandle = sNextDirectChannelHandle.getAndIncrement();
+            SharedMemory sharedMemory = SharedMemory.fromFileDescriptor(fd);
+            try {
+                mCallback.onDirectChannelCreated(channelHandle, sharedMemory);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to call sensor callback: " + e);
+                return UNKNOWN_ERROR;
+            }
+            return channelHandle;
+        }
+
+        @Override
+        public void onDirectChannelDestroyed(int channelHandle) {
+            if (mCallback == null) {
+                Slog.e(TAG, "No sensor callback for virtual deviceId " + mVirtualDeviceId);
+                return;
+            }
+            try {
+                mCallback.onDirectChannelDestroyed(channelHandle);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to call sensor callback: " + e);
+            }
+        }
+
+        @Override
+        public int onDirectChannelConfigured(int channelHandle, int sensorHandle,
+                @SensorDirectChannel.RateLevel int rateLevel) {
+            if (mCallback == null) {
+                Slog.e(TAG, "No runtime sensor callback configured.");
+                return BAD_VALUE;
+            }
+            VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, sensorHandle);
+            if (sensor == null) {
+                Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId
+                        + " and sensor handle=" + sensorHandle);
+                return BAD_VALUE;
+            }
+            try {
+                mCallback.onDirectChannelConfigured(channelHandle, sensor, rateLevel, sensorHandle);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to call sensor callback: " + e);
+                return UNKNOWN_ERROR;
+            }
+            if (rateLevel == SensorDirectChannel.RATE_STOP) {
+                return OK;
+            } else {
+                // Use the sensor handle as a report token, i.e. a unique identifier of the sensor.
+                return sensorHandle;
+            }
+        }
     }
 
     @VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f650560..642eaef 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -105,6 +105,14 @@
 
     private static final String TAG = "VirtualDeviceImpl";
 
+    private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
+            DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
+                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
     /**
      * Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched.
      */
@@ -281,7 +289,7 @@
      * device.
      */
     int getBaseVirtualDisplayFlags() {
-        int flags = 0;
+        int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
         if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
         }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 10cd6ac..e9a7f20 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -192,20 +192,21 @@
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
 
-    private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).setDeferUntilActive(true)
+    private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
+            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+            .setDeferUntilActive(true)
             .toBundle();
-    private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).setDeferUntilActive(true)
+    /** Used for both connected/disconnected, so match using key */
+    private Bundle mPowerOptions = BroadcastOptions.makeBasic()
+            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+            .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED)
+            .setDeferUntilActive(true)
             .toBundle();
-    private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_POWER_CONNECTED)).setDeferUntilActive(true)
-            .toBundle();
-    private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_BATTERY_OKAY)).setDeferUntilActive(true)
-            .toBundle();
-    private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
-            new IntentFilter(Intent.ACTION_BATTERY_LOW)).setDeferUntilActive(true)
+    /** Used for both low/okay, so match using key */
+    private Bundle mBatteryOptions = BroadcastOptions.makeBasic()
+            .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+            .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY)
+            .setDeferUntilActive(true)
             .toBundle();
 
     private MetricsLogger mMetricsLogger;
@@ -636,7 +637,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mPowerConnectedOptions);
+                                mPowerOptions);
                     }
                 });
             }
@@ -648,7 +649,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mPowerDisconnectedOptions);
+                                mPowerOptions);
                     }
                 });
             }
@@ -662,7 +663,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mBatteryLowOptions);
+                                mBatteryOptions);
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
@@ -675,7 +676,7 @@
                     @Override
                     public void run() {
                         mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
-                                mBatteryOkayOptions);
+                                mBatteryOptions);
                     }
                 });
             }
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 725ea5c..19e5cb1 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -79,6 +79,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.zip.GZIPOutputStream;
@@ -105,6 +106,10 @@
     // Size beyond which to force-compress newly added entries.
     private static final long COMPRESS_THRESHOLD_BYTES = 16_384;
 
+    // Tags that we should drop by default.
+    private static final List<String> DISABLED_BY_DEFAULT_TAGS =
+            List.of("data_app_wtf", "system_app_wtf", "system_server_wtf");
+
     // TODO: This implementation currently uses one file per entry, which is
     // inefficient for smallish entries -- consider using a single queue file
     // per tag (or even globally) instead.
@@ -549,8 +554,13 @@
     public boolean isTagEnabled(String tag) {
         final long token = Binder.clearCallingIdentity();
         try {
-            return !"disabled".equals(Settings.Global.getString(
+            if (DISABLED_BY_DEFAULT_TAGS.contains(tag)) {
+                return "enabled".equals(Settings.Global.getString(
                     mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+            } else {
+                return !"disabled".equals(Settings.Global.getString(
+                    mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 70304c5..c1850bd 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -7822,7 +7822,7 @@
         final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
                 ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
 
-        // TODO(short-service): Log BFSL too.
+        // TODO(short-service): Log the UID capabilities (for BFSL) too, and also the procstate?
         FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                 r.appInfo.uid,
                 r.shortInstanceName,
@@ -7872,7 +7872,8 @@
                 r.mFgsNotificationShown ? 1 : 0,
                 durationMs,
                 r.mStartForegroundCount,
-                fgsStopReasonToString(fgsStopReason));
+                fgsStopReasonToString(fgsStopReason),
+                r.foregroundServiceType);
     }
 
     private void updateNumForegroundServicesLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4a134ee..93effd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3537,7 +3537,7 @@
 
         // We'll take the stack crawls of just the top apps using CPU.
         final int workingStatsNumber = processCpuTracker.countWorkingStats();
-        for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
+        for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
             ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
             if (lastPids.indexOfKey(stats.pid) >= 0) {
                 if (DEBUG_ANR) {
@@ -7015,36 +7015,6 @@
     }
 
     /**
-     * Allows apps to retrieve the MIME type of a URI.
-     * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
-     * users, then it does not need permission to access the ContentProvider.
-     * Either, it needs cross-user uri grants.
-     *
-     * CTS tests for this functionality can be run with "runtest cts-appsecurity".
-     *
-     * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
-     *     src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
-     *
-     * @deprecated -- use getProviderMimeTypeAsync.
-     */
-    @Deprecated
-    @Override
-    public String getProviderMimeType(Uri uri, int userId) {
-        return mCpHelper.getProviderMimeType(uri, userId);
-    }
-
-    /**
-     * Allows apps to retrieve the MIME type of a URI.
-     * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
-     * users, then it does not need permission to access the ContentProvider.
-     * Either way, it needs cross-user uri grants.
-     */
-    @Override
-    public void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
-        mCpHelper.getProviderMimeTypeAsync(uri, userId, resultCallback);
-    }
-
-    /**
      * Filters calls to getType based on permission. If the caller has required permission,
      * then it returns the contentProvider#getType.
      * Else, it returns the contentProvider#getTypeAnonymous, which does not
@@ -18209,8 +18179,9 @@
                     bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
                             TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                             PowerExemptionManager.REASON_LOCALE_CHANGED, "");
-                    bOptions.setRemoveMatchingFilter(
-                            new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+                    bOptions.setDeliveryGroupPolicy(
+                            BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+                    bOptions.setDeferUntilActive(true);
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
                             null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
                             SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 72d6ca9..4c1835e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -146,6 +146,7 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
@@ -595,10 +596,14 @@
             return 1;
         }
 
-        String mimeType = intent.getType();
-        if (mimeType == null && intent.getData() != null
+        AtomicReference<String> mimeType = new AtomicReference<>(intent.getType());
+
+        if (mimeType.get() == null && intent.getData() != null
                 && "content".equals(intent.getData().getScheme())) {
-            mimeType = mInterface.getProviderMimeType(intent.getData(), mUserId);
+            mInterface.getMimeTypeFilterAsync(intent.getData(), mUserId,
+                    new RemoteCallback(result -> {
+                        mimeType.set(result.getPairValue());
+                    }));
         }
 
         do {
@@ -611,8 +616,8 @@
                     int userIdForQuery = mInternal.mUserController.handleIncomingUser(
                             Binder.getCallingPid(), Binder.getCallingUid(), mUserId, false,
                             ALLOW_NON_FULL, "ActivityManagerShellCommand", null);
-                    List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
-                            userIdForQuery).getList();
+                    List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType.get(),
+                            0, userIdForQuery).getList();
                     if (activities == null || activities.size() <= 0) {
                         getErrPrintWriter().println("Error: Intent does not match any activities: "
                                 + intent);
@@ -708,12 +713,12 @@
             }
             if (mWaitOption) {
                 result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
-                        mimeType, null, null, 0, mStartFlags, profilerInfo,
+                        mimeType.get(), null, null, 0, mStartFlags, profilerInfo,
                         options != null ? options.toBundle() : null, mUserId);
                 res = result.result;
             } else {
                 res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null,
-                        intent, mimeType, null, null, 0, mStartFlags, profilerInfo,
+                        intent, mimeType.get(), null, null, 0, mStartFlags, profilerInfo,
                         options != null ? options.toBundle() : null, mUserId);
             }
             final long endTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b942f4b..841b61e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -597,18 +597,6 @@
         final int cookie = traceBegin("enqueueBroadcast");
         r.applySingletonPolicy(mService);
 
-        final IntentFilter removeMatchingFilter = (r.options != null)
-                ? r.options.getRemoveMatchingFilter() : null;
-        if (removeMatchingFilter != null) {
-            final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
-            forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
-                // We only allow caller to remove broadcasts they enqueued
-                return (r.callingUid == testRecord.callingUid)
-                        && (r.userId == testRecord.userId)
-                        && removeMatching.test(testRecord.intent);
-            }, mBroadcastConsumerSkipAndCanceled, true);
-        }
-
         applyDeliveryGroupPolicy(r);
 
         r.enqueueTime = SystemClock.uptimeMillis();
@@ -909,6 +897,10 @@
         final IApplicationThread thread = app.getOnewayThread();
         if (thread != null) {
             try {
+                if (r.shareIdentity) {
+                    mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
+                            UserHandle.getAppId(app.uid), r.callingUid, true);
+                }
                 if (receiver instanceof BroadcastFilter) {
                     notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
                     thread.scheduleRegisteredReceiver(
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f721d69..48df1494 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -969,122 +969,6 @@
     }
 
     /**
-     * Allows apps to retrieve the MIME type of a URI.
-     * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
-     * users, then it does not need permission to access the ContentProvider.
-     * Either, it needs cross-user uri grants.
-     *
-     * CTS tests for this functionality can be run with "runtest cts-appsecurity".
-     *
-     * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
-     *     src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
-     *
-     * @deprecated -- use getProviderMimeTypeAsync.
-     */
-    @Deprecated
-    String getProviderMimeType(Uri uri, int userId) {
-        mService.enforceNotIsolatedCaller("getProviderMimeType");
-        final String name = uri.getAuthority();
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
-        final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
-                ? Binder.clearCallingIdentity() : 0;
-        final ContentProviderHolder holder;
-        try {
-            holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
-                    "*getmimetype*", safeUserId);
-        } finally {
-            if (ident != 0) {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-        try {
-            if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
-                final IBinder providerConnection = holder.connection;
-                final ComponentName providerName = holder.info.getComponentName();
-                // Note: creating a new Runnable instead of using a lambda here since lambdas in
-                // java provide no guarantee that there will be a new instance returned every call.
-                // Hence, it's possible that a cached copy is returned and the ANR is executed on
-                // the incorrect provider.
-                final Runnable providerNotResponding = new Runnable() {
-                    @Override
-                    public void run() {
-                        Log.w(TAG, "Provider " + providerName + " didn't return from getType().");
-                        appNotRespondingViaProvider(providerConnection);
-                    }
-                };
-                mService.mHandler.postDelayed(providerNotResponding, 1000);
-                try {
-                    final String type = holder.provider.getType(uri);
-                    return type;
-                } finally {
-                    mService.mHandler.removeCallbacks(providerNotResponding);
-                    // We need to clear the identity to call removeContentProviderExternalUnchecked
-                    final long token = Binder.clearCallingIdentity();
-                    try {
-                        removeContentProviderExternalUnchecked(name, null /* token */, safeUserId);
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                }
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Content provider dead retrieving " + uri, e);
-            return null;
-        } catch (Exception e) {
-            Log.w(TAG, "Exception while determining type of " + uri, e);
-            return null;
-        }
-
-        return null;
-    }
-
-    /**
-     * Allows apps to retrieve the MIME type of a URI.
-     * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
-     * users, then it does not need permission to access the ContentProvider.
-     * Either way, it needs cross-user uri grants.
-     */
-    void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
-        mService.enforceNotIsolatedCaller("getProviderMimeTypeAsync");
-        final String name = uri.getAuthority();
-        final int callingUid = Binder.getCallingUid();
-        final int callingPid = Binder.getCallingPid();
-        final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
-        final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
-                ? Binder.clearCallingIdentity() : 0;
-        final ContentProviderHolder holder;
-        try {
-            holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
-                    "*getmimetype*", safeUserId);
-        } finally {
-            if (ident != 0) {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        try {
-            if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
-                holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        removeContentProviderExternalUnchecked(name, null, safeUserId);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-                    resultCallback.sendResult(result);
-                }));
-            } else {
-                resultCallback.sendResult(Bundle.EMPTY);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Content provider dead retrieving " + uri, e);
-            resultCallback.sendResult(Bundle.EMPTY);
-        }
-    }
-
-    /**
      * Filters calls to getType based on permission. If the caller has required permission,
      * then it returns the contentProvider#getType.
      * Else, it returns the contentProvider#getTypeAnonymous, which does not
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 1534ff5..50841ae 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -122,9 +122,9 @@
 30091 um_user_visibility_changed (userId|1|5),(visible|1)
 
 # Foreground service start/stop events.
-30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
-30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
-30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
+30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
+30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
+30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
 
 # Intent Sender redirect for UserHandle.USER_CURRENT
 30110 am_intent_sender_redirect_user (userId|1|5)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 0c36626..d05301a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -207,7 +207,8 @@
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_BEGIN;
             case OOM_ADJ_REASON_PROCESS_END:
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
-            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: // TODO(short-service) add value to AppProtoEnums
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+                return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 45181e8..0d0e576 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1315,7 +1315,7 @@
         // persistent data
         initVolumeGroupStates();
 
-        mSoundDoseHelper.initSafeUsbMediaVolumeIndex();
+        mSoundDoseHelper.initSafeMediaVolumeIndex();
         // Link VGS on VSS
         initVolumeStreamStates();
 
@@ -8837,7 +8837,7 @@
         final VolumeStreamState streamState = mStreamStates[update.mStreamType];
         if (update.hasVolumeIndex()) {
             int index = update.getVolumeIndex();
-            if (!mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
+            if (mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
                 index = mSoundDoseHelper.safeMediaVolumeIndex(update.mDevice);
             }
             streamState.setIndex(index, update.mDevice, update.mCaller,
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 4b30234..cf81dbe 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -52,10 +52,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
@@ -80,7 +79,6 @@
     // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
     // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
     // (when user opts out).
-    // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED
     private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
     private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
     private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
@@ -94,6 +92,8 @@
 
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
 
+    private static final int MOMENTARY_EXPOSURE_TIMEOUT_MS = (20 * 3600 * 1000); // 20 hours
+
     // 30s after boot completed
     private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;
 
@@ -127,30 +127,50 @@
 
     // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
     private int mSafeMediaVolumeIndex;
-    // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
+    // mSafeMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
     // property, divided by 100.0.
-    private float mSafeUsbMediaVolumeDbfs;
+    // For now using the same value for CSD supported devices
+    private float mSafeMediaVolumeDbfs;
 
-    // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
-    // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
-    // flinger mixer.
-    // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
-    // amplification when both effects are on with all band gains at maximum.
-    // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
-    // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
-    private int mSafeUsbMediaVolumeIndex;
-    // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
-    private final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
-            Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
-                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
+    private static class SafeDeviceVolumeInfo {
+        int mDeviceType;
+        int mSafeVolumeIndex = -1;
 
-    private final Set<Integer> mSafeMediaCsdDevices = new HashSet<>(
-            Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
-                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET,
-                    AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_BROADCAST,
-                    AudioSystem.DEVICE_OUT_HEARING_AID,
-                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
-                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+        SafeDeviceVolumeInfo(int deviceType) {
+            mDeviceType = deviceType;
+        }
+    }
+
+    /**
+     * mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced.
+     * Contains a safe volume index for a given device type.
+     * Indexes are used for headsets and is the music volume UI index
+     * corresponding to a gain of mSafeMediaVolumeDbfs (defaulting to -37dB) in audio
+     * flinger mixer.
+     * We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+     * amplification when both effects are on with all band gains at maximum.
+     * This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
+     * the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
+     */
+    private final HashMap<Integer, SafeDeviceVolumeInfo> mSafeMediaVolumeDevices =
+            new HashMap<>() {{
+                put(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADSET));
+                put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE));
+                put(AudioSystem.DEVICE_OUT_USB_HEADSET,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_USB_HEADSET));
+                put(AudioSystem.DEVICE_OUT_BLE_HEADSET,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_HEADSET));
+                put(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_BROADCAST));
+                put(AudioSystem.DEVICE_OUT_HEARING_AID,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_HEARING_AID));
+                put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES));
+                put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+            }};
 
     // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
@@ -173,6 +193,10 @@
 
     @GuardedBy("mCsdStateLock")
     private float mCurrentCsd = 0.f;
+
+    @GuardedBy("mCsdStateLock")
+    private long mLastMomentaryExposureTimeMs = -1;
+
     // dose at which the next dose reached warning occurs
     @GuardedBy("mCsdStateLock")
     private float mNextCsdWarning = 1.0f;
@@ -188,10 +212,26 @@
 
     private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
         public void onMomentaryExposure(float currentMel, int deviceId) {
+            if (!mEnableCsd) {
+                Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback");
+                return;
+            }
+
             Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
                     + currentMel);
             mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
-            if (mEnableCsd) {
+
+            boolean postWarning = false;
+            synchronized (mCsdStateLock) {
+                if (mLastMomentaryExposureTimeMs < 0
+                        || (System.currentTimeMillis() - mLastMomentaryExposureTimeMs)
+                        >= MOMENTARY_EXPOSURE_TIMEOUT_MS) {
+                    mLastMomentaryExposureTimeMs = System.currentTimeMillis();
+                    postWarning = true;
+                }
+            }
+
+            if (postWarning) {
                 mVolumeController.postDisplayCsdWarning(
                         AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE,
                         getTimeoutMsForWarning(AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE));
@@ -250,15 +290,11 @@
         mContext = context;
 
         mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
-        if (mEnableCsd) {
-            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
-        } else {
-            mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
-                    Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
-        }
-
         initCsd();
 
+        mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+                Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
+
         // The default safe volume index read here will be replaced by the actual value when
         // the mcc is read by onConfigureSafeMedia()
         // For now we use the same index for RS2 initial warning with CSD
@@ -388,14 +424,12 @@
     }
 
     /*package*/ int safeMediaVolumeIndex(int device) {
-        if (!mSafeMediaVolumeDevices.contains(device)) {
+        final SafeDeviceVolumeInfo vi = mSafeMediaVolumeDevices.get(device);
+        if (vi == null) {
             return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
         }
-        if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
-            return mSafeUsbMediaVolumeIndex;
-        } else {
-            return mSafeMediaVolumeIndex;
-        }
+
+        return vi.mSafeVolumeIndex;
     }
 
     /*package*/ void restoreMusicActiveMs() {
@@ -419,20 +453,24 @@
     /*package*/ void enforceSafeMediaVolume(String caller) {
         AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
                 AudioSystem.STREAM_MUSIC);
-        Set<Integer> devices = mSafeMediaVolumeDevices;
 
-        for (int device : devices) {
-            int index = streamState.getIndex(device);
-            int safeIndex = safeMediaVolumeIndex(device);
+        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
+            int index = streamState.getIndex(vi.mDeviceType);
+            int safeIndex = safeMediaVolumeIndex(vi.mDeviceType);
             if (index > safeIndex) {
-                streamState.setIndex(safeIndex, device, caller, true /*hasModifyAudioSettings*/);
+                streamState.setIndex(safeIndex, vi.mDeviceType, caller,
+                        true /*hasModifyAudioSettings*/);
                 mAudioHandler.sendMessageAtTime(
-                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, device, /*arg2=*/0,
-                                streamState), /*delay=*/0);
+                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, vi.mDeviceType,
+                                /*arg2=*/0, streamState), /*delay=*/0);
             }
         }
     }
 
+    /**
+     * Returns {@code true} if the safe media actions can be applied for the given stream type,
+     * volume index and device.
+     **/
     /*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) {
         boolean result;
         synchronized (mSafeMediaVolumeStateLock) {
@@ -443,17 +481,16 @@
 
     @GuardedBy("mSafeMediaVolumeStateLock")
     private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
-        return (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_ACTIVE)
-                    || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
-                    || (!mSafeMediaVolumeDevices.contains(device))
-                    || (index <= safeMediaVolumeIndex(device))
-                    || mEnableCsd;
+        return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
+                    && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
+                    && (mSafeMediaVolumeDevices.containsKey(device))
+                    && (index > safeMediaVolumeIndex(device));
     }
 
     /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
             int flags) {
         synchronized (mSafeMediaVolumeStateLock) {
-            if (!checkSafeMediaVolume_l(streamType, index, device)) {
+            if (checkSafeMediaVolume_l(streamType, index, device)) {
                 mVolumeController.postDisplaySafeVolumeWarning(flags);
                 mPendingVolumeCommand = new StreamVolumeCommand(
                         streamType, index, flags, device);
@@ -484,15 +521,13 @@
     /*package*/ void scheduleMusicActiveCheck() {
         synchronized (mSafeMediaVolumeStateLock) {
             cancelMusicActiveCheck();
-            if (!mEnableCsd) {
-                mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
-                        REQUEST_CODE_CHECK_MUSIC_ACTIVE,
-                        new Intent(ACTION_CHECK_MUSIC_ACTIVE),
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-                mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                        SystemClock.elapsedRealtime()
-                                + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
-            }
+            mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+                    REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+                    new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime()
+                            + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
         }
     }
 
@@ -500,7 +535,7 @@
         synchronized (mSafeMediaVolumeStateLock) {
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
                 int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                if (mSafeMediaVolumeDevices.contains(device) && isStreamActive) {
+                if (mSafeMediaVolumeDevices.containsKey(device) && isStreamActive) {
                     scheduleMusicActiveCheck();
                     int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
                             device);
@@ -528,27 +563,31 @@
 
     /*package*/ void configureSafeMedia(boolean forced, String caller) {
         int msg = MSG_CONFIGURE_SAFE_MEDIA;
-        mAudioHandler.removeMessages(msg);
+        if (forced) {
+            // unforced should not cancel forced configure messages
+            mAudioHandler.removeMessages(msg);
+        }
 
         long time = 0;
         if (forced) {
             time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean(
                     "audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS));
         }
+
         mAudioHandler.sendMessageAtTime(
                 mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
                 time);
     }
 
-    /*package*/ void initSafeUsbMediaVolumeIndex() {
-        // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
-        // relies on audio policy having correct ranges for volume indexes.
-        mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+    /*package*/ void initSafeMediaVolumeIndex() {
+        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
+            vi.mSafeVolumeIndex = getSafeDeviceMediaVolumeIndex(vi.mDeviceType);
+        }
     }
 
     /*package*/ int getSafeMediaVolumeIndex(int device) {
-        if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains(
-                device)) {
+        if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE
+                && mSafeMediaVolumeDevices.containsKey(device)) {
             return safeMediaVolumeIndex(device);
         } else {
             return -1;
@@ -557,7 +596,7 @@
 
     /*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device,
             int flags) {
-        if (checkSafeMediaVolume(streamType, index, device)) {
+        if (!checkSafeMediaVolume(streamType, index, device)) {
             return false;
         }
 
@@ -566,7 +605,7 @@
     }
 
     /*package*/ boolean safeDevicesContains(int device) {
-        return mSafeMediaVolumeDevices.contains(device);
+        return mSafeMediaVolumeDevices.containsKey(device);
     }
 
     /*package*/ void invalidatPendingVolumeCommand() {
@@ -612,8 +651,11 @@
         pw.print("  mSafeMediaVolumeState=");
         pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
         pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
-        pw.print("  mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
-        pw.print("  mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
+        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
+            pw.print("  mSafeMediaVolumeIndex["); pw.print(vi.mDeviceType);
+            pw.print("]="); pw.println(vi.mSafeVolumeIndex);
+        }
+        pw.print("  mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs);
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
         pw.print("  mMcc="); pw.println(mMcc);
         pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
@@ -660,11 +702,12 @@
             if (!isAbsoluteVolume) {
                 // remove any possible previous attenuation
                 soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
+
                 return;
             }
 
             if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
-                    && mSafeMediaCsdDevices.contains(device)) {
+                    && mSafeMediaVolumeDevices.containsKey(device)) {
                 soundDose.updateAttenuation(
                         AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
                                 (newIndex + 5) / 10,
@@ -715,7 +758,7 @@
                 mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                         com.android.internal.R.integer.config_safe_media_volume_index) * 10;
 
-                mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+                initSafeMediaVolumeIndex();
 
                 boolean safeMediaVolumeEnabled =
                         SystemProperties.getBoolean("audio.safemedia.force", false)
@@ -728,7 +771,7 @@
                 // The persisted state is either "disabled" or "active": this is the state applied
                 // next time we boot and cannot be "inactive"
                 int persistedState;
-                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) {
+                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
                     persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
                     // The state can already be "inactive" here if the user has forced it before
                     // the 30 seconds timeout for forced configuration. In this case we don't reset
@@ -801,25 +844,32 @@
         mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
     }
 
-    private int getSafeUsbMediaVolumeIndex() {
+    private int getSafeDeviceMediaVolumeIndex(int deviceType) {
+        // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP
+        // instead of computing it from the volume curves
+        if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+                || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd) {
+            return mSafeMediaVolumeIndex;
+        }
+
         // determine UI volume index corresponding to the wanted safe gain in dBFS
         int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
         int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
 
-        mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
+        mSafeMediaVolumeDbfs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
 
         while (Math.abs(max - min) > 1) {
             int index = (max + min) / 2;
-            float gainDB = AudioSystem.getStreamVolumeDB(
-                    AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
+            float gainDB = AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, index,
+                    deviceType);
             if (Float.isNaN(gainDB)) {
                 //keep last min in case of read error
                 break;
-            } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
+            } else if (gainDB == mSafeMediaVolumeDbfs) {
                 min = index;
                 break;
-            } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
+            } else if (gainDB < mSafeMediaVolumeDbfs) {
                 min = index;
             } else {
                 max = index;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 8a33f22..f6c1375 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -427,13 +427,6 @@
                 return -1;
             }
 
-            if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, options.getUserId())) {
-                // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
-                // ever be invoked when the user is encrypted or lockdown.
-                Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
-                return -1;
-            }
-
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 6e1640d..22b6a53 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -35,7 +35,6 @@
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientBrightnessDayStats;
 import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
@@ -126,7 +125,7 @@
     private static final int MSG_BRIGHTNESS_CHANGED = 1;
     private static final int MSG_STOP_SENSOR_LISTENER = 2;
     private static final int MSG_START_SENSOR_LISTENER = 3;
-    private static final int MSG_BRIGHTNESS_CONFIG_CHANGED = 4;
+    private static final int MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED = 4;
     private static final int MSG_SENSOR_CHANGED = 5;
 
     private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -162,7 +161,7 @@
     private boolean mColorSamplingEnabled;
     private int mNoFramesToSample;
     private float mFrameRate;
-    private BrightnessConfiguration mBrightnessConfiguration;
+    private boolean mShouldCollectColorSample = false;
     // End of block of members that should only be accessed on the mBgHandler thread.
 
     private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL;
@@ -208,9 +207,9 @@
     /**
      * Update tracker with new brightness configuration.
      */
-    public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration) {
-        mBgHandler.obtainMessage(MSG_BRIGHTNESS_CONFIG_CHANGED,
-                brightnessConfiguration).sendToTarget();
+    public void setShouldCollectColorSample(boolean shouldCollectColorSample) {
+        mBgHandler.obtainMessage(MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED,
+                shouldCollectColorSample).sendToTarget();
     }
 
     private void backgroundStart(float initialBrightness) {
@@ -320,7 +319,7 @@
      * Notify the BrightnessTracker that the user has changed the brightness of the display.
      */
     public void notifyBrightnessChanged(float brightness, boolean userInitiated,
-            float powerBrightnessFactor, boolean isUserSetBrightness,
+            float powerBrightnessFactor, boolean wasShortTermModelActive,
             boolean isDefaultBrightnessConfig, String uniqueDisplayId, float[] luxValues,
             long[] luxTimestamps) {
         if (DEBUG) {
@@ -329,7 +328,7 @@
         }
         Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
                 userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
-                        powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
+                        powerBrightnessFactor, wasShortTermModelActive, isDefaultBrightnessConfig,
                         mInjector.currentTimeMillis(), uniqueDisplayId, luxValues, luxTimestamps));
         m.sendToTarget();
     }
@@ -343,7 +342,7 @@
     }
 
     private void handleBrightnessChanged(float brightness, boolean userInitiated,
-            float powerBrightnessFactor, boolean isUserSetBrightness,
+            float powerBrightnessFactor, boolean wasShortTermModelActive,
             boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId,
             float[] luxValues, long[] luxTimestamps) {
         BrightnessChangeEvent.Builder builder;
@@ -368,7 +367,7 @@
             builder.setBrightness(brightness);
             builder.setTimeStamp(timestamp);
             builder.setPowerBrightnessFactor(powerBrightnessFactor);
-            builder.setUserBrightnessPoint(isUserSetBrightness);
+            builder.setUserBrightnessPoint(wasShortTermModelActive);
             builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
             builder.setUniqueDisplayId(uniqueDisplayId);
 
@@ -827,8 +826,7 @@
         if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
                 || !mInjector.isInteractive(mContext)
                 || mColorSamplingEnabled
-                || mBrightnessConfiguration == null
-                || !mBrightnessConfiguration.shouldCollectColorSamples()) {
+                || !mShouldCollectColorSample) {
             return;
         }
 
@@ -997,7 +995,7 @@
                     BrightnessChangeValues values = (BrightnessChangeValues) msg.obj;
                     boolean userInitiatedChange = (msg.arg1 == 1);
                     handleBrightnessChanged(values.brightness, userInitiatedChange,
-                            values.powerBrightnessFactor, values.isUserSetBrightness,
+                            values.powerBrightnessFactor, values.wasShortTermModelActive,
                             values.isDefaultBrightnessConfig, values.timestamp,
                             values.uniqueDisplayId, values.luxValues, values.luxTimestamps);
                     break;
@@ -1009,14 +1007,11 @@
                     stopSensorListener();
                     disableColorSampling();
                     break;
-                case MSG_BRIGHTNESS_CONFIG_CHANGED:
-                    mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
-                    boolean shouldCollectColorSamples =
-                            mBrightnessConfiguration != null
-                                    && mBrightnessConfiguration.shouldCollectColorSamples();
-                    if (shouldCollectColorSamples && !mColorSamplingEnabled) {
+                case MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED:
+                    mShouldCollectColorSample = (boolean) msg.obj;
+                    if (mShouldCollectColorSample && !mColorSamplingEnabled) {
                         enableColorSampling();
-                    } else if (!shouldCollectColorSamples && mColorSamplingEnabled) {
+                    } else if (!mShouldCollectColorSample && mColorSamplingEnabled) {
                         disableColorSampling();
                     }
                     break;
@@ -1031,7 +1026,7 @@
     private static class BrightnessChangeValues {
         public final float brightness;
         public final float powerBrightnessFactor;
-        public final boolean isUserSetBrightness;
+        public final boolean wasShortTermModelActive;
         public final boolean isDefaultBrightnessConfig;
         public final long timestamp;
         public final String uniqueDisplayId;
@@ -1039,11 +1034,11 @@
         public final long[] luxTimestamps;
 
         BrightnessChangeValues(float brightness, float powerBrightnessFactor,
-                boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
+                boolean wasShortTermModelActive, boolean isDefaultBrightnessConfig,
                 long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) {
             this.brightness = brightness;
             this.powerBrightnessFactor = powerBrightnessFactor;
-            this.isUserSetBrightness = isUserSetBrightness;
+            this.wasShortTermModelActive = wasShortTermModelActive;
             this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
             this.timestamp = timestamp;
             this.uniqueDisplayId = uniqueDisplayId;
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 99e709e..7b560ce 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -27,6 +27,8 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.server.display.mode.DisplayModeDirector;
+
 import java.io.PrintWriter;
 
 /**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d9b3501..75f8acc 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1260,7 +1260,7 @@
         return mAmbientDarkeningPercentagesIdle;
     }
 
-    SensorData getAmbientLightSensor() {
+    public SensorData getAmbientLightSensor() {
         return mAmbientLightSensor;
     }
 
@@ -2821,7 +2821,7 @@
     /**
      * Uniquely identifies a Sensor, with the combination of Type and Name.
      */
-    static class SensorData {
+    public static class SensorData {
         public String type;
         public String name;
         public float minRefreshRate = 0.0f;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6eb465e..214c591 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -152,6 +152,7 @@
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
 import com.android.server.display.layout.Layout;
+import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.SurfaceAnimationThread;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9917bfc..da8e6a6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1538,10 +1538,10 @@
         // user, or is a temporary adjustment.
         boolean userInitiatedChange = (Float.isNaN(brightnessState))
                 && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
-        boolean hadUserBrightnessPoint = false;
+        boolean wasShortTermModelActive = false;
         // Configure auto-brightness.
         if (mAutomaticBrightnessController != null) {
-            hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+            wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
             mAutomaticBrightnessController.configure(autoBrightnessState,
                     mBrightnessConfiguration,
                     mLastUserSetScreenBrightness,
@@ -1555,7 +1555,8 @@
                 : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         if (mBrightnessTracker != null) {
-            mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+            mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
+                    && mBrightnessConfiguration.shouldCollectColorSamples());
         }
 
         boolean updateScreenBrightnessSetting = false;
@@ -1823,7 +1824,7 @@
                     userInitiatedChange = false;
                 }
                 notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
-                        hadUserBrightnessPoint);
+                        wasShortTermModelActive);
             }
 
             // We save the brightness info *after* the brightness setting has been changed and
@@ -1865,7 +1866,7 @@
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
-        mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
+        mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -2634,7 +2635,7 @@
     }
 
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
-            boolean hadUserDataPoint) {
+            boolean wasShortTermModelActive) {
         final float brightnessInNits = convertToNits(brightness);
         if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
@@ -2645,7 +2646,7 @@
                     ? mPowerRequest.screenLowPowerBrightnessFactor
                     : 1.0f;
             mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
-                    powerFactor, hadUserDataPoint,
+                    powerFactor, wasShortTermModelActive,
                     mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
                     mAutomaticBrightnessController.getLastSensorValues(),
                     mAutomaticBrightnessController.getLastSensorTimestamps());
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index f49419cf..a2a53e3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1250,10 +1250,10 @@
         // user, or is a temporary adjustment.
         boolean userInitiatedChange = (Float.isNaN(brightnessState))
                 && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
-        boolean hadUserBrightnessPoint = false;
+        boolean wasShortTermModelActive = false;
         // Configure auto-brightness.
         if (mAutomaticBrightnessController != null) {
-            hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+            wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
             mAutomaticBrightnessController.configure(autoBrightnessState,
                     mBrightnessConfiguration,
                     mDisplayBrightnessController.getLastUserSetScreenBrightness(),
@@ -1267,7 +1267,8 @@
                 : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         if (mBrightnessTracker != null) {
-            mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+            mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
+                    && mBrightnessConfiguration.shouldCollectColorSamples());
         }
 
         boolean updateScreenBrightnessSetting = false;
@@ -1537,7 +1538,7 @@
                     userInitiatedChange = false;
                 }
                 notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
-                        hadUserBrightnessPoint);
+                        wasShortTermModelActive);
             }
 
             // We save the brightness info *after* the brightness setting has been changed and
@@ -1579,7 +1580,7 @@
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
-        mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
+        mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
         mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
                 .getDisplayBrightnessStrategyName());
         // Temporary is what we use during slider interactions. We avoid logging those so that
@@ -2220,7 +2221,7 @@
     }
 
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
-            boolean hadUserDataPoint) {
+            boolean wasShortTermModelActive) {
         final float brightnessInNits = convertToNits(brightness);
         if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
@@ -2231,7 +2232,7 @@
                     ? mPowerRequest.screenLowPowerBrightnessFactor
                     : 1.0f;
             mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
-                    powerFactor, hadUserDataPoint,
+                    powerFactor, wasShortTermModelActive,
                     mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
                     mAutomaticBrightnessController.getLastSensorValues(),
                     mAutomaticBrightnessController.getLastSensorTimestamps());
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8f52c97..58c5034 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -46,6 +46,7 @@
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
+import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index fc90db6..78c597e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -33,6 +33,7 @@
 import android.view.SurfaceControl;
 
 import com.android.server.display.layout.Layout;
+import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 3e67f0a..2ce7690 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.display.mode.DisplayModeDirector;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
similarity index 98%
rename from services/core/java/com/android/server/display/DisplayModeDirector.java
rename to services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 31f5ab7..24d5ca4 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.display;
+package com.android.server.display.mode;
 
 import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
@@ -67,6 +67,7 @@
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
 import com.android.server.display.utils.SensorUtils;
@@ -214,6 +215,9 @@
         mUdfpsObserver.observe();
     }
 
+    /**
+    * Enables or disables component logging
+    */
     public void setLoggingEnabled(boolean loggingEnabled) {
         if (mLoggingEnabled == loggingEnabled) {
             return;
@@ -1574,7 +1578,10 @@
         }
     }
 
-    final class AppRequestObserver {
+    /**
+     *  Responsible for keeping track of app requested refresh rates per display
+     */
+    public final class AppRequestObserver {
         private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
         private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
 
@@ -1583,6 +1590,9 @@
             mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
         }
 
+        /**
+         * Sets refresh rates from app request
+         */
         public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange,
                 float requestedMaxRefreshRateRange) {
             synchronized (mLock) {
@@ -1665,7 +1675,7 @@
             return null;
         }
 
-        public void dumpLocked(PrintWriter pw) {
+        private void dumpLocked(PrintWriter pw) {
             pw.println("  AppRequestObserver");
             pw.println("    mAppRequestedModeByDisplay:");
             for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
@@ -1794,7 +1804,7 @@
      */
     @VisibleForTesting
     public class BrightnessObserver implements DisplayManager.DisplayListener {
-        private final static int LIGHT_SENSOR_RATE_MS = 250;
+        private static final int LIGHT_SENSOR_RATE_MS = 250;
         private int[] mLowDisplayBrightnessThresholds;
         private int[] mLowAmbientBrightnessThresholds;
         private int[] mHighDisplayBrightnessThresholds;
@@ -2019,7 +2029,7 @@
             return mLowAmbientBrightnessThresholds;
         }
 
-        public void observe(SensorManager sensorManager) {
+        private void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
             mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
 
@@ -2064,11 +2074,11 @@
             mDeviceConfigDisplaySettings.startListening();
 
             mInjector.registerDisplayListener(this, mHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                            | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
-        public void setLoggingEnabled(boolean loggingEnabled) {
+        private void setLoggingEnabled(boolean loggingEnabled) {
             if (mLoggingEnabled == loggingEnabled) {
                 return;
             }
@@ -2076,7 +2086,8 @@
             mLightSensorListener.setLoggingEnabled(loggingEnabled);
         }
 
-        public void onRefreshRateSettingChangedLocked(float min, float max) {
+        @VisibleForTesting
+        void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
                 mRefreshRateChangeable = changeable;
@@ -2089,14 +2100,14 @@
             }
         }
 
-        public void onLowPowerModeEnabledLocked(boolean b) {
+        private void onLowPowerModeEnabledLocked(boolean b) {
             if (mLowPowerModeEnabled != b) {
                 mLowPowerModeEnabled = b;
                 updateSensorStatus();
             }
         }
 
-        public void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
+        private void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
                 int[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
@@ -2123,7 +2134,7 @@
             }
         }
 
-        public void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
+        private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
                 int[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
@@ -2150,7 +2161,7 @@
             }
         }
 
-        public void dumpLocked(PrintWriter pw) {
+        void dumpLocked(PrintWriter pw) {
             pw.println("  BrightnessObserver");
             pw.println("    mAmbientLux: " + mAmbientLux);
             pw.println("    mBrightness: " + mBrightness);
@@ -2400,7 +2411,7 @@
         }
 
         @VisibleForTesting
-        public void setDefaultDisplayState(int state) {
+        void setDefaultDisplayState(int state) {
             if (mLoggingEnabled) {
                 Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = "
                         + mDefaultDisplayState + ", state = " + state);
@@ -2475,7 +2486,7 @@
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
-            final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
+            private static final int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
             private float mLastSensorData;
             private long mTimestamp;
             private boolean mLoggingEnabled;
@@ -2876,10 +2887,10 @@
             }
 
             final int hbmMode = info.highBrightnessMode;
-            final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF &&
-                info.adjustedBrightness > info.highBrightnessTransitionPoint;
-            if (hbmMode == mHbmMode.get(displayId) &&
-                isHbmActive == mHbmActive.get(displayId)) {
+            final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+                    && info.adjustedBrightness > info.highBrightnessTransitionPoint;
+            if (hbmMode == mHbmMode.get(displayId)
+                    && isHbmActive == mHbmActive.get(displayId)) {
                 // no change, ignore.
                 return;
             }
@@ -2901,7 +2912,7 @@
             Vote vote = null;
             if (mHbmActive.get(displayId, false)) {
                 final int hbmMode =
-                    mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+                        mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
                 if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
                     // Device resource properties take priority over DisplayDeviceConfig
                     if (mRefreshRateInHbmSunlight > 0) {
@@ -2909,7 +2920,7 @@
                                 mRefreshRateInHbmSunlight);
                     } else {
                         final List<RefreshRateLimitation> limits =
-                            mDisplayManagerInternal.getRefreshRateLimitations(displayId);
+                                mDisplayManagerInternal.getRefreshRateLimitations(displayId);
                         for (int i = 0; limits != null && i < limits.size(); i++) {
                             final RefreshRateLimitation limitation = limits.get(i);
                             if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
@@ -2919,8 +2930,8 @@
                             }
                         }
                     }
-                } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR &&
-                        mRefreshRateInHbmHdr > 0) {
+                } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+                        && mRefreshRateInHbmHdr > 0) {
                     // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for
                     // a vote from Device properties
                     vote = Vote.forPhysicalRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
@@ -2988,9 +2999,6 @@
     }
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
-        public DeviceConfigDisplaySettings() {
-        }
-
         public void startListening() {
             mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     BackgroundThread.getExecutor(), this);
@@ -3001,8 +3009,8 @@
          */
         public int[] getLowDisplayBrightnessThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.
-                            KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+                    DisplayManager.DeviceConfig
+                            .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -3010,8 +3018,8 @@
          */
         public int[] getLowAmbientBrightnessThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.
-                            KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+                    DisplayManager.DeviceConfig
+                            .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
         }
 
         public int getRefreshRateInLowZone() {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0c69855..5a832b7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1065,18 +1065,7 @@
         mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsHave");
     }
 
-    private static final String[] UNPROTECTED_SETTINGS = {
-        // These three LOCK_PATTERN_* settings have traditionally been readable via the public API
-        // android.provider.Settings.{System,Secure}.getString() without any permission.
-        Settings.Secure.LOCK_PATTERN_ENABLED,
-        Settings.Secure.LOCK_PATTERN_VISIBLE,
-        Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED,
-    };
-
     private final void checkDatabaseReadPermission(String requestedKey, int userId) {
-        if (ArrayUtils.contains(UNPROTECTED_SETTINGS, requestedKey)) {
-            return;
-        }
         if (!hasPermission(PERMISSION)) {
             throw new SecurityException("uid=" + getCallingUid() + " needs permission "
                     + PERMISSION + " to read " + requestedKey + " for user " + userId);
@@ -1190,9 +1179,6 @@
     @Override
     public boolean getBoolean(String key, boolean defaultValue, int userId) {
         checkDatabaseReadPermission(key, userId);
-        if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
-            return getCredentialTypeInternal(userId) == CREDENTIAL_TYPE_PATTERN;
-        }
         return mStorage.getBoolean(key, defaultValue, userId);
     }
 
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
new file mode 100644
index 0000000..58fdb57
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
+import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
+import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaRoute2Info;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controls bluetooth routes and provides selected route override.
+ *
+ * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does
+ * not support routes selection logic. Instead, relies on external clients to make a decision
+ * about currently selected route.
+ *
+ * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio
+ * Policies.
+ */
+class AudioPoliciesBluetoothRouteController implements BluetoothRouteController {
+    private static final String TAG = "APBtRouteController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
+    private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
+
+    // Maps hardware address to BluetoothRouteInfo
+    private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+    private final List<BluetoothRouteInfo> mActiveRoutes = new ArrayList<>();
+
+    // Route type -> volume map
+    private final SparseIntArray mVolumeMap = new SparseIntArray();
+
+    private final Context mContext;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final BluetoothRoutesUpdatedListener mListener;
+    private final AudioManager mAudioManager;
+    private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+    private final AdapterStateChangedReceiver mAdapterStateChangedReceiver =
+            new AdapterStateChangedReceiver();
+    private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
+            new DeviceStateChangedReceiver();
+
+    private BluetoothA2dp mA2dpProfile;
+    private BluetoothHearingAid mHearingAidProfile;
+    private BluetoothLeAudio mLeAudioProfile;
+
+    AudioPoliciesBluetoothRouteController(Context context, BluetoothAdapter btAdapter,
+            BluetoothRoutesUpdatedListener listener) {
+        mContext = context;
+        mBluetoothAdapter = btAdapter;
+        mListener = listener;
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        buildBluetoothRoutes();
+    }
+
+    /**
+     * Registers listener to bluetooth status changes as the provided user.
+     *
+     * The registered receiver listens to {@link BluetoothA2dp#ACTION_ACTIVE_DEVICE_CHANGED} and
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED } events for {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#HEARING_AID}, and {@link BluetoothProfile#LE_AUDIO} bluetooth profiles.
+     *
+     * @param user {@code UserHandle} as which receiver is registered
+     */
+    @Override
+    public void start(UserHandle user) {
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
+
+        IntentFilter adapterStateChangedIntentFilter = new IntentFilter();
+
+        adapterStateChangedIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        mContext.registerReceiverAsUser(mAdapterStateChangedReceiver, user,
+                adapterStateChangedIntentFilter, null, null);
+
+        IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
+
+        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+
+        mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
+                deviceStateChangedIntentFilter, null, null);
+    }
+
+    @Override
+    public void stop() {
+        mContext.unregisterReceiver(mAdapterStateChangedReceiver);
+        mContext.unregisterReceiver(mDeviceStateChangedReceiver);
+    }
+
+    @Override
+    public boolean selectRoute(String deviceAddress) {
+        // Temporary no-op.
+        return false;
+    }
+
+    /**
+     * Transfers to a given bluetooth route.
+     * The dedicated BT device with the route would be activated.
+     *
+     * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of
+     *               BT routes.
+     */
+    @Override
+    public void transferTo(@Nullable String routeId) {
+        if (routeId == null) {
+            clearActiveDevices();
+            return;
+        }
+
+        BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId);
+
+        if (btRouteInfo == null) {
+            Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId);
+            return;
+        }
+
+        if (mBluetoothAdapter != null) {
+            mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO);
+        }
+    }
+
+    private BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) {
+        if (routeId == null) {
+            return null;
+        }
+        for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
+            if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
+                return btRouteInfo;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Clears the active device for all known profiles.
+     */
+    private void clearActiveDevices() {
+        if (mBluetoothAdapter != null) {
+            mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO);
+        }
+    }
+
+    private void buildBluetoothRoutes() {
+        mBluetoothRoutes.clear();
+        Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
+        if (bondedDevices != null) {
+            for (BluetoothDevice device : bondedDevices) {
+                if (device.isConnected()) {
+                    BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+                    if (newBtRoute.mConnectedProfiles.size() > 0) {
+                        mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+                    }
+                }
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public MediaRoute2Info getSelectedRoute() {
+        // For now, active routes can be multiple only when a pair of hearing aid devices is active.
+        // Let the first active device represent them.
+        return (mActiveRoutes.isEmpty() ? null : mActiveRoutes.get(0).mRoute);
+    }
+
+    @NonNull
+    @Override
+    public List<MediaRoute2Info> getTransferableRoutes() {
+        List<MediaRoute2Info> routes = getAllBluetoothRoutes();
+        for (BluetoothRouteInfo btRoute : mActiveRoutes) {
+            routes.remove(btRoute.mRoute);
+        }
+        return routes;
+    }
+
+    @NonNull
+    @Override
+    public List<MediaRoute2Info> getAllBluetoothRoutes() {
+        List<MediaRoute2Info> routes = new ArrayList<>();
+        List<String> routeIds = new ArrayList<>();
+
+        MediaRoute2Info selectedRoute = getSelectedRoute();
+        if (selectedRoute != null) {
+            routes.add(selectedRoute);
+            routeIds.add(selectedRoute.getId());
+        }
+
+        for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+            // A pair of hearing aid devices or having the same hardware address
+            if (routeIds.contains(btRoute.mRoute.getId())) {
+                continue;
+            }
+            routes.add(btRoute.mRoute);
+            routeIds.add(btRoute.mRoute.getId());
+        }
+        return routes;
+    }
+
+    /**
+     * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}.
+     *
+     * @return true if devices can be handled by the provider.
+     */
+    @Override
+    public boolean updateVolumeForDevices(int devices, int volume) {
+        int routeType;
+        if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
+            routeType = MediaRoute2Info.TYPE_HEARING_AID;
+        } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
+                | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
+                | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
+            routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+        } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
+            routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
+        } else {
+            return false;
+        }
+        mVolumeMap.put(routeType, volume);
+
+        boolean shouldNotify = false;
+        for (BluetoothRouteInfo btRoute : mActiveRoutes) {
+            if (btRoute.mRoute.getType() != routeType) {
+                continue;
+            }
+            btRoute.mRoute = new MediaRoute2Info.Builder(btRoute.mRoute)
+                    .setVolume(volume)
+                    .build();
+            shouldNotify = true;
+        }
+        if (shouldNotify) {
+            notifyBluetoothRoutesUpdated();
+        }
+        return true;
+    }
+
+    private void notifyBluetoothRoutesUpdated() {
+        if (mListener != null) {
+            mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+        }
+    }
+
+    private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+        BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+        newBtRoute.mBtDevice = device;
+
+        String routeId = device.getAddress();
+        String deviceName = device.getName();
+        if (TextUtils.isEmpty(deviceName)) {
+            deviceName = mContext.getResources().getText(R.string.unknownName).toString();
+        }
+        int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+        newBtRoute.mConnectedProfiles = new SparseBooleanArray();
+        if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+            newBtRoute.mConnectedProfiles.put(BluetoothProfile.A2DP, true);
+        }
+        if (mHearingAidProfile != null
+                && mHearingAidProfile.getConnectedDevices().contains(device)) {
+            newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+            // Intentionally assign the same ID for a pair of devices to publish only one of them.
+            routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device);
+            type = MediaRoute2Info.TYPE_HEARING_AID;
+        }
+        if (mLeAudioProfile != null
+                && mLeAudioProfile.getConnectedDevices().contains(device)) {
+            newBtRoute.mConnectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
+            routeId = LE_AUDIO_ROUTE_ID_PREFIX + mLeAudioProfile.getGroupId(device);
+            type = MediaRoute2Info.TYPE_BLE_HEADSET;
+        }
+
+        // Current volume will be set when connected.
+        newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName)
+                .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+                .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+                .setDescription(mContext.getResources().getText(
+                        R.string.bluetooth_a2dp_audio_route_name).toString())
+                .setType(type)
+                .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+                .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                .setAddress(device.getAddress())
+                .build();
+        return newBtRoute;
+    }
+
+    private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
+            @MediaRoute2Info.ConnectionState int state) {
+        if (btRoute == null) {
+            Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
+            return;
+        }
+        if (btRoute.mRoute.getConnectionState() == state) {
+            return;
+        }
+
+        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute)
+                .setConnectionState(state);
+        builder.setType(btRoute.getRouteType());
+
+        if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
+            builder.setVolume(mVolumeMap.get(btRoute.getRouteType(), 0));
+        }
+        btRoute.mRoute = builder.build();
+    }
+
+    private void addActiveRoute(BluetoothRouteInfo btRoute) {
+        if (btRoute == null) {
+            Slog.w(TAG, "addActiveRoute: btRoute is null");
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Adding active route: " + btRoute.mRoute);
+        }
+        if (mActiveRoutes.contains(btRoute)) {
+            Slog.w(TAG, "addActiveRoute: btRoute is already added.");
+            return;
+        }
+        setRouteConnectionState(btRoute, STATE_CONNECTED);
+        mActiveRoutes.add(btRoute);
+    }
+
+    private void removeActiveRoute(BluetoothRouteInfo btRoute) {
+        if (DEBUG) {
+            Log.d(TAG, "Removing active route: " + btRoute.mRoute);
+        }
+        if (mActiveRoutes.remove(btRoute)) {
+            setRouteConnectionState(btRoute, STATE_DISCONNECTED);
+        }
+    }
+
+    private void clearActiveRoutesWithType(int type) {
+        if (DEBUG) {
+            Log.d(TAG, "Clearing active routes with type. type=" + type);
+        }
+        Iterator<BluetoothRouteInfo> iter = mActiveRoutes.iterator();
+        while (iter.hasNext()) {
+            BluetoothRouteInfo btRoute = iter.next();
+            if (btRoute.mRoute.getType() == type) {
+                iter.remove();
+                setRouteConnectionState(btRoute, STATE_DISCONNECTED);
+            }
+        }
+    }
+
+    private void addActiveDevices(BluetoothDevice device) {
+        // Let the given device be the first active device
+        BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress());
+        // This could happen if ACTION_ACTIVE_DEVICE_CHANGED is sent before
+        // ACTION_CONNECTION_STATE_CHANGED is sent.
+        if (activeBtRoute == null) {
+            activeBtRoute = createBluetoothRoute(device);
+            mBluetoothRoutes.put(device.getAddress(), activeBtRoute);
+        }
+        addActiveRoute(activeBtRoute);
+
+        // A bluetooth route with the same route ID should be added.
+        for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+            if (TextUtils.equals(btRoute.mRoute.getId(), activeBtRoute.mRoute.getId())
+                    && !TextUtils.equals(btRoute.mBtDevice.getAddress(),
+                    activeBtRoute.mBtDevice.getAddress())) {
+                addActiveRoute(btRoute);
+            }
+        }
+    }
+
+    private static class BluetoothRouteInfo {
+        private BluetoothDevice mBtDevice;
+        private MediaRoute2Info mRoute;
+        private SparseBooleanArray mConnectedProfiles;
+
+        @MediaRoute2Info.Type
+        int getRouteType() {
+            // Let hearing aid profile have a priority.
+            if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
+                return MediaRoute2Info.TYPE_HEARING_AID;
+            }
+
+            if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
+                return MediaRoute2Info.TYPE_BLE_HEADSET;
+            }
+
+            return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+        }
+    }
+
+    // These callbacks run on the main thread.
+    private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            List<BluetoothDevice> activeDevices;
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = (BluetoothA2dp) proxy;
+                    // It may contain null.
+                    activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP);
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = (BluetoothHearingAid) proxy;
+                    activeDevices = mBluetoothAdapter.getActiveDevices(
+                            BluetoothProfile.HEARING_AID);
+                    break;
+                case BluetoothProfile.LE_AUDIO:
+                    mLeAudioProfile = (BluetoothLeAudio) proxy;
+                    activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
+                    break;
+                default:
+                    return;
+            }
+            for (BluetoothDevice device : proxy.getConnectedDevices()) {
+                BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                }
+                if (activeDevices.contains(device)) {
+                    addActiveRoute(btRoute);
+                }
+            }
+            notifyBluetoothRoutesUpdated();
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = null;
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = null;
+                    break;
+                case BluetoothProfile.LE_AUDIO:
+                    mLeAudioProfile = null;
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+
+    private class AdapterStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+            if (state == BluetoothAdapter.STATE_OFF
+                    || state == BluetoothAdapter.STATE_TURNING_OFF) {
+                mBluetoothRoutes.clear();
+                notifyBluetoothRoutesUpdated();
+            } else if (state == BluetoothAdapter.STATE_ON) {
+                buildBluetoothRoutes();
+                if (!mBluetoothRoutes.isEmpty()) {
+                    notifyBluetoothRoutesUpdated();
+                }
+            }
+        }
+    }
+
+    private class DeviceStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            BluetoothDevice device = intent.getParcelableExtra(
+                    BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+
+            switch (intent.getAction()) {
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+                    if (device != null) {
+                        addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
+                    }
+                    notifyBluetoothRoutesUpdated();
+                    break;
+                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
+                    clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID);
+                    if (device != null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Setting active hearing aid devices. device=" + device);
+                        }
+
+                        addActiveDevices(device);
+                    }
+                    notifyBluetoothRoutesUpdated();
+                    break;
+                case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
+                    clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET);
+                    if (device != null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Setting active le audio devices. device=" + device);
+                        }
+
+                        addActiveDevices(device);
+                    }
+                    notifyBluetoothRoutesUpdated();
+                    break;
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+                    break;
+                case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device);
+                    break;
+                case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.LE_AUDIO, intent, device);
+                    break;
+            }
+        }
+
+        private void handleConnectionStateChanged(int profile, Intent intent,
+                BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (state == BluetoothProfile.STATE_CONNECTED) {
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    if (btRoute.mConnectedProfiles.size() > 0) {
+                        mBluetoothRoutes.put(device.getAddress(), btRoute);
+                        notifyBluetoothRoutesUpdated();
+                    }
+                } else {
+                    btRoute.mConnectedProfiles.put(profile, true);
+                }
+            } else if (state == BluetoothProfile.STATE_DISCONNECTING
+                    || state == BluetoothProfile.STATE_DISCONNECTED) {
+                if (btRoute != null) {
+                    btRoute.mConnectedProfiles.delete(profile);
+                    if (btRoute.mConnectedProfiles.size() == 0) {
+                        removeActiveRoute(mBluetoothRoutes.remove(device.getAddress()));
+                        notifyBluetoothRoutesUpdated();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index 08691d0..d4a1184 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -68,6 +68,17 @@
      */
     void stop();
 
+
+    /**
+     * Selects the route with the given {@code deviceAddress}.
+     *
+     * @param deviceAddress The physical address of the device to select. May be null to unselect
+     *                      the currently selected device.
+     * @return Whether the selection succeeds. If the selection fails, the state of the instance
+     * remains unaltered.
+     */
+    boolean selectRoute(@Nullable String deviceAddress);
+
     /**
      * Transfers Bluetooth output to the given route.
      *
@@ -145,6 +156,12 @@
         }
 
         @Override
+        public boolean selectRoute(String deviceAddress) {
+            // no op
+            return false;
+        }
+
+        @Override
         public void transferTo(String routeId) {
             // no op
         }
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
new file mode 100644
index 0000000..8bd6416
--- /dev/null
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 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.media;
+
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
+import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.IAudioService;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Controls device routes.
+ *
+ * <p>A device route is a system wired route, for example, built-in speaker, wired
+ * headsets and headphones, dock, hdmi, or usb devices.
+ *
+ * <p>Thread safe.
+ *
+ * @see SystemMediaRoute2Provider
+ */
+/* package */ final class DeviceRouteController {
+
+    private static final String TAG = "WiredRoutesController";
+
+    private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final AudioManager mAudioManager;
+    @NonNull
+    private final IAudioService mAudioService;
+
+    @NonNull
+    private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+    @NonNull
+    private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+
+    private int mDeviceVolume;
+    private MediaRoute2Info mDeviceRoute;
+
+    /* package */ static DeviceRouteController createInstance(@NonNull Context context,
+            @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
+        AudioManager audioManager = context.getSystemService(AudioManager.class);
+        IAudioService audioService = IAudioService.Stub.asInterface(
+                ServiceManager.getService(Context.AUDIO_SERVICE));
+
+        return new DeviceRouteController(context,
+                audioManager,
+                audioService,
+                onDeviceRouteChangedListener);
+    }
+
+    @VisibleForTesting
+    /* package */ DeviceRouteController(@NonNull Context context,
+            @NonNull AudioManager audioManager,
+            @NonNull IAudioService audioService,
+            @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(audioManager);
+        Objects.requireNonNull(audioService);
+        Objects.requireNonNull(onDeviceRouteChangedListener);
+
+        mContext = context;
+        mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
+
+        mAudioManager = audioManager;
+        mAudioService = audioService;
+
+        AudioRoutesInfo newAudioRoutes = null;
+        try {
+            newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
+        }
+
+        mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+    }
+
+    @NonNull
+    /* package */ synchronized MediaRoute2Info getDeviceRoute() {
+        return mDeviceRoute;
+    }
+
+    /* package */ synchronized boolean updateVolume(int volume) {
+        if (mDeviceVolume == volume) {
+            return false;
+        }
+
+        mDeviceVolume = volume;
+        mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
+                .setVolume(volume)
+                .build();
+
+        return true;
+    }
+
+    private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
+        int name = R.string.default_audio_route_name;
+        int type = TYPE_BUILTIN_SPEAKER;
+
+        if (newRoutes != null) {
+            if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+                type = TYPE_WIRED_HEADPHONES;
+                name = com.android.internal.R.string.default_audio_route_name_headphones;
+            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+                type = TYPE_WIRED_HEADSET;
+                name = com.android.internal.R.string.default_audio_route_name_headphones;
+            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+                type = TYPE_DOCK;
+                name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
+            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+                type = TYPE_HDMI;
+                name = com.android.internal.R.string.default_audio_route_name_external_device;
+            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+                type = TYPE_USB_DEVICE;
+                name = com.android.internal.R.string.default_audio_route_name_usb;
+            }
+        }
+
+        synchronized (this) {
+            return new MediaRoute2Info.Builder(
+                    DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
+                    .setVolumeHandling(mAudioManager.isVolumeFixed()
+                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+                    .setVolume(mDeviceVolume)
+                    .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                    .setType(type)
+                    .addFeature(FEATURE_LIVE_AUDIO)
+                    .addFeature(FEATURE_LIVE_VIDEO)
+                    .addFeature(FEATURE_LOCAL_PLAYBACK)
+                    .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
+                    .build();
+        }
+    }
+
+    private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
+        mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+    }
+
+    /* package */ interface OnDeviceRouteChangedListener {
+        void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+    }
+
+    private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
+
+        @Override
+        public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
+            MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+            synchronized (DeviceRouteController.this) {
+                mDeviceRoute = deviceRoute;
+            }
+            notifyDeviceRouteUpdate(deviceRoute);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 7979d2a..e31a7fc 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -52,7 +52,7 @@
 import java.util.Set;
 
 class LegacyBluetoothRouteController implements BluetoothRouteController {
-    private static final String TAG = "BTRouteProvider";
+    private static final String TAG = "LBtRouteProvider";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
@@ -132,6 +132,12 @@
         mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
+    @Override
+    public boolean selectRoute(String deviceAddress) {
+        // No-op as the class decides if a route is selected based on Bluetooth events.
+        return false;
+    }
+
     /**
      * Transfers to a given bluetooth route.
      * The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 76ff19f..638e81a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,25 +16,12 @@
 
 package com.android.server.media;
 
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
-import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
-import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_DOCK;
-import static android.media.MediaRoute2Info.TYPE_HDMI;
-import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.IAudioService;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
@@ -43,14 +30,11 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.Objects;
@@ -68,23 +52,21 @@
             SystemMediaRoute2Provider.class.getName());
 
     static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
-    static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
     static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
 
     private final AudioManager mAudioManager;
-    private final IAudioService mAudioService;
     private final Handler mHandler;
     private final Context mContext;
     private final UserHandle mUser;
+
+    private final DeviceRouteController mDeviceRouteController;
     private final BluetoothRouteController mBtRouteProvider;
 
     private String mSelectedRouteId;
     // For apps without MODIFYING_AUDIO_ROUTING permission.
     // This should be the currently selected route.
     MediaRoute2Info mDefaultRoute;
-    MediaRoute2Info mDeviceRoute;
     RoutingSessionInfo mDefaultSessionInfo;
-    int mDeviceVolume;
 
     private final AudioManagerBroadcastReceiver mAudioReceiver =
             new AudioManagerBroadcastReceiver();
@@ -93,19 +75,6 @@
     @GuardedBy("mRequestLock")
     private volatile SessionCreationRequest mPendingSessionCreationRequest;
 
-    final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
-        @Override
-        public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
-            mHandler.post(() -> {
-                updateDeviceRoute(newRoutes);
-                notifyProviderState();
-                if (updateSessionInfosIfNeeded()) {
-                    notifySessionInfoUpdated();
-                }
-            });
-        }
-    };
-
     SystemMediaRoute2Provider(Context context, UserHandle user) {
         super(COMPONENT_NAME);
         mIsSystemRouteProvider = true;
@@ -114,8 +83,6 @@
         mHandler = new Handler(Looper.getMainLooper());
 
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        mAudioService = IAudioService.Stub.asInterface(
-                ServiceManager.getService(Context.AUDIO_SERVICE));
 
         mBtRouteProvider = BluetoothRouteController.createInstance(context, (routes) -> {
             publishProviderState();
@@ -124,15 +91,18 @@
             }
         });
 
-        AudioRoutesInfo newAudioRoutes = null;
-        try {
-            newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
-        } catch (RemoteException e) {
-        }
+        mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
+            mHandler.post(() -> {
+                publishProviderState();
+                if (updateSessionInfosIfNeeded()) {
+                    notifySessionInfoUpdated();
+                }
+            });
+        });
 
-        // The methods below should be called after all fields are initialized, as they
+        // These methods below should be called after all fields are initialized, as they
         // access the fields inside.
-        updateDeviceRoute(newAudioRoutes);
+        updateProviderState();
         updateSessionInfosIfNeeded();
     }
 
@@ -216,7 +186,9 @@
             // The currently selected route is the default route.
             return;
         }
-        if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
+
+        MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+        if (TextUtils.equals(routeId, deviceRoute.getId())) {
             mBtRouteProvider.transferTo(null);
         } else {
             mBtRouteProvider.transferTo(routeId);
@@ -254,9 +226,12 @@
             if (mSessionInfos.isEmpty()) {
                 return null;
             }
+
+            MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+
             RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
                     SYSTEM_SESSION_ID, packageName).setSystemSession(true);
-            builder.addSelectedRoute(mDeviceRoute.getId());
+            builder.addSelectedRoute(deviceRoute.getId());
             for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
                 builder.addTransferableRoute(route.getId());
             }
@@ -264,47 +239,12 @@
         }
     }
 
-    private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
-        int name = R.string.default_audio_route_name;
-        int type = TYPE_BUILTIN_SPEAKER;
-        if (newRoutes != null) {
-            if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
-                type = TYPE_WIRED_HEADPHONES;
-                name = com.android.internal.R.string.default_audio_route_name_headphones;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
-                type = TYPE_WIRED_HEADSET;
-                name = com.android.internal.R.string.default_audio_route_name_headphones;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
-                type = TYPE_DOCK;
-                name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
-                type = TYPE_HDMI;
-                name = com.android.internal.R.string.default_audio_route_name_external_device;
-            } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
-                type = TYPE_USB_DEVICE;
-                name = com.android.internal.R.string.default_audio_route_name_usb;
-            }
-        }
-
-        mDeviceRoute = new MediaRoute2Info.Builder(
-                DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
-                .setVolumeHandling(mAudioManager.isVolumeFixed()
-                        ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
-                        : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
-                .setVolume(mDeviceVolume)
-                .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
-                .setType(type)
-                .addFeature(FEATURE_LIVE_AUDIO)
-                .addFeature(FEATURE_LIVE_VIDEO)
-                .addFeature(FEATURE_LOCAL_PLAYBACK)
-                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
-                .build();
-        updateProviderState();
-    }
-
     private void updateProviderState() {
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
-        builder.addRoute(mDeviceRoute);
+
+        // We must have a device route in the provider info.
+        builder.addRoute(mDeviceRouteController.getDeviceRoute());
+
         for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) {
             builder.addRoute(route);
         }
@@ -327,11 +267,12 @@
                     SYSTEM_SESSION_ID, "" /* clientPackageName */)
                     .setSystemSession(true);
 
-            MediaRoute2Info selectedRoute = mDeviceRoute;
+            MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute();
+            MediaRoute2Info selectedRoute = deviceRoute;
             MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
             if (selectedBtRoute != null) {
                 selectedRoute = selectedBtRoute;
-                builder.addTransferableRoute(mDeviceRoute.getId());
+                builder.addTransferableRoute(deviceRoute.getId());
             }
             mSelectedRouteId = selectedRoute.getId();
             mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
@@ -423,12 +364,9 @@
         if (mBtRouteProvider.updateVolumeForDevices(devices, volume)) {
             return;
         }
-        if (mDeviceVolume != volume) {
-            mDeviceVolume = volume;
-            mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
-                    .setVolume(volume)
-                    .build();
-        }
+
+        mDeviceRouteController.updateVolume(volume);
+
         publishProviderState();
     }
 
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index 41c2fbf..6c32ec2 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -17,6 +17,8 @@
 package com.android.server.sensors;
 
 import android.annotation.NonNull;
+import android.hardware.SensorDirectChannel;
+import android.os.ParcelFileDescriptor;
 
 import java.util.concurrent.Executor;
 
@@ -58,7 +60,7 @@
      * @return The sensor handle.
      */
     public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
-            @NonNull String vendor, @NonNull RuntimeSensorCallback callback);
+            @NonNull String vendor, int flags, @NonNull RuntimeSensorCallback callback);
 
     /**
      * Unregisters the sensor with the given handle from the framework.
@@ -98,9 +100,31 @@
     public interface RuntimeSensorCallback {
         /**
          * Invoked when the listeners of the runtime sensor have changed.
-         * Returns an error code if the invocation was unsuccessful, zero otherwise.
+         * Returns zero on success, negative error code otherwise.
          */
         int onConfigurationChanged(int handle, boolean enabled, int samplingPeriodMicros,
                 int batchReportLatencyMicros);
+
+        /**
+         * Invoked when a direct sensor channel has been created.
+         * Wraps the file descriptor in a {@link android.os.SharedMemory} object and passes it to
+         * the client process.
+         * Returns a positive identifier of the channel on success, negative error code otherwise.
+         */
+        int onDirectChannelCreated(ParcelFileDescriptor fd);
+
+        /**
+         * Invoked when a direct sensor channel has been destroyed.
+         */
+        void onDirectChannelDestroyed(int channelHandle);
+
+        /**
+         * Invoked when a direct sensor channel has been configured for a sensor.
+         * If the invocation is unsuccessful, a negative error code is returned.
+         * On success, the return value is zero if the rate level is {@code RATE_STOP}, and a
+         * positive report token otherwise.
+         */
+        int onDirectChannelConfigured(int channelHandle, int sensorHandle,
+                @SensorDirectChannel.RateLevel int rateLevel);
     }
 }
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 9790659..1baa0a6 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -56,7 +56,8 @@
     private static native void unregisterProximityActiveListenerNative(long ptr);
 
     private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
-            String name, String vendor, SensorManagerInternal.RuntimeSensorCallback callback);
+            String name, String vendor, int flags,
+            SensorManagerInternal.RuntimeSensorCallback callback);
     private static native void unregisterRuntimeSensorNative(long ptr, int handle);
     private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
             long timestampNanos, float[] values);
@@ -95,9 +96,9 @@
     class LocalService extends SensorManagerInternal {
         @Override
         public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
-                @NonNull String vendor, @NonNull RuntimeSensorCallback callback) {
+                @NonNull String vendor, int flags, @NonNull RuntimeSensorCallback callback) {
             synchronized (mLock) {
-                int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+                int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor, flags,
                         callback);
                 mRuntimeSensorHandles.add(handle);
                 return handle;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e2bdcdd..2ce86ad 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -179,6 +179,7 @@
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
+        "android.hardware.thermal-V1-ndk",
         "android.hardware.tv.input@1.0",
         "android.hardware.tv.input-V1-ndk",
         "android.hardware.vibrator-V2-cpp",
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index ed79352..7e0bb68 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -16,27 +16,28 @@
 
 #define LOG_TAG "HardwarePropertiesManagerService-JNI"
 
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
-
-#include <math.h>
-#include <stdlib.h>
-
+#include <aidl/android/hardware/thermal/IThermal.h>
+#include <android/binder_manager.h>
 #include <android/hardware/thermal/1.0/IThermal.h>
+#include <math.h>
+#include <nativehelper/JNIHelp.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 
 #include "core_jni_helpers.h"
+#include "jni.h"
 
 namespace android {
 
+using ::aidl::android::hardware::thermal::CoolingDevice;
+using ::aidl::android::hardware::thermal::IThermal;
+using ::aidl::android::hardware::thermal::Temperature;
+using ::aidl::android::hardware::thermal::TemperatureThreshold;
+using ::aidl::android::hardware::thermal::TemperatureType;
+using ::aidl::android::hardware::thermal::ThrottlingSeverity;
 using android::hidl::base::V1_0::IBase;
 using hardware::hidl_death_recipient;
 using hardware::hidl_vec;
-using hardware::thermal::V1_0::CoolingDevice;
-using hardware::thermal::V1_0::CpuUsage;
-using hardware::thermal::V1_0::IThermal;
-using hardware::thermal::V1_0::Temperature;
 using hardware::thermal::V1_0::ThermalStatus;
 using hardware::thermal::V1_0::ThermalStatusCode;
 template<typename T>
@@ -62,20 +63,28 @@
 
 static void getThermalHalLocked();
 static std::mutex gThermalHalMutex;
-static sp<IThermal> gThermalHal = nullptr;
+static sp<hardware::thermal::V1_0::IThermal> gThermalHidlHal = nullptr;
+static std::shared_ptr<IThermal> gThermalAidlHal = nullptr;
 
-// struct ThermalHalDeathRecipient;
-struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
-      // hidl_death_recipient interface
-      virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
-          std::lock_guard<std::mutex> lock(gThermalHalMutex);
-          ALOGE("ThermalHAL just died");
-          gThermalHal = nullptr;
-          getThermalHalLocked();
-      }
+struct ThermalHidlHalDeathRecipient : virtual public hidl_death_recipient {
+    // hidl_death_recipient interface
+    virtual void serviceDied(uint64_t cookie, const wp<IBase> &who) override {
+        std::lock_guard<std::mutex> lock(gThermalHalMutex);
+        ALOGE("Thermal HAL just died");
+        gThermalHidlHal = nullptr;
+        getThermalHalLocked();
+    }
 };
 
-sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
+static void onThermalAidlBinderDied(void *cookie) {
+    std::lock_guard<std::mutex> lock(gThermalHalMutex);
+    ALOGE("Thermal AIDL HAL just died");
+    gThermalAidlHal = nullptr;
+    getThermalHalLocked();
+}
+
+sp<ThermalHidlHalDeathRecipient> gThermalHidlHalDeathRecipient = nullptr;
+ndk::ScopedAIBinder_DeathRecipient gThermalAidlDeathRecipient;
 
 // ----------------------------------------------------------------------------
 
@@ -85,27 +94,49 @@
 
 // The caller must be holding gThermalHalMutex.
 static void getThermalHalLocked() {
-    if (gThermalHal != nullptr) {
+    if (gThermalAidlHal || gThermalHidlHal) {
+        return;
+    }
+    const std::string thermalInstanceName = std::string(IThermal::descriptor) + "/default";
+    if (AServiceManager_isDeclared(thermalInstanceName.c_str())) {
+        auto binder = AServiceManager_waitForService(thermalInstanceName.c_str());
+        auto thermalAidlService = IThermal::fromBinder(ndk::SpAIBinder(binder));
+        if (thermalAidlService) {
+            gThermalAidlHal = thermalAidlService;
+            if (gThermalAidlDeathRecipient.get() == nullptr) {
+                gThermalAidlDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(
+                        AIBinder_DeathRecipient_new(onThermalAidlBinderDied));
+            }
+            auto linked = AIBinder_linkToDeath(thermalAidlService->asBinder().get(),
+                                               gThermalAidlDeathRecipient.get(), nullptr);
+            if (linked != STATUS_OK) {
+                ALOGW("Failed to link to death (AIDL): %d", linked);
+                gThermalAidlHal = nullptr;
+            }
+        } else {
+            ALOGE("Unable to get Thermal AIDL service");
+        }
         return;
     }
 
-    gThermalHal = IThermal::getService();
+    ALOGI("Thermal AIDL service is not declared, trying HIDL");
+    gThermalHidlHal = hardware::thermal::V1_0::IThermal::getService();
 
-    if (gThermalHal == nullptr) {
+    if (gThermalHidlHal == nullptr) {
         ALOGE("Unable to get Thermal service.");
     } else {
-        if (gThermalHalDeathRecipient == nullptr) {
-            gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
+        if (gThermalHidlHalDeathRecipient == nullptr) {
+            gThermalHidlHalDeathRecipient = new ThermalHidlHalDeathRecipient();
         }
-        hardware::Return<bool> linked = gThermalHal->linkToDeath(
-            gThermalHalDeathRecipient, 0x451F /* cookie */);
+        hardware::Return<bool> linked =
+                gThermalHidlHal->linkToDeath(gThermalHidlHalDeathRecipient, 0x451F /* cookie */);
         if (!linked.isOk()) {
             ALOGE("Transaction error in linking to ThermalHAL death: %s",
-            linked.description().c_str());
-            gThermalHal = nullptr;
+                  linked.description().c_str());
+            gThermalHidlHal = nullptr;
         } else if (!linked) {
             ALOGW("Unable to link to ThermalHal death notifications");
-            gThermalHal = nullptr;
+            gThermalHidlHal = nullptr;
         } else {
             ALOGD("Link to death notification successful");
         }
@@ -117,17 +148,27 @@
     getThermalHalLocked();
 }
 
-static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) {
-    std::lock_guard<std::mutex> lock(gThermalHalMutex);
-    getThermalHalLocked();
-    if (gThermalHal == nullptr) {
-        ALOGE("Couldn't get fan speeds because of HAL error.");
+static jfloatArray getFanSpeedsAidl(JNIEnv *env) {
+    std::vector<CoolingDevice> list;
+    auto status = gThermalAidlHal->getCoolingDevices(&list);
+    if (!status.isOk()) {
+        ALOGE("getFanSpeeds failed status: %s", status.getMessage());
         return env->NewFloatArray(0);
     }
+    float values[list.size()];
+    for (size_t i = 0; i < list.size(); ++i) {
+        values[i] = list[i].value;
+    }
+    jfloatArray fanSpeeds = env->NewFloatArray(list.size());
+    env->SetFloatArrayRegion(fanSpeeds, 0, list.size(), values);
+    return fanSpeeds;
+}
 
-    hidl_vec<CoolingDevice> list;
-    Return<void> ret = gThermalHal->getCoolingDevices(
-            [&list](ThermalStatus status, hidl_vec<CoolingDevice> devices) {
+static jfloatArray getFanSpeedsHidl(JNIEnv *env) {
+    hidl_vec<hardware::thermal::V1_0::CoolingDevice> list;
+    Return<void> ret = gThermalHidlHal->getCoolingDevices(
+            [&list](ThermalStatus status,
+                    hidl_vec<hardware::thermal::V1_0::CoolingDevice> devices) {
                 if (status.code == ThermalStatusCode::SUCCESS) {
                     list = std::move(devices);
                 } else {
@@ -137,9 +178,9 @@
             });
 
     if (!ret.isOk()) {
-        ALOGE("getCoolingDevices failed status: %s", ret.description().c_str());
+        ALOGE("getFanSpeeds failed status: %s", ret.description().c_str());
+        return env->NewFloatArray(0);
     }
-
     float values[list.size()];
     for (size_t i = 0; i < list.size(); ++i) {
         values[i] = list[i].currentValue;
@@ -149,17 +190,79 @@
     return fanSpeeds;
 }
 
-static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type,
-                                               int source) {
+static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) {
     std::lock_guard<std::mutex> lock(gThermalHalMutex);
     getThermalHalLocked();
-    if (gThermalHal == nullptr) {
-        ALOGE("Couldn't get device temperatures because of HAL error.");
+    if (!gThermalHidlHal && !gThermalAidlHal) {
+        ALOGE("Couldn't get fan speeds because of HAL error.");
         return env->NewFloatArray(0);
     }
-    hidl_vec<Temperature> list;
-    Return<void> ret = gThermalHal->getTemperatures(
-            [&list](ThermalStatus status, hidl_vec<Temperature> temperatures) {
+    if (gThermalAidlHal) {
+        return getFanSpeedsAidl(env);
+    }
+    return getFanSpeedsHidl(env);
+}
+
+static jfloatArray getDeviceTemperaturesAidl(JNIEnv *env, int type, int source) {
+    jfloat *values;
+    size_t length = 0;
+    if (source == TEMPERATURE_CURRENT) {
+        std::vector<Temperature> list;
+        auto status =
+                gThermalAidlHal->getTemperaturesWithType(static_cast<TemperatureType>(type), &list);
+
+        if (!status.isOk()) {
+            ALOGE("getDeviceTemperatures failed status: %s", status.getMessage());
+            return env->NewFloatArray(0);
+        }
+        values = new jfloat[list.size()];
+        for (const auto &temp : list) {
+            if (static_cast<int>(temp.type) == type) {
+                values[length++] = finalizeTemperature(temp.value);
+            }
+        }
+    } else if (source == TEMPERATURE_THROTTLING_BELOW_VR_MIN) {
+        values = new jfloat[1];
+        values[length++] = gUndefinedTemperature;
+    } else {
+        std::vector<TemperatureThreshold> list;
+        auto status =
+                gThermalAidlHal->getTemperatureThresholdsWithType(static_cast<TemperatureType>(
+                                                                          type),
+                                                                  &list);
+
+        if (!status.isOk()) {
+            ALOGE("getDeviceTemperatures failed status: %s", status.getMessage());
+            return env->NewFloatArray(0);
+        }
+        values = new jfloat[list.size()];
+        for (auto &t : list) {
+            if (static_cast<int>(t.type) == type) {
+                switch (source) {
+                    case TEMPERATURE_THROTTLING:
+                        values[length++] =
+                                finalizeTemperature(t.hotThrottlingThresholds[static_cast<int>(
+                                        ThrottlingSeverity::SEVERE)]);
+                        break;
+                    case TEMPERATURE_SHUTDOWN:
+                        values[length++] =
+                                finalizeTemperature(t.hotThrottlingThresholds[static_cast<int>(
+                                        ThrottlingSeverity::SHUTDOWN)]);
+                        break;
+                }
+            }
+        }
+    }
+    jfloatArray deviceTemps = env->NewFloatArray(length);
+    env->SetFloatArrayRegion(deviceTemps, 0, length, values);
+    return deviceTemps;
+}
+
+static jfloatArray getDeviceTemperaturesHidl(JNIEnv *env, int type, int source) {
+    hidl_vec<hardware::thermal::V1_0::Temperature> list;
+    Return<void> ret = gThermalHidlHal->getTemperatures(
+            [&list](ThermalStatus status,
+                    hidl_vec<hardware::thermal::V1_0::Temperature> temperatures) {
                 if (status.code == ThermalStatusCode::SUCCESS) {
                     list = std::move(temperatures);
                 } else {
@@ -170,9 +273,9 @@
 
     if (!ret.isOk()) {
         ALOGE("getDeviceTemperatures failed status: %s", ret.description().c_str());
+        return env->NewFloatArray(0);
     }
-
-    jfloat values[list.size()];
+    float values[list.size()];
     size_t length = 0;
     for (size_t i = 0; i < list.size(); ++i) {
         if (static_cast<int>(list[i].type) == type) {
@@ -197,16 +300,34 @@
     return deviceTemps;
 }
 
+static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type,
+                                               int source) {
+    std::lock_guard<std::mutex> lock(gThermalHalMutex);
+    getThermalHalLocked();
+    if (!gThermalHidlHal && !gThermalAidlHal) {
+        ALOGE("Couldn't get device temperatures because of HAL error.");
+        return env->NewFloatArray(0);
+    }
+    if (gThermalAidlHal) {
+        return getDeviceTemperaturesAidl(env, type, source);
+    }
+    return getDeviceTemperaturesHidl(env, type, source);
+}
+
 static jobjectArray nativeGetCpuUsages(JNIEnv *env, jclass /* clazz */) {
     std::lock_guard<std::mutex> lock(gThermalHalMutex);
     getThermalHalLocked();
-    if (gThermalHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) {
+    if (gThermalAidlHal) {
+        ALOGW("getCpuUsages is not supported");
+        return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
+    }
+    if (gThermalHidlHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) {
         ALOGE("Couldn't get CPU usages because of HAL error.");
         return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
     }
-    hidl_vec<CpuUsage> list;
-    Return<void> ret = gThermalHal->getCpuUsages(
-            [&list](ThermalStatus status, hidl_vec<CpuUsage> cpuUsages) {
+    hidl_vec<hardware::thermal::V1_0::CpuUsage> list;
+    Return<void> ret = gThermalHidlHal->getCpuUsages(
+            [&list](ThermalStatus status, hidl_vec<hardware::thermal::V1_0::CpuUsage> cpuUsages) {
                 if (status.code == ThermalStatusCode::SUCCESS) {
                     list = std::move(cpuUsages);
                 } else {
@@ -217,6 +338,7 @@
 
     if (!ret.isOk()) {
         ALOGE("getCpuUsages failed status: %s", ret.description().c_str());
+        return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
     }
 
     jobjectArray cpuUsages = env->NewObjectArray(list.size(), gCpuUsageInfoClassInfo.clazz,
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 356e9a9..a916b64 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -17,10 +17,13 @@
 #define LOG_TAG "NativeSensorService"
 
 #include <android-base/properties.h>
+#include <android_os_NativeHandle.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <core_jni_helpers.h>
+#include <cutils/native_handle.h>
 #include <cutils/properties.h>
 #include <jni.h>
+#include <nativehelper/JNIPlatformHelp.h>
 #include <sensorservice/SensorService.h>
 #include <string.h>
 #include <utils/Log.h>
@@ -28,6 +31,8 @@
 
 #include <mutex>
 
+#include "android_util_Binder.h"
+
 #define PROXIMITY_ACTIVE_CLASS \
     "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
 
@@ -38,7 +43,10 @@
 
 static JavaVM* sJvm = nullptr;
 static jmethodID sMethodIdOnProximityActive;
-static jmethodID sMethodIdOnConfigurationChanged;
+static jmethodID sMethodIdRuntimeSensorOnConfigurationChanged;
+static jmethodID sMethodIdRuntimeSensorOnDirectChannelCreated;
+static jmethodID sMethodIdRuntimeSensorOnDirectChannelDestroyed;
+static jmethodID sMethodIdRuntimeSensorOnDirectChannelConfigured;
 
 class NativeSensorService {
 public:
@@ -47,7 +55,7 @@
     void registerProximityActiveListener();
     void unregisterProximityActiveListener();
     jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
-                               jobject callback);
+                               jint flags, jobject callback);
     void unregisterRuntimeSensor(jint handle);
     jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
                                     jfloatArray values);
@@ -74,6 +82,9 @@
 
         status_t onConfigurationChanged(int32_t handle, bool enabled, int64_t samplingPeriodNs,
                                         int64_t batchReportLatencyNs) override;
+        int onDirectChannelCreated(int fd) override;
+        void onDirectChannelDestroyed(int channelHandle) override;
+        int onDirectChannelConfigured(int channelHandle, int sensorHandle, int rateLevel) override;
 
     private:
         jobject mCallback;
@@ -108,7 +119,7 @@
 }
 
 jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
-                                                jstring vendor, jobject callback) {
+                                                jstring vendor, jint flags, jobject callback) {
     if (mService == nullptr) {
         ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
         return -1;
@@ -119,6 +130,11 @@
             .vendor = env->GetStringUTFChars(vendor, 0),
             .version = sizeof(sensor_t),
             .type = type,
+#ifdef __LP64__
+            .flags = static_cast<uint64_t>(flags),
+#else
+            .flags = static_cast<uint32_t>(flags),
+#endif
     };
 
     sp<RuntimeSensorCallbackDelegate> callbackDelegate(
@@ -234,12 +250,39 @@
 status_t NativeSensorService::RuntimeSensorCallbackDelegate::onConfigurationChanged(
         int32_t handle, bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
     auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
-    return jniEnv->CallIntMethod(mCallback, sMethodIdOnConfigurationChanged,
+    return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnConfigurationChanged,
                                  static_cast<jint>(handle), static_cast<jboolean>(enabled),
                                  static_cast<jint>(ns2us(samplingPeriodNs)),
                                  static_cast<jint>(ns2us(batchReportLatencyNs)));
 }
 
+int NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelCreated(int fd) {
+    if (fd <= 0) {
+        return 0;
+    }
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    jobject jfd = jniCreateFileDescriptor(jniEnv, fd);
+    jobject parcelFileDescriptor = newParcelFileDescriptor(jniEnv, jfd);
+    return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelCreated,
+                                 parcelFileDescriptor);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelDestroyed(
+        int channelHandle) {
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    return jniEnv->CallVoidMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelDestroyed,
+                                  static_cast<jint>(channelHandle));
+}
+
+int NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelConfigured(int channelHandle,
+                                                                                  int sensorHandle,
+                                                                                  int rateLevel) {
+    auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+    return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelConfigured,
+                                 static_cast<jint>(channelHandle), static_cast<jint>(sensorHandle),
+                                 static_cast<jint>(rateLevel));
+}
+
 static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
     NativeSensorService* service = new NativeSensorService(env, listener);
     return reinterpret_cast<jlong>(service);
@@ -256,9 +299,10 @@
 }
 
 static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
-                                        jstring name, jstring vendor, jobject callback) {
+                                        jstring name, jstring vendor, jint flags,
+                                        jobject callback) {
     auto* service = reinterpret_cast<NativeSensorService*>(ptr);
-    return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+    return service->registerRuntimeSensor(env, deviceId, type, name, vendor, flags, callback);
 }
 
 static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
@@ -280,7 +324,7 @@
         {"unregisterProximityActiveListenerNative", "(J)V",
          reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
         {"registerRuntimeSensorNative",
-         "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+         "(JIILjava/lang/String;Ljava/lang/String;IL" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
          reinterpret_cast<void*>(registerRuntimeSensorNative)},
         {"unregisterRuntimeSensorNative", "(JI)V",
          reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
@@ -293,8 +337,17 @@
     jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
     sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
     jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
-    sMethodIdOnConfigurationChanged =
+    sMethodIdRuntimeSensorOnConfigurationChanged =
             GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onConfigurationChanged", "(IZII)I");
+    sMethodIdRuntimeSensorOnDirectChannelCreated =
+            GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelCreated",
+                             "(Landroid/os/ParcelFileDescriptor;)I");
+    sMethodIdRuntimeSensorOnDirectChannelDestroyed =
+            GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelDestroyed", "(I)V");
+    sMethodIdRuntimeSensorOnDirectChannelConfigured =
+            GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelConfigured",
+                             "(III)I");
+
     return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
                                     NELEM(methods));
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8c94b0a..6498b6a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -415,25 +415,8 @@
     /** Returns true if either an exception or a response is found. */
     private void onActionEntrySelected(ProviderPendingIntentResponse
             providerPendingIntentResponse) {
-        // Action entry is expected to either contain the final GetCredentialResponse, or it is
-        // also acceptable if it does not contain anything. In the second case, we re-show this
-        // action on the UI.
-        if (providerPendingIntentResponse == null) {
-            Log.i(TAG, "providerPendingIntentResponse is null");
-            return;
-        }
-
-        GetCredentialException exception = maybeGetPendingIntentException(
-                providerPendingIntentResponse);
-        if (exception != null) {
-            invokeCallbackWithError(exception.getType(), exception.getMessage());
-        }
-        GetCredentialResponse response = PendingIntentResultHandler
-                .extractGetCredentialResponse(
-                        providerPendingIntentResponse.getResultData());
-        if (response != null) {
-            mCallbacks.onFinalResponseReceived(mComponentName, response);
-        }
+        Log.i(TAG, "onActionEntrySelected");
+        onCredentialEntrySelected(providerPendingIntentResponse);
     }
 
 
@@ -450,11 +433,11 @@
 
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
-     * we send back a TYPE_UNKNOWN error as to the developer.
+     * we send back a TYPE_NO_CREDENTIAL error as to the developer.
      */
     private void invokeCallbackOnInternalInvalidState() {
         mCallbacks.onFinalErrorReceived(mComponentName,
-                GetCredentialException.TYPE_UNKNOWN, null);
+                GetCredentialException.TYPE_NO_CREDENTIAL, null);
     }
 
     /** Update auth entries status based on an auth entry selected from a different session. */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 95c2ed2..99da415 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -759,40 +759,6 @@
     }
 
     /**
-     * Verify that sending a broadcast that removes any matching pending
-     * broadcasts is applied as expected.
-     */
-    @Test
-    public void testRemoveMatchingFilter() {
-        final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
-        final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
-        optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
-
-        final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
-        final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
-        optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
-
-        // Halt all processing so that we get a consistent view
-        mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
-        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
-
-        // While we're here, give our health check some test coverage
-        mImpl.checkHealthLocked();
-
-        // Marching through the queue we should only have one SCREEN_OFF
-        // broadcast, since that's the last state we dispatched
-        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
-                getUidForPackage(PACKAGE_GREEN));
-        queue.makeActiveNextPending();
-        assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
-        assertTrue(queue.isEmpty());
-    }
-
-    /**
      * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected.
      */
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 95a5884..5f82ec1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -53,6 +53,7 @@
 import com.android.internal.R;
 import com.android.server.LocalServices;
 import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index 6431e88..1259d71 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -81,7 +81,7 @@
     @Test
     public void createSensor_invalidHandle_throwsException() {
         doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
-                anyInt(), anyInt(), anyString(), anyString(), any());
+                anyInt(), anyInt(), anyString(), anyString(), anyInt(), any());
 
         Throwable thrown = assertThrows(
                 RuntimeException.class,
@@ -138,7 +138,7 @@
 
     private void doCreateSensorSuccessfully() {
         doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
-                anyInt(), anyInt(), anyString(), anyString(), any());
+                anyInt(), anyInt(), anyString(), anyString(), anyInt(), any());
         assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig))
                 .isEqualTo(SENSOR_HANDLE);
     }
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 9910a80..8f4c81f 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
@@ -500,7 +500,7 @@
                 .build();
 
         doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
-                anyInt(), anyInt(), anyString(), anyString(), any());
+                anyInt(), anyInt(), anyString(), anyString(), anyInt(), any());
         mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
 
         VirtualSensor sensor = mLocalService.getVirtualSensor(VIRTUAL_DEVICE_ID_1, SENSOR_HANDLE);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 800f60b..ffe2fec 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -43,6 +43,7 @@
 import com.android.server.display.BrightnessThrottler.Injector;
 import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.mode.DisplayModeDirectorTest;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 6def7b1..8981160 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -176,21 +176,18 @@
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Update brightness config to enabled color sampling.
-        mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
-                /* collectColorSamples= */ true));
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
         mInjector.waitForHandler();
         assertTrue(mInjector.mColorSamplingEnabled);
 
         // Update brightness config to disable color sampling.
-        mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
-                /* collectColorSamples= */ false));
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false);
         mInjector.waitForHandler();
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Pretend screen is off, update config to turn on color sampling.
         mInjector.sendScreenChange(/* screenOn= */ false);
-        mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
-                /* collectColorSamples= */ true));
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
         mInjector.waitForHandler();
         assertFalse(mInjector.mColorSamplingEnabled);
 
@@ -883,7 +880,7 @@
     private void startTracker(BrightnessTracker tracker, float initialBrightness,
             boolean collectColorSamples) {
         tracker.start(initialBrightness);
-        tracker.setBrightnessConfiguration(buildBrightnessConfiguration(collectColorSamples));
+        tracker.setShouldCollectColorSample(collectColorSamples);
         mInjector.waitForHandler();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
rename to services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 7e6eeee..f256c8a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.display;
+package com.android.server.display.mode;
 
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
@@ -27,8 +27,7 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
-import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE;
-import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -88,9 +87,11 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
-import com.android.server.display.DisplayModeDirector.BrightnessObserver;
-import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
-import com.android.server.display.DisplayModeDirector.Vote;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.TestUtils;
+import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
+import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
+import com.android.server.display.mode.DisplayModeDirector.Vote;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -126,6 +127,8 @@
     private static final int DISPLAY_ID = 0;
     private static final float TRANSITION_POINT = 0.763f;
 
+    private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
     private Context mContext;
     private FakesInjector mInjector;
     private Handler mHandler;
@@ -2234,7 +2237,7 @@
                 ArgumentCaptor.forClass(IThermalEventListener.class);
 
         verify(mThermalServiceMock).registerThermalEventListenerWithType(
-            thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
+                thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
         final IThermalEventListener listener = thermalEventListener.getValue();
 
         // Verify that there is no skin temperature vote initially.
@@ -2548,7 +2551,7 @@
                     KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
         }
 
-        void setBrightnessThrottlingData(String brightnessThrottlingData) {
+        public void setBrightnessThrottlingData(String brightnessThrottlingData) {
             putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                     KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
         }
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
new file mode 100644
index 0000000..24ed42c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 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.media;
+
+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.Mockito.when;
+
+import android.annotation.IdRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.server.audio.AudioService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Enclosed.class)
+public class DeviceRouteControllerTest {
+
+    private static final String DEFAULT_ROUTE_NAME = "default_route";
+    private static final String DEFAULT_HEADPHONES_NAME = "headphone";
+    private static final String DEFAULT_HEADSET_NAME = "headset";
+    private static final String DEFAULT_DOCK_NAME = "dock";
+    private static final String DEFAULT_HDMI_NAME = "hdmi";
+    private static final String DEFAULT_USB_NAME = "usb";
+    private static final int VOLUME_DEFAULT_VALUE = 0;
+    private static final int VOLUME_VALUE_SAMPLE_1 = 10;
+
+    private static AudioRoutesInfo createFakeBluetoothAudioRoute() {
+        AudioRoutesInfo btRouteInfo = new AudioRoutesInfo();
+        btRouteInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
+        btRouteInfo.bluetoothName = "bt_device";
+        return btRouteInfo;
+    }
+
+    @RunWith(JUnit4.class)
+    public static class DefaultDeviceRouteValueTest {
+        @Mock
+        private Context mContext;
+        @Mock
+        private Resources mResources;
+        @Mock
+        private AudioManager mAudioManager;
+        @Mock
+        private AudioService mAudioService;
+        @Mock
+        private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+        @Before
+        public void setUp() {
+            MockitoAnnotations.initMocks(this);
+
+            when(mContext.getResources()).thenReturn(mResources);
+        }
+
+        @Test
+        public void initialize_noRoutesInfo_defaultRouteIsNotNull() {
+            // Mocking default_audio_route_name.
+            when(mResources.getText(R.string.default_audio_route_name))
+                    .thenReturn(DEFAULT_ROUTE_NAME);
+
+            // Default route should be initialized even when AudioService returns null.
+            when(mAudioService.startWatchingRoutes(any())).thenReturn(null);
+
+            DeviceRouteController deviceRouteController = new DeviceRouteController(
+                    mContext,
+                    mAudioManager,
+                    mAudioService,
+                    mOnDeviceRouteChangedListener
+            );
+
+            MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+
+            assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+            assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
+                    .isTrue();
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+        }
+
+        @Test
+        public void initialize_bluetoothRouteAvailable_deviceRouteReturnsDefaultRoute() {
+            // Mocking default_audio_route_name.
+            when(mResources.getText(R.string.default_audio_route_name))
+                    .thenReturn(DEFAULT_ROUTE_NAME);
+
+            // This route should be ignored.
+            AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
+            when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute);
+
+            DeviceRouteController deviceRouteController = new DeviceRouteController(
+                    mContext,
+                    mAudioManager,
+                    mAudioService,
+                    mOnDeviceRouteChangedListener
+            );
+
+            MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+
+            assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+            assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
+                    .isTrue();
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+        }
+    }
+
+    @RunWith(Parameterized.class)
+    public static class DeviceRouteInitializationTest {
+
+        @Parameterized.Parameters
+        public static Collection<Object[]> data() {
+            return Arrays.asList(new Object[][] {
+                    {     /* expected res */
+                          com.android.internal.R.string.default_audio_route_name_headphones,
+                          /* expected name */
+                          DEFAULT_HEADPHONES_NAME,
+                          /* expected type */
+                          MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+                          /* actual audio route type */
+                          AudioRoutesInfo.MAIN_HEADPHONES },
+                    {   /* expected res */
+                        com.android.internal.R.string.default_audio_route_name_headphones,
+                        /* expected name */
+                        DEFAULT_HEADSET_NAME,
+                        /* expected type */
+                        MediaRoute2Info.TYPE_WIRED_HEADSET,
+                        /* actual audio route type */
+                        AudioRoutesInfo.MAIN_HEADSET },
+                    {    /* expected res */
+                        R.string.default_audio_route_name_dock_speakers,
+                        /* expected name */
+                        DEFAULT_DOCK_NAME,
+                        /* expected type */
+                        MediaRoute2Info.TYPE_DOCK,
+                        /* actual audio route type */
+                        AudioRoutesInfo.MAIN_DOCK_SPEAKERS },
+                    {   /* expected res */
+                        R.string.default_audio_route_name_external_device,
+                        /* expected name */
+                        DEFAULT_HDMI_NAME,
+                        /* expected type */
+                        MediaRoute2Info.TYPE_HDMI,
+                        /* actual audio route type */
+                        AudioRoutesInfo.MAIN_HDMI },
+                    {   /* expected res */
+                        R.string.default_audio_route_name_usb,
+                        /* expected name */
+                        DEFAULT_USB_NAME,
+                        /* expected type */
+                        MediaRoute2Info.TYPE_USB_DEVICE,
+                        /* actual audio route type */
+                        AudioRoutesInfo.MAIN_USB }
+            });
+        }
+
+        @Mock
+        private Context mContext;
+        @Mock
+        private Resources mResources;
+        @Mock
+        private AudioManager mAudioManager;
+        @Mock
+        private AudioService mAudioService;
+        @Mock
+        private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+        @IdRes
+        private final int mExpectedRouteNameResource;
+        private final String mExpectedRouteNameValue;
+        private final int mExpectedRouteType;
+        private final int mActualAudioRouteType;
+
+        public DeviceRouteInitializationTest(int expectedRouteNameResource,
+                String expectedRouteNameValue,
+                int expectedMediaRouteType,
+                int actualAudioRouteType) {
+            this.mExpectedRouteNameResource = expectedRouteNameResource;
+            this.mExpectedRouteNameValue = expectedRouteNameValue;
+            this.mExpectedRouteType = expectedMediaRouteType;
+            this.mActualAudioRouteType = actualAudioRouteType;
+        }
+
+        @Before
+        public void setUp() {
+            MockitoAnnotations.initMocks(this);
+
+            when(mContext.getResources()).thenReturn(mResources);
+        }
+
+        @Test
+        public void initialize_wiredRouteAvailable_deviceRouteReturnsWiredRoute() {
+            // Mocking default_audio_route_name.
+            when(mResources.getText(R.string.default_audio_route_name))
+                    .thenReturn(DEFAULT_ROUTE_NAME);
+
+            // At first, WiredRouteController should initialize device
+            // route based on AudioService response.
+            AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+            audioRoutesInfo.mainType = mActualAudioRouteType;
+            when(mAudioService.startWatchingRoutes(any())).thenReturn(audioRoutesInfo);
+
+            when(mResources.getText(mExpectedRouteNameResource))
+                    .thenReturn(mExpectedRouteNameValue);
+
+            DeviceRouteController deviceRouteController = new DeviceRouteController(
+                    mContext,
+                    mAudioManager,
+                    mAudioService,
+                    mOnDeviceRouteChangedListener
+            );
+
+            MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute();
+
+            assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType);
+            assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue))
+                    .isTrue();
+            // Volume did not change, so it should be set to default value (0).
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+        }
+    }
+
+    @RunWith(JUnit4.class)
+    public static class VolumeAndDeviceRoutesChangesTest {
+        @Mock
+        private Context mContext;
+        @Mock
+        private Resources mResources;
+        @Mock
+        private AudioManager mAudioManager;
+        @Mock
+        private AudioService mAudioService;
+        @Mock
+        private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+        @Captor
+        private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
+
+        private DeviceRouteController mDeviceRouteController;
+        private IAudioRoutesObserver.Stub mAudioRoutesObserver;
+
+        @Before
+        public void setUp() {
+            MockitoAnnotations.initMocks(this);
+
+            when(mContext.getResources()).thenReturn(mResources);
+
+            when(mResources.getText(R.string.default_audio_route_name))
+                    .thenReturn(DEFAULT_ROUTE_NAME);
+
+            // Setting built-in speaker as default speaker.
+            AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+            audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
+            when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
+                    .thenReturn(audioRoutesInfo);
+
+            mDeviceRouteController = new DeviceRouteController(
+                    mContext,
+                    mAudioManager,
+                    mAudioService,
+                    mOnDeviceRouteChangedListener
+            );
+
+            mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
+        }
+
+        @Test
+        public void newDeviceConnects_wiredDevice_deviceRouteReturnsWiredDevice() {
+            // Connecting wired headset
+            AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+            audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+
+            when(mResources.getText(
+                    com.android.internal.R.string.default_audio_route_name_headphones))
+                    .thenReturn(DEFAULT_HEADPHONES_NAME);
+
+            // Simulating wired device being connected.
+            callAudioRoutesObserver(audioRoutesInfo);
+
+            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+
+            assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
+            assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME))
+                    .isTrue();
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+        }
+
+        @Test
+        public void newDeviceConnects_bluetoothDevice_deviceRouteReturnsBluetoothDevice() {
+            // Simulating bluetooth speaker being connected.
+            AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
+            callAudioRoutesObserver(fakeBluetoothAudioRoute);
+
+            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+
+            assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+            assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME))
+                    .isTrue();
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+        }
+
+        @Test
+        public void updateVolume_differentValue_updatesDeviceRouteVolume() {
+            MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE);
+
+            assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
+
+            actualMediaRoute = mDeviceRouteController.getDeviceRoute();
+            assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1);
+        }
+
+        @Test
+        public void updateVolume_sameValue_returnsFalse() {
+            assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue();
+            assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isFalse();
+        }
+
+        /**
+         * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
+         * from {@link AudioService}. This happens when there is a wired route change,
+         * like a wired headset being connected.
+         *
+         * @param audioRoutesInfo updated state of connected wired device
+         */
+        private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
+            try {
+                // this is a captured observer implementation
+                // from WiredRoutesController's AudioService#startWatchingRoutes call
+                mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
+            } catch (RemoteException exception) {
+                // Should not happen since the object is mocked.
+                assertWithMessage("An unexpected RemoteException happened.").fail();
+            }
+        }
+    }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index ce1157e..7d5750e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -30,11 +30,14 @@
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.BatteryStatsInternal;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -291,6 +294,8 @@
             public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
                     throws RemoteException {
                 try {
+                    BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger(
+                            SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
                     mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
                     logVoidReturn("onRecognition", modelHandle, event);
                 } catch (Exception e) {
@@ -304,6 +309,8 @@
                     int captureSession)
                     throws RemoteException {
                 try {
+                    BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger(
+                            SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
                     startKeyphraseEventLatencyTracking(event);
                     mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
                     logVoidReturn("onPhraseRecognition", modelHandle, event);
@@ -387,6 +394,12 @@
         }
     }
 
+    private static class BatteryStatsHolder {
+        private static final BatteryStatsInternal INSTANCE =
+                LocalServices.getService(BatteryStatsInternal.class);
+    }
+
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
     // Actual logging logic below.
     private static final int NUM_EVENTS_TO_DUMP = 64;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index b9008c4..788d0e8 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1526,7 +1526,7 @@
      * Formats the specified {@code phoneNumber} to the E.164 representation.
      *
      * @param phoneNumber the phone number to format.
-     * @param defaultCountryIso the ISO 3166-1 two letters country code.
+     * @param defaultCountryIso the ISO 3166-1 two letters country code in UPPER CASE.
      * @return the E.164 representation, or null if the given phone number is not valid.
      */
     public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) {
@@ -1537,7 +1537,7 @@
      * Formats the specified {@code phoneNumber} to the RFC3966 representation.
      *
      * @param phoneNumber the phone number to format.
-     * @param defaultCountryIso the ISO 3166-1 two letters country code.
+     * @param defaultCountryIso the ISO 3166-1 two letters country code in UPPER CASE.
      * @return the RFC3966 representation, or null if the given phone number is not valid.
      */
     public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index c352266..64454cc 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -166,8 +166,8 @@
     public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
 
     /**
-     * Bundle key to get the respoonse from
-     * {@link #sendSatelliteDatagram(long, int, SatelliteDatagram, Executor, OutcomeReceiver)}.
+     * Bundle key to get the respoonse from {@link
+     * #sendSatelliteDatagram(long, int, SatelliteDatagram, boolean, Executor, OutcomeReceiver)}.
      * @hide
      */
     public static final String KEY_SEND_SATELLITE_DATAGRAM = "send_satellite_datagram";
@@ -701,6 +701,10 @@
      */
     public static final int SATELLITE_MODEM_STATE_OFF = 4;
     /**
+     * Satellite modem is unavailable.
+     */
+    public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5;
+    /**
      * Satellite modem state is unknown. This generic modem state should be used only when the
      * modem state cannot be mapped to other specific modem states.
      */
@@ -713,6 +717,7 @@
             SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
             SATELLITE_MODEM_STATE_DATAGRAM_RETRYING,
             SATELLITE_MODEM_STATE_OFF,
+            SATELLITE_MODEM_STATE_UNAVAILABLE,
             SATELLITE_MODEM_STATE_UNKNOWN
     })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 5dc1a65..d93ee21 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -31,7 +31,6 @@
      * Register the callback interface with satellite service.
      *
      * @param listener The callback interface to handle satellite service indications.
-     * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -43,7 +42,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void setSatelliteListener(in ISatelliteListener listener, in IIntegerConsumer errorCallback);
+    void setSatelliteListener(in ISatelliteListener listener);
 
     /**
      * Request to enable or disable the satellite service listening mode.
@@ -51,6 +50,8 @@
      *
      * @param enable True to enable satellite listening mode and false to disable.
      * @param isDemoMode Whether demo mode is enabled.
+     * @param timeout How long the satellite modem should wait for the next incoming page before
+     *                disabling listening mode.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -63,7 +64,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode,
+    void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout,
             in IIntegerConsumer errorCallback);
 
     /**
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index e24e892e..d966868 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -36,10 +36,10 @@
     /**
      * Indicates that new datagrams have been received on the device.
      *
-     * @param datagrams Array of new datagrams received.
-     * @param pendingCount The number of datagrams that are pending.
+     * @param datagram New datagram that was received.
+     * @param pendingCount Number of additional datagrams yet to be received.
      */
-    void onSatelliteDatagramsReceived(in SatelliteDatagram[] datagrams, in int pendingCount);
+    void onSatelliteDatagramReceived(in SatelliteDatagram datagram, in int pendingCount);
 
     /**
      * Indicates that the satellite has pending datagrams for the device to be pulled.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index df51432..711dcbe 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -63,19 +63,19 @@
 
     private final IBinder mBinder = new ISatellite.Stub() {
         @Override
-        public void setSatelliteListener(ISatelliteListener listener,
-                IIntegerConsumer errorCallback) throws RemoteException {
+        public void setSatelliteListener(ISatelliteListener listener) throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.setSatelliteListener(listener, errorCallback),
+                    () -> SatelliteImplBase.this.setSatelliteListener(listener),
                     "setSatelliteListener");
         }
 
         @Override
         public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
-                IIntegerConsumer errorCallback) throws RemoteException {
+                int timeout, IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteListeningEnabled(enable, isDemoMode, errorCallback),
+                            .requestSatelliteListeningEnabled(
+                                    enable, isDemoMode, timeout, errorCallback),
                     "requestSatelliteListeningEnabled");
         }
 
@@ -229,7 +229,6 @@
      * Register the callback interface with satellite service.
      *
      * @param listener The callback interface to handle satellite service indications.
-     * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -241,8 +240,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void setSatelliteListener(@NonNull ISatelliteListener listener,
-            @NonNull IIntegerConsumer errorCallback) {
+    public void setSatelliteListener(@NonNull ISatelliteListener listener) {
         // stub implementation
     }
 
@@ -252,6 +250,8 @@
      *
      * @param enable True to enable satellite listening mode and false to disable.
      * @param isDemoMode Whether demo mode is enabled.
+     * @param timeout How long the satellite modem should wait for the next incoming page before
+     *                disabling listening mode.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -264,7 +264,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
+    public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
index 5ee7f9a..e4f9413 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
@@ -42,6 +42,10 @@
      */
     SATELLITE_MODEM_STATE_OFF = 4,
     /**
+     * Satellite modem is unavailable.
+     */
+    SATELLITE_MODEM_STATE_UNAVAILABLE = 5,
+    /**
      * Satellite modem state is unknown. This generic modem state should be used only when the
      * modem state cannot be mapped to other specific modem states.
      */
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index c7e5a5e..e59071b 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -29,6 +29,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.Debug.MemoryInfo;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.test.InstrumentationTestCase;
@@ -40,6 +41,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * This test is intended to measure the amount of memory applications use when
@@ -313,17 +315,19 @@
 
         public void run() {
             try {
-                String mimeType = mLaunchIntent.getType();
-                if (mimeType == null && mLaunchIntent.getData() != null
+                AtomicReference<String> mimeType = new AtomicReference<>(mLaunchIntent.getType());
+                if (mimeType.get() == null && mLaunchIntent.getData() != null
                         && "content".equals(mLaunchIntent.getData().getScheme())) {
-                    mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(),
-                            UserHandle.USER_CURRENT);
+                    mAm.getMimeTypeFilterAsync(mLaunchIntent.getData(), UserHandle.USER_CURRENT,
+                            new RemoteCallback(result -> {
+                                mimeType.set(result.getPairValue());
+                            }));
                 }
 
                 mAtm.startActivityAndWait(null,
                         getInstrumentation().getContext().getBasePackageName(),
                         getInstrumentation().getContext().getAttributionTag(), mLaunchIntent,
-                        mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null,
+                        mimeType.get(), null, null, 0, mLaunchIntent.getFlags(), null, null,
                         UserHandle.USER_CURRENT_OR_SELF);
             } catch (RemoteException e) {
                 Log.w(TAG, "Error launching app", e);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
index 161c83c..1fb1c63 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -24,11 +24,12 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A data class representing a known Wi-Fi network.
@@ -59,7 +60,7 @@
 
     @NetworkSource private final int mNetworkSource;
     private final String mSsid;
-    @SecurityType private final int[] mSecurityTypes;
+    @SecurityType private final ArraySet<Integer> mSecurityTypes;
     private final DeviceInfo mDeviceInfo;
 
     /**
@@ -68,11 +69,9 @@
     public static final class Builder {
         @NetworkSource private int mNetworkSource = -1;
         private String mSsid;
-        @SecurityType private int[] mSecurityTypes;
+        @SecurityType private final ArraySet<Integer> mSecurityTypes = new ArraySet<>();
         private android.net.wifi.sharedconnectivity.app.DeviceInfo mDeviceInfo;
 
-        public Builder() {}
-
         /**
          * Sets the indicated source of the known network.
          *
@@ -98,14 +97,14 @@
         }
 
         /**
-         * Sets the security types of the known network.
+         * Adds a security type of the known network.
          *
-         * @param securityTypes The array of security types supported by the known network.
+         * @param securityType A security type supported by the known network.
          * @return Returns the Builder object.
          */
         @NonNull
-        public Builder setSecurityTypes(@NonNull @SecurityType int[] securityTypes) {
-            mSecurityTypes = securityTypes;
+        public Builder addSecurityType(@SecurityType int securityType) {
+            mSecurityTypes.add(securityType);
             return this;
         }
 
@@ -136,7 +135,7 @@
         }
     }
 
-    private static void validate(int networkSource, String ssid, int [] securityTypes) {
+    private static void validate(int networkSource, String ssid, Set<Integer> securityTypes) {
         if (networkSource != NETWORK_SOURCE_CLOUD_SELF && networkSource
                 != NETWORK_SOURCE_NEARBY_SELF) {
             throw new IllegalArgumentException("Illegal network source");
@@ -144,7 +143,7 @@
         if (TextUtils.isEmpty(ssid)) {
             throw new IllegalArgumentException("SSID must be set");
         }
-        if (securityTypes == null || securityTypes.length == 0) {
+        if (securityTypes.isEmpty()) {
             throw new IllegalArgumentException("SecurityTypes must be set");
         }
     }
@@ -152,12 +151,12 @@
     private KnownNetwork(
             @NetworkSource int networkSource,
             @NonNull String ssid,
-            @NonNull @SecurityType int[] securityTypes,
+            @NonNull @SecurityType ArraySet<Integer> securityTypes,
             @NonNull DeviceInfo deviceInfo) {
         validate(networkSource, ssid, securityTypes);
         mNetworkSource = networkSource;
         mSsid = ssid;
-        mSecurityTypes = securityTypes;
+        mSecurityTypes = new ArraySet<>(securityTypes);
         mDeviceInfo = deviceInfo;
     }
 
@@ -184,11 +183,11 @@
     /**
      * Gets the security types of the known network.
      *
-     * @return Returns the array of security types supported by the known network.
+     * @return Returns a set with security types supported by the known network.
      */
     @NonNull
     @SecurityType
-    public int[] getSecurityTypes() {
+    public Set<Integer> getSecurityTypes() {
         return mSecurityTypes;
     }
 
@@ -208,14 +207,13 @@
         KnownNetwork other = (KnownNetwork) obj;
         return mNetworkSource == other.getNetworkSource()
                 && Objects.equals(mSsid, other.getSsid())
-                && Arrays.equals(mSecurityTypes, other.getSecurityTypes())
+                && Objects.equals(mSecurityTypes, other.getSecurityTypes())
                 && Objects.equals(mDeviceInfo, other.getDeviceInfo());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkSource, mSsid, Arrays.hashCode(mSecurityTypes),
-                mDeviceInfo.hashCode());
+        return Objects.hash(mNetworkSource, mSsid, mSecurityTypes, mDeviceInfo);
     }
 
     @Override
@@ -227,7 +225,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mNetworkSource);
         dest.writeString(mSsid);
-        dest.writeIntArray(mSecurityTypes);
+        dest.writeArraySet(mSecurityTypes);
         mDeviceInfo.writeToParcel(dest, flags);
     }
 
@@ -238,7 +236,8 @@
      */
     @NonNull
     public static KnownNetwork readFromParcel(@NonNull Parcel in) {
-        return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(),
+        return new KnownNetwork(in.readInt(), in.readString(),
+                (ArraySet<Integer>) in.readArraySet(null),
                 DeviceInfo.readFromParcel(in));
     }
 
@@ -260,7 +259,7 @@
         return new StringBuilder("KnownNetwork[")
                 .append("NetworkSource=").append(mNetworkSource)
                 .append(", ssid=").append(mSsid)
-                .append(", securityTypes=").append(Arrays.toString(mSecurityTypes))
+                .append(", securityTypes=").append(mSecurityTypes.toString())
                 .append(", deviceInfo=").append(mDeviceInfo.toString())
                 .append("]").toString();
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
index af4fd4a..7b591d3 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
@@ -25,12 +25,12 @@
 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
 import android.os.Parcel;
 import android.os.Parcelable;
-
+import android.util.ArraySet;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A data class representing an Instant Tether network.
@@ -79,7 +79,7 @@
     private final String mNetworkName;
     @Nullable private final String mHotspotSsid;
     @Nullable private final String mHotspotBssid;
-    @Nullable @SecurityType private final int[] mHotspotSecurityTypes;
+    @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes;
 
     /**
      * Builder class for {@link TetherNetwork}.
@@ -91,9 +91,8 @@
         private String mNetworkName;
         @Nullable private String mHotspotSsid;
         @Nullable private String mHotspotBssid;
-        @Nullable @SecurityType private int[] mHotspotSecurityTypes;
-
-        public Builder() {}
+        @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes =
+                new ArraySet<>();
 
         /**
          * Set the remote device ID.
@@ -168,15 +167,14 @@
         }
 
         /**
-         * Sets the hotspot security types supported by the remote device, or null if hotspot is
-         * off.
+         * Adds a security type supported by the hotspot created by the remote device.
          *
-         * @param hotspotSecurityTypes The array of security types supported by the hotspot.
+         * @param hotspotSecurityType A security type supported by the hotspot.
          * @return Returns the Builder object.
          */
         @NonNull
-        public Builder setHotspotSecurityTypes(@NonNull @SecurityType int[] hotspotSecurityTypes) {
-            mHotspotSecurityTypes = hotspotSecurityTypes;
+        public Builder addHotspotSecurityType(@SecurityType int hotspotSecurityType) {
+            mHotspotSecurityTypes.add(hotspotSecurityType);
             return this;
         }
 
@@ -218,7 +216,7 @@
             @NonNull String networkName,
             @Nullable String hotspotSsid,
             @Nullable String hotspotBssid,
-            @Nullable @SecurityType int[] hotspotSecurityTypes) {
+            @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes) {
         validate(deviceId,
                 networkType,
                 networkName);
@@ -228,7 +226,7 @@
         mNetworkName = networkName;
         mHotspotSsid = hotspotSsid;
         mHotspotBssid = hotspotBssid;
-        mHotspotSecurityTypes = hotspotSecurityTypes;
+        mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes);
     }
 
     /**
@@ -293,11 +291,11 @@
     /**
      * Gets the hotspot security types supported by the remote device.
      *
-     * @return Returns the array of security types supported by the hotspot.
+     * @return Returns a set of the security types supported by the hotspot.
      */
-    @Nullable
+    @NonNull
     @SecurityType
-    public int[] getHotspotSecurityTypes() {
+    public Set<Integer> getHotspotSecurityTypes() {
         return mHotspotSecurityTypes;
     }
 
@@ -311,13 +309,13 @@
                 && Objects.equals(mNetworkName, other.getNetworkName())
                 && Objects.equals(mHotspotSsid, other.getHotspotSsid())
                 && Objects.equals(mHotspotBssid, other.getHotspotBssid())
-                && Arrays.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes());
+                && Objects.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes());
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mDeviceId, mDeviceInfo, mNetworkName, mHotspotSsid, mHotspotBssid,
-                Arrays.hashCode(mHotspotSecurityTypes));
+                mHotspotSecurityTypes);
     }
 
     @Override
@@ -333,7 +331,7 @@
         dest.writeString(mNetworkName);
         dest.writeString(mHotspotSsid);
         dest.writeString(mHotspotBssid);
-        dest.writeIntArray(mHotspotSecurityTypes);
+        dest.writeArraySet(mHotspotSecurityTypes);
     }
 
     /**
@@ -345,7 +343,7 @@
     public static TetherNetwork readFromParcel(@NonNull Parcel in) {
         return new TetherNetwork(in.readLong(), DeviceInfo.readFromParcel(in),
                 in.readInt(), in.readString(), in.readString(), in.readString(),
-                in.createIntArray());
+                (ArraySet<Integer>) in.readArraySet(null));
     }
 
     @NonNull
@@ -370,7 +368,7 @@
                 .append(", networkName=").append(mNetworkName)
                 .append(", hotspotSsid=").append(mHotspotSsid)
                 .append(", hotspotBssid=").append(mHotspotBssid)
-                .append(", hotspotSecurityTypes=").append(Arrays.toString(mHotspotSecurityTypes))
+                .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString())
                 .append("]").toString();
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
index f8f0700..e6595eb 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
@@ -19,8 +19,7 @@
 import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_LAPTOP;
 import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Parcel;
 
@@ -29,7 +28,7 @@
 import org.junit.Test;
 
 /**
- * Unit tests for {@link android.app.sharedconnectivity.DeviceInfo}.
+ * Unit tests for {@link DeviceInfo}.
  */
 @SmallTest
 public class DeviceInfoTest {
@@ -63,8 +62,8 @@
         parcelR.setDataPosition(0);
         DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(info, fromParcel);
-        assertEquals(info.hashCode(), fromParcel.hashCode());
+        assertThat(fromParcel).isEqualTo(info);
+        assertThat(fromParcel.hashCode()).isEqualTo(info.hashCode());
     }
 
     /**
@@ -74,24 +73,24 @@
     public void testEqualsOperation() {
         DeviceInfo info1 = buildDeviceInfoBuilder().build();
         DeviceInfo info2 = buildDeviceInfoBuilder().build();
-        assertEquals(info1, info2);
+        assertThat(info1).isEqualTo(info2);
 
         DeviceInfo.Builder builder = buildDeviceInfoBuilder().setDeviceType(DEVICE_TYPE_1);
-        assertNotEquals(info1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(info1);
 
         builder = buildDeviceInfoBuilder().setDeviceName(DEVICE_NAME_1);
-        assertNotEquals(info1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(info1);
 
         builder = buildDeviceInfoBuilder().setModelName(DEVICE_MODEL_1);
-        assertNotEquals(info1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(info1);
 
         builder = buildDeviceInfoBuilder()
                 .setBatteryPercentage(BATTERY_PERCENTAGE_1);
-        assertNotEquals(info1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(info1);
 
         builder = buildDeviceInfoBuilder()
                 .setConnectionStrength(CONNECTION_STRENGTH_1);
-        assertNotEquals(info1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(info1);
     }
 
     /**
@@ -100,12 +99,19 @@
     @Test
     public void testGetMethods() {
         DeviceInfo info = buildDeviceInfoBuilder().build();
-        assertEquals(info.getDeviceType(), DEVICE_TYPE);
-        assertEquals(info.getDeviceName(), DEVICE_NAME);
-        assertEquals(info.getModelName(), DEVICE_MODEL);
-        assertEquals(info.getBatteryPercentage(), BATTERY_PERCENTAGE);
-        assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH);
-        assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH);
+        assertThat(info.getDeviceType()).isEqualTo(DEVICE_TYPE);
+        assertThat(info.getDeviceName()).isEqualTo(DEVICE_NAME);
+        assertThat(info.getModelName()).isEqualTo(DEVICE_MODEL);
+        assertThat(info.getBatteryPercentage()).isEqualTo(BATTERY_PERCENTAGE);
+        assertThat(info.getConnectionStrength()).isEqualTo(CONNECTION_STRENGTH);
+    }
+
+    @Test
+    public void testHashCode() {
+        DeviceInfo info1 = buildDeviceInfoBuilder().build();
+        DeviceInfo info2 = buildDeviceInfoBuilder().build();
+
+        assertThat(info1.hashCode()).isEqualTo(info2.hashCode());
     }
 
     private DeviceInfo.Builder buildDeviceInfoBuilder() {
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
index 37dca8d..8a0f21e 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java
@@ -22,8 +22,7 @@
 import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED;
 import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVE_FAILED;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Bundle;
 import android.os.Parcel;
@@ -32,8 +31,10 @@
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 /**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus}.
+ * Unit tests for {@link KnownNetworkConnectionStatus}.
  */
 @SmallTest
 public class KnownNetworkConnectionStatusTest {
@@ -45,6 +46,7 @@
             .setConnectionStrength(2).setBatteryPercentage(50).build();
     private static final String SSID_1 = "TEST_SSID1";
     private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
 
     /**
      * Verifies parcel serialization/deserialization.
@@ -64,8 +66,8 @@
         KnownNetworkConnectionStatus fromParcel =
                 KnownNetworkConnectionStatus.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(status, fromParcel);
-        assertEquals(status.hashCode(), fromParcel.hashCode());
+        assertThat(fromParcel).isEqualTo(status);
+        assertThat(fromParcel.hashCode()).isEqualTo(status.hashCode());
     }
 
     /**
@@ -75,15 +77,15 @@
     public void testEqualsOperation() {
         KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
         KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
-        assertEquals(status2, status2);
+        assertThat(status1).isEqualTo(status2);
 
         KnownNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder()
                 .setStatus(CONNECTION_STATUS_SAVE_FAILED);
-        assertNotEquals(status1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(status1);
 
         builder = buildConnectionStatusBuilder()
                 .setKnownNetwork(buildKnownNetworkBuilder().setSsid(SSID_1).build());
-        assertNotEquals(status1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(status1);
     }
 
     /**
@@ -92,9 +94,17 @@
     @Test
     public void testGetMethods() {
         KnownNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
-        assertEquals(status.getStatus(), CONNECTION_STATUS_SAVED);
-        assertEquals(status.getKnownNetwork(), buildKnownNetworkBuilder().build());
-        assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY));
+        assertThat(status.getStatus()).isEqualTo(CONNECTION_STATUS_SAVED);
+        assertThat(status.getKnownNetwork()).isEqualTo(buildKnownNetworkBuilder().build());
+        assertThat(status.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
+    }
+
+    @Test
+    public void testHashCode() {
+        KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
+        KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
+
+        assertThat(status1.hashCode()).isEqualTo(status2.hashCode());
     }
 
     private KnownNetworkConnectionStatus.Builder buildConnectionStatusBuilder() {
@@ -106,13 +116,15 @@
 
     private Bundle buildBundle() {
         Bundle bundle = new Bundle();
-        bundle.putInt(BUNDLE_KEY, 1);
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
         return bundle;
     }
 
     private KnownNetwork.Builder buildKnownNetworkBuilder() {
-        return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
-                .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO);
+        KnownNetwork.Builder builder =  new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
+                .setSsid(SSID).setDeviceInfo(DEVICE_INFO);
+        Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
+        return builder;
     }
 
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
index 266afcc..872dd2e 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
@@ -23,18 +23,19 @@
 import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_CLOUD_SELF;
 import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Parcel;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 /**
- * Unit tests for {@link android.app.sharedconnectivity.KnownNetwork}.
+ * Unit tests for {@link KnownNetwork}.
  */
 @SmallTest
 public class KnownNetworkTest {
@@ -69,8 +70,8 @@
         parcelR.setDataPosition(0);
         KnownNetwork fromParcel = KnownNetwork.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(network, fromParcel);
-        assertEquals(network.hashCode(), fromParcel.hashCode());
+        assertThat(fromParcel).isEqualTo(network);
+        assertThat(fromParcel.hashCode()).isEqualTo(network.hashCode());
     }
 
     /**
@@ -80,20 +81,21 @@
     public void testEqualsOperation() {
         KnownNetwork network1 = buildKnownNetworkBuilder().build();
         KnownNetwork network2 = buildKnownNetworkBuilder().build();
-        assertEquals(network1, network2);
+        assertThat(network1).isEqualTo(network2);
 
         KnownNetwork.Builder builder = buildKnownNetworkBuilder()
                 .setNetworkSource(NETWORK_SOURCE_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildKnownNetworkBuilder().setSsid(SSID_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
-        builder = buildKnownNetworkBuilder().setSecurityTypes(SECURITY_TYPES_1);
-        assertNotEquals(network1, builder.build());
+        builder = buildKnownNetworkBuilder();
+        Arrays.stream(SECURITY_TYPES_1).forEach(builder::addSecurityType);
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildKnownNetworkBuilder().setDeviceInfo(DEVICE_INFO_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
     }
 
     /**
@@ -102,14 +104,27 @@
     @Test
     public void testGetMethods() {
         KnownNetwork network = buildKnownNetworkBuilder().build();
-        assertEquals(network.getNetworkSource(), NETWORK_SOURCE);
-        assertEquals(network.getSsid(), SSID);
-        assertArrayEquals(network.getSecurityTypes(), SECURITY_TYPES);
-        assertEquals(network.getDeviceInfo(), DEVICE_INFO);
+        ArraySet<Integer> securityTypes = new ArraySet<>();
+        Arrays.stream(SECURITY_TYPES).forEach(securityTypes::add);
+
+        assertThat(network.getNetworkSource()).isEqualTo(NETWORK_SOURCE);
+        assertThat(network.getSsid()).isEqualTo(SSID);
+        assertThat(network.getSecurityTypes()).containsExactlyElementsIn(securityTypes);
+        assertThat(network.getDeviceInfo()).isEqualTo(DEVICE_INFO);
+    }
+
+    @Test
+    public void testHashCode() {
+        KnownNetwork network1 = buildKnownNetworkBuilder().build();
+        KnownNetwork network2 = buildKnownNetworkBuilder().build();
+
+        assertThat(network1.hashCode()).isEqualTo(network2.hashCode());
     }
 
     private KnownNetwork.Builder buildKnownNetworkBuilder() {
-        return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
-                .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO);
+        KnownNetwork.Builder builder =  new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
+                .setSsid(SSID).setDeviceInfo(DEVICE_INFO);
+        Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
+        return builder;
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index cdb438f..7c0a8b6 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -22,11 +22,8 @@
 import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
 import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doThrow;
@@ -49,6 +46,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -110,7 +108,7 @@
     public void resourcesNotDefined() {
         when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException());
 
-        assertNull(SharedConnectivityManager.create(mContext));
+        assertThat(SharedConnectivityManager.create(mContext)).isNull();
     }
 
     /**
@@ -183,7 +181,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertFalse(manager.unregisterCallback(mClientCallback));
+        assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
     }
 
     @Test
@@ -191,7 +189,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
 
-        assertFalse(manager.unregisterCallback(mClientCallback));
+        assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
     }
 
     @Test
@@ -201,7 +199,7 @@
 
         manager.registerCallback(mExecutor, mClientCallback);
 
-        assertTrue(manager.unregisterCallback(mClientCallback));
+        assertThat(manager.unregisterCallback(mClientCallback)).isTrue();
         verify(mService).unregisterCallback(any());
     }
 
@@ -213,7 +211,7 @@
         manager.registerCallback(mExecutor, mClientCallback);
         manager.unregisterCallback(mClientCallback);
 
-        assertFalse(manager.unregisterCallback(mClientCallback));
+        assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
     }
 
     @Test
@@ -224,7 +222,7 @@
         manager.registerCallback(mExecutor, mClientCallback);
         manager.unregisterCallback(mClientCallback);
 
-        assertFalse(manager.unregisterCallback(mClientCallback));
+        assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
     }
 
     @Test
@@ -234,7 +232,7 @@
 
         doThrow(new RemoteException()).when(mService).unregisterCallback(any());
 
-        assertFalse(manager.unregisterCallback(mClientCallback));
+        assertThat(manager.unregisterCallback(mClientCallback)).isFalse();
     }
 
     /**
@@ -294,7 +292,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertFalse(manager.connectTetherNetwork(network));
+        assertThat(manager.connectTetherNetwork(network)).isFalse();
     }
 
     @Test
@@ -315,7 +313,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).connectTetherNetwork(network);
 
-        assertFalse(manager.connectTetherNetwork(network));
+        assertThat(manager.connectTetherNetwork(network)).isFalse();
     }
 
     /**
@@ -327,7 +325,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertFalse(manager.disconnectTetherNetwork(network));
+        assertThat(manager.disconnectTetherNetwork(network)).isFalse();
     }
 
     @Test
@@ -348,7 +346,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).disconnectTetherNetwork(any());
 
-        assertFalse(manager.disconnectTetherNetwork(network));
+        assertThat(manager.disconnectTetherNetwork(network)).isFalse();
     }
 
     /**
@@ -360,7 +358,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertFalse(manager.connectKnownNetwork(network));
+        assertThat(manager.connectKnownNetwork(network)).isFalse();
     }
 
     @Test
@@ -381,7 +379,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).connectKnownNetwork(network);
 
-        assertFalse(manager.connectKnownNetwork(network));
+        assertThat(manager.connectKnownNetwork(network)).isFalse();
     }
 
     /**
@@ -393,7 +391,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertFalse(manager.forgetKnownNetwork(network));
+        assertThat(manager.forgetKnownNetwork(network)).isFalse();
     }
 
     @Test
@@ -414,7 +412,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).forgetKnownNetwork(network);
 
-        assertFalse(manager.forgetKnownNetwork(network));
+        assertThat(manager.forgetKnownNetwork(network)).isFalse();
     }
 
     /**
@@ -425,7 +423,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertArrayEquals(List.of().toArray(), manager.getTetherNetworks().toArray());
+        assertThat(manager.getKnownNetworks()).isEmpty();
     }
 
     @Test
@@ -434,18 +432,17 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getTetherNetworks();
 
-        assertArrayEquals(List.of().toArray(), manager.getTetherNetworks().toArray());
+        assertThat(manager.getKnownNetworks()).isEmpty();
     }
 
     @Test
     public void getTetherNetworks_shouldReturnNetworksList() throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         List<TetherNetwork> networks = List.of(buildTetherNetwork());
-        List<TetherNetwork> expected = List.of(buildTetherNetwork());
         manager.setService(mService);
         when(mService.getTetherNetworks()).thenReturn(networks);
 
-        assertArrayEquals(expected.toArray(), manager.getTetherNetworks().toArray());
+        assertThat(manager.getTetherNetworks()).containsExactly(buildTetherNetwork());
     }
 
     @Test
@@ -454,7 +451,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertArrayEquals(List.of().toArray(), manager.getKnownNetworks().toArray());
+        assertThat(manager.getKnownNetworks()).isEmpty();
     }
 
     @Test
@@ -463,18 +460,17 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getKnownNetworks();
 
-        assertArrayEquals(List.of().toArray(), manager.getKnownNetworks().toArray());
+        assertThat(manager.getKnownNetworks()).isEmpty();
     }
 
     @Test
     public void getKnownNetworks_shouldReturnNetworksList() throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         List<KnownNetwork> networks = List.of(buildKnownNetwork());
-        List<KnownNetwork> expected = List.of(buildKnownNetwork());
         manager.setService(mService);
         when(mService.getKnownNetworks()).thenReturn(networks);
 
-        assertArrayEquals(expected.toArray(), manager.getKnownNetworks().toArray());
+        assertThat(manager.getKnownNetworks()).containsExactly(buildKnownNetwork());
     }
 
     @Test
@@ -482,7 +478,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertNull(manager.getSettingsState());
+        assertThat(manager.getSettingsState()).isNull();
     }
 
     @Test
@@ -491,7 +487,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getSettingsState();
 
-        assertNull(manager.getSettingsState());
+        assertThat(manager.getSettingsState()).isNull();
     }
 
     @Test
@@ -502,7 +498,7 @@
         manager.setService(mService);
         when(mService.getSettingsState()).thenReturn(state);
 
-        assertEquals(state, manager.getSettingsState());
+        assertThat(manager.getSettingsState()).isEqualTo(state);
     }
 
     @Test
@@ -511,7 +507,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertNull(manager.getTetherNetworkConnectionStatus());
+        assertThat(manager.getTetherNetworkConnectionStatus()).isNull();
     }
 
     @Test
@@ -521,7 +517,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getTetherNetworkConnectionStatus();
 
-        assertNull(manager.getTetherNetworkConnectionStatus());
+        assertThat(manager.getTetherNetworkConnectionStatus()).isNull();
     }
 
     @Test
@@ -534,7 +530,7 @@
         manager.setService(mService);
         when(mService.getTetherNetworkConnectionStatus()).thenReturn(status);
 
-        assertEquals(status, manager.getTetherNetworkConnectionStatus());
+        assertThat(manager.getTetherNetworkConnectionStatus()).isEqualTo(status);
     }
 
     @Test
@@ -543,7 +539,7 @@
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertNull(manager.getKnownNetworkConnectionStatus());
+        assertThat(manager.getKnownNetworkConnectionStatus()).isNull();
     }
 
     @Test
@@ -553,7 +549,7 @@
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getKnownNetworkConnectionStatus();
 
-        assertNull(manager.getKnownNetworkConnectionStatus());
+        assertThat(manager.getKnownNetworkConnectionStatus()).isNull();
     }
 
     @Test
@@ -566,7 +562,7 @@
         manager.setService(mService);
         when(mService.getKnownNetworkConnectionStatus()).thenReturn(status);
 
-        assertEquals(status, manager.getKnownNetworkConnectionStatus());
+        assertThat(manager.getKnownNetworkConnectionStatus()).isEqualTo(status);
     }
 
     private void setResources(@Mock Context context) {
@@ -576,18 +572,20 @@
     }
 
     private TetherNetwork buildTetherNetwork() {
-        return new TetherNetwork.Builder()
+        TetherNetwork.Builder builder =  new TetherNetwork.Builder()
                 .setDeviceId(DEVICE_ID)
                 .setDeviceInfo(DEVICE_INFO)
                 .setNetworkType(NETWORK_TYPE)
                 .setNetworkName(NETWORK_NAME)
-                .setHotspotSsid(HOTSPOT_SSID)
-                .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES)
-                .build();
+                .setHotspotSsid(HOTSPOT_SSID);
+        Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
+        return builder.build();
     }
 
     private KnownNetwork buildKnownNetwork() {
-        return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
-                .setSecurityTypes(SECURITY_TYPES).build();
+        KnownNetwork.Builder builder =  new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE)
+                .setSsid(SSID).setDeviceInfo(DEVICE_INFO);
+        Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType);
+        return builder.build();
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
index 3137c72..752b749 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
@@ -16,8 +16,7 @@
 
 package android.net.wifi.sharedconnectivity.app;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Parcel;
 
@@ -26,7 +25,7 @@
 import org.junit.Test;
 
 /**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState}.
+ * Unit tests for {@link SharedConnectivitySettingsState}.
  */
 @SmallTest
 public class SharedConnectivitySettingsStateTest {
@@ -51,8 +50,8 @@
         SharedConnectivitySettingsState fromParcel =
                 SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(state, fromParcel);
-        assertEquals(state.hashCode(), fromParcel.hashCode());
+        assertThat(fromParcel).isEqualTo(state);
+        assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode());
     }
 
     /**
@@ -62,11 +61,11 @@
     public void testEqualsOperation() {
         SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build();
         SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build();
-        assertEquals(state1, state2);
+        assertThat(state1).isEqualTo(state2);
 
         SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder()
                 .setInstantTetherEnabled(INSTANT_TETHER_STATE_1);
-        assertNotEquals(state1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(state1);
     }
 
     /**
@@ -75,7 +74,15 @@
     @Test
     public void testGetMethods() {
         SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
-        assertEquals(state.isInstantTetherEnabled(), INSTANT_TETHER_STATE);
+        assertThat(state.isInstantTetherEnabled()).isEqualTo(INSTANT_TETHER_STATE);
+    }
+
+    @Test
+    public void testHashCode() {
+        SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build();
+        SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build();
+
+        assertThat(state1.hashCode()).isEqualTo(state2.hashCode());
     }
 
     private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() {
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
index 1d9c2e6..0844364 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java
@@ -23,8 +23,7 @@
 import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT;
 import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Bundle;
 import android.os.Parcel;
@@ -33,8 +32,10 @@
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 /**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus}.
+ * Unit tests for {@link TetherNetworkConnectionStatus}.
  */
 @SmallTest
 public class TetherNetworkConnectionStatusTest {
@@ -49,6 +50,7 @@
     private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
     private static final long DEVICE_ID_1 = 111L;
     private static final String BUNDLE_KEY = "INT-KEY";
+    private static final int BUNDLE_VALUE = 1;
 
     /**
      * Verifies parcel serialization/deserialization.
@@ -68,8 +70,8 @@
         TetherNetworkConnectionStatus fromParcel =
                 TetherNetworkConnectionStatus.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(status, fromParcel);
-        assertEquals(status.hashCode(), fromParcel.hashCode());
+        assertThat(fromParcel).isEqualTo(status);
+        assertThat(fromParcel.hashCode()).isEqualTo(status.hashCode());
     }
 
     /**
@@ -79,15 +81,15 @@
     public void testEqualsOperation() {
         TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
         TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
-        assertEquals(status2, status2);
+        assertThat(status1).isEqualTo(status2);
 
         TetherNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder()
                 .setStatus(CONNECTION_STATUS_TETHERING_TIMEOUT);
-        assertNotEquals(status1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(status1);
 
         builder = buildConnectionStatusBuilder()
                 .setTetherNetwork(buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1).build());
-        assertNotEquals(status1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(status1);
     }
 
     /**
@@ -96,13 +98,20 @@
     @Test
     public void testGetMethods() {
         TetherNetworkConnectionStatus status = buildConnectionStatusBuilder().build();
-        assertEquals(status.getStatus(), CONNECTION_STATUS_ENABLING_HOTSPOT);
-        assertEquals(status.getTetherNetwork(), buildTetherNetworkBuilder().build());
-        assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY));
+        assertThat(status.getStatus()).isEqualTo(CONNECTION_STATUS_ENABLING_HOTSPOT);
+        assertThat(status.getTetherNetwork()).isEqualTo(buildTetherNetworkBuilder().build());
+        assertThat(status.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
+    }
+
+    @Test
+    public void testHashCode() {
+        TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build();
+        TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build();
+
+        assertThat(status1.hashCode()).isEqualTo(status2.hashCode());
     }
 
     private TetherNetworkConnectionStatus.Builder buildConnectionStatusBuilder() {
-
         return new TetherNetworkConnectionStatus.Builder()
                 .setStatus(CONNECTION_STATUS_ENABLING_HOTSPOT)
                 .setTetherNetwork(buildTetherNetworkBuilder().build())
@@ -111,18 +120,19 @@
 
     private Bundle buildBundle() {
         Bundle bundle = new Bundle();
-        bundle.putInt(BUNDLE_KEY, 1);
+        bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE);
         return bundle;
     }
 
     private TetherNetwork.Builder buildTetherNetworkBuilder() {
-        return new TetherNetwork.Builder()
+        TetherNetwork.Builder builder =  new TetherNetwork.Builder()
                 .setDeviceId(DEVICE_ID)
                 .setDeviceInfo(DEVICE_INFO)
                 .setNetworkType(NETWORK_TYPE)
                 .setNetworkName(NETWORK_NAME)
                 .setHotspotSsid(HOTSPOT_SSID)
-                .setHotspotBssid(HOTSPOT_BSSID)
-                .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES);
+                .setHotspotBssid(HOTSPOT_BSSID);
+        Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
+        return builder;
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
index b01aec4..a50d767 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
@@ -24,18 +24,19 @@
 import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
 import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_WIFI;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Parcel;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 /**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetwork}.
+ * Unit tests for {@link TetherNetwork}.
  */
 @SmallTest
 public class TetherNetworkTest {
@@ -76,8 +77,8 @@
         parcelR.setDataPosition(0);
         TetherNetwork fromParcel = TetherNetwork.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(network, fromParcel);
-        assertEquals(network.hashCode(), fromParcel.hashCode());
+        assertThat(fromParcel).isEqualTo(network);
+        assertThat(fromParcel.hashCode()).isEqualTo(network.hashCode());
     }
 
     /**
@@ -87,28 +88,31 @@
     public void testEqualsOperation() {
         TetherNetwork network1 = buildTetherNetworkBuilder().build();
         TetherNetwork network2 = buildTetherNetworkBuilder().build();
-        assertEquals(network1, network2);
+        assertThat(network1).isEqualTo(network2);
 
         TetherNetwork.Builder builder = buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildTetherNetworkBuilder().setDeviceInfo(DEVICE_INFO_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildTetherNetworkBuilder().setNetworkType(NETWORK_TYPE_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildTetherNetworkBuilder().setNetworkName(NETWORK_NAME_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildTetherNetworkBuilder().setHotspotSsid(HOTSPOT_SSID_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
         builder = buildTetherNetworkBuilder().setHotspotBssid(HOTSPOT_BSSID_1);
-        assertNotEquals(network1, builder.build());
+        assertThat(builder.build()).isNotEqualTo(network1);
 
-        builder = buildTetherNetworkBuilder().setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES_1);
-        assertNotEquals(network1, builder.build());
+        builder = buildTetherNetworkBuilder();
+        TetherNetwork.Builder builder1 = buildTetherNetworkBuilder();
+        Arrays.stream(HOTSPOT_SECURITY_TYPES_1).forEach(builder1::addHotspotSecurityType);
+
+        assertThat(builder1.build()).isNotEqualTo(builder.build());
     }
 
     /**
@@ -117,23 +121,35 @@
     @Test
     public void testGetMethods() {
         TetherNetwork network = buildTetherNetworkBuilder().build();
-        assertEquals(network.getDeviceId(), DEVICE_ID);
-        assertEquals(network.getDeviceInfo(), DEVICE_INFO);
-        assertEquals(network.getNetworkType(), NETWORK_TYPE);
-        assertEquals(network.getNetworkName(), NETWORK_NAME);
-        assertEquals(network.getHotspotSsid(), HOTSPOT_SSID);
-        assertEquals(network.getHotspotBssid(), HOTSPOT_BSSID);
-        assertArrayEquals(network.getHotspotSecurityTypes(), HOTSPOT_SECURITY_TYPES);
+        ArraySet<Integer> securityTypes = new ArraySet<>();
+        Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(securityTypes::add);
+
+        assertThat(network.getDeviceId()).isEqualTo(DEVICE_ID);
+        assertThat(network.getDeviceInfo()).isEqualTo(DEVICE_INFO);
+        assertThat(network.getNetworkType()).isEqualTo(NETWORK_TYPE);
+        assertThat(network.getNetworkName()).isEqualTo(NETWORK_NAME);
+        assertThat(network.getHotspotSsid()).isEqualTo(HOTSPOT_SSID);
+        assertThat(network.getHotspotBssid()).isEqualTo(HOTSPOT_BSSID);
+        assertThat(network.getHotspotSecurityTypes()).containsExactlyElementsIn(securityTypes);
+    }
+
+    @Test
+    public void testHashCode() {
+        TetherNetwork network1 = buildTetherNetworkBuilder().build();
+        TetherNetwork network2 = buildTetherNetworkBuilder().build();
+
+        assertThat(network1.hashCode()).isEqualTo(network2.hashCode());
     }
 
     private TetherNetwork.Builder buildTetherNetworkBuilder() {
-        return new TetherNetwork.Builder()
+        TetherNetwork.Builder builder =  new TetherNetwork.Builder()
                 .setDeviceId(DEVICE_ID)
                 .setDeviceInfo(DEVICE_INFO)
                 .setNetworkType(NETWORK_TYPE)
                 .setNetworkName(NETWORK_NAME)
                 .setHotspotSsid(HOTSPOT_SSID)
-                .setHotspotBssid(HOTSPOT_BSSID)
-                .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES);
+                .setHotspotBssid(HOTSPOT_BSSID);
+        Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType);
+        return builder;
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
index a04526a..81efa79 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -24,9 +24,8 @@
 import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
 import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -52,11 +51,10 @@
 import java.util.List;
 
 /**
- * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}.
+ * Unit tests for {@link SharedConnectivityService}.
  */
 @SmallTest
 public class SharedConnectivityServiceTest {
-    private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
     private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
             .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
             .setConnectionStrength(2).setBatteryPercentage(50).build();
@@ -64,12 +62,13 @@
             new TetherNetwork.Builder().setDeviceId(1).setDeviceInfo(DEVICE_INFO)
                     .setNetworkType(NETWORK_TYPE_CELLULAR).setNetworkName("TEST_NETWORK")
                     .setHotspotSsid("TEST_SSID").setHotspotBssid("TEST_BSSID")
-                    .setHotspotSecurityTypes(SECURITY_TYPES).build();
+                    .addHotspotSecurityType(SECURITY_TYPE_WEP)
+                    .addHotspotSecurityType(SECURITY_TYPE_EAP).build();
     private static final List<TetherNetwork> TETHER_NETWORKS = List.of(TETHER_NETWORK);
     private static final KnownNetwork KNOWN_NETWORK =
             new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE_NEARBY_SELF)
-                    .setSsid("TEST_SSID").setSecurityTypes(SECURITY_TYPES)
-                    .setDeviceInfo(DEVICE_INFO).build();
+                    .setSsid("TEST_SSID").addSecurityType(SECURITY_TYPE_WEP)
+                    .addSecurityType(SECURITY_TYPE_EAP).setDeviceInfo(DEVICE_INFO).build();
     private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
     private static final SharedConnectivitySettingsState SETTINGS_STATE =
             new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
@@ -111,7 +110,8 @@
     @Test
     public void onBind_isNotNull() {
         SharedConnectivityService service = createService();
-        assertNotNull(service.onBind(new Intent()));
+
+        assertThat(service.onBind(new Intent())).isNotNull();
     }
 
     @Test
@@ -121,7 +121,9 @@
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
 
         service.setTetherNetworks(TETHER_NETWORKS);
-        assertArrayEquals(TETHER_NETWORKS.toArray(), binder.getTetherNetworks().toArray());
+
+        assertThat(binder.getTetherNetworks())
+                .containsExactlyElementsIn(List.copyOf(TETHER_NETWORKS));
     }
 
     @Test
@@ -131,7 +133,9 @@
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
 
         service.setKnownNetworks(KNOWN_NETWORKS);
-        assertArrayEquals(KNOWN_NETWORKS.toArray(), binder.getKnownNetworks().toArray());
+
+        assertThat(binder.getKnownNetworks())
+                .containsExactlyElementsIn(List.copyOf(KNOWN_NETWORKS));
     }
 
     @Test
@@ -141,7 +145,8 @@
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
 
         service.setSettingsState(SETTINGS_STATE);
-        assertEquals(SETTINGS_STATE, binder.getSettingsState());
+
+        assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE);
     }
 
     @Test
@@ -151,7 +156,9 @@
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
 
         service.updateTetherNetworkConnectionStatus(TETHER_NETWORK_CONNECTION_STATUS);
-        assertEquals(TETHER_NETWORK_CONNECTION_STATUS, binder.getTetherNetworkConnectionStatus());
+
+        assertThat(binder.getTetherNetworkConnectionStatus())
+                .isEqualTo(TETHER_NETWORK_CONNECTION_STATUS);
     }
 
     @Test
@@ -161,7 +168,9 @@
                 (ISharedConnectivityService.Stub) service.onBind(new Intent());
 
         service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
-        assertEquals(KNOWN_NETWORK_CONNECTION_STATUS, binder.getKnownNetworkConnectionStatus());
+
+        assertThat(binder.getKnownNetworkConnectionStatus())
+                .isEqualTo(KNOWN_NETWORK_CONNECTION_STATUS);
     }
 
     private SharedConnectivityService createService() {