Merge "Revise logging in provider sessions" into udc-dev
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fbc69e3..eaa1dbe 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10117,7 +10117,7 @@
   public final class NetworkProviderInfo implements android.os.Parcelable {
     method public int describeContents();
     method @IntRange(from=0, to=100) public int getBatteryPercentage();
-    method @IntRange(from=0, to=3) public int getConnectionStrength();
+    method @IntRange(from=0, to=4) public int getConnectionStrength();
     method @NonNull public String getDeviceName();
     method public int getDeviceType();
     method @NonNull public android.os.Bundle getExtras();
@@ -10136,7 +10136,7 @@
     ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
-    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
     method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2dfda51..9b5e31a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1651,6 +1651,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
   }
 
+  public class SoundTrigger {
+    field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+  }
+
   public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
   }
@@ -1663,6 +1668,19 @@
     ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
   }
 
+  public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+    field public final boolean allowMultipleTriggers;
+    field public final int audioCapabilities;
+    field public final boolean captureRequested;
+    field @NonNull public final byte[] data;
+    field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
   }
@@ -2000,6 +2018,57 @@
 
 }
 
+package android.media.soundtrigger {
+
+  public final class SoundTriggerInstrumentation {
+    method public void setResourceContention(boolean);
+    method public void triggerOnResourcesAvailable();
+    method public void triggerRestart();
+  }
+
+  public static interface SoundTriggerInstrumentation.GlobalCallback {
+    method public default void onClientAttached();
+    method public default void onClientDetached();
+    method public default void onFrameworkDetached();
+    method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+    method public default void onPreempted();
+    method public default void onRestarted();
+  }
+
+  public static interface SoundTriggerInstrumentation.ModelCallback {
+    method public default void onModelUnloaded();
+    method public default void onParamSet(int, int);
+    method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+  }
+
+  public class SoundTriggerInstrumentation.ModelSession {
+    method public void clearModelCallback();
+    method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+    method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+    method public boolean isKeyphrase();
+    method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+    method public void triggerUnloadModel();
+  }
+
+  public static interface SoundTriggerInstrumentation.RecognitionCallback {
+    method public void onRecognitionStopped();
+  }
+
+  public class SoundTriggerInstrumentation.RecognitionSession {
+    method public void clearRecognitionCallback();
+    method public int getAudioSession();
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+    method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+    method public void triggerAbortRecognition();
+    method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+  }
+
+  public final class SoundTriggerManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+  }
+
+}
+
 package android.media.tv {
 
   public final class TvInputManager {
@@ -2024,6 +2093,14 @@
 
 }
 
+package android.media.voice {
+
+  public final class KeyphraseModelManager {
+    method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+  }
+
+}
+
 package android.net {
 
   public class NetworkPolicyManager {
@@ -2944,6 +3021,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
     method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+    method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
   }
 
   public static class VoiceInteractionSession.ActivityId {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb5..95e446d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@
      */
     public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
 
-    public abstract void updateOomAdj();
+    /**
+     * Oom Adj Reason: none - internal use only, do not use it.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_NONE = 0;
+
+    /**
+     * Oom Adj Reason: activity changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+    /**
+     * Oom Adj Reason: finishing a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+    /**
+     * Oom Adj Reason: starting a broadcast receiver.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+    /**
+     * Oom Adj Reason: binding to a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+    /**
+     * Oom Adj Reason: unbinding from a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+    /**
+     * Oom Adj Reason: starting a service.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+    /**
+     * Oom Adj Reason: connecting to a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+    /**
+     * Oom Adj Reason: disconnecting from a content provider.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+    /**
+     * Oom Adj Reason: UI visibility changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+    /**
+     * Oom Adj Reason: device power allowlist changes.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+    /**
+     * Oom Adj Reason: starting a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+    /**
+     * Oom Adj Reason: ending a process.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+    /**
+     * Oom Adj Reason: short FGS timeout.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+    /**
+     * Oom Adj Reason: system initialization.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+    /**
+     * Oom Adj Reason: backup/restore.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+    /**
+     * Oom Adj Reason: instrumented by the SHELL.
+     * @hide
+     */
+    public static final int OOM_ADJ_REASON_SHELL = 16;
+
+    /**
+     * Oom Adj Reason: task stack is being removed.
+     */
+    public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+    /**
+     * Oom Adj Reason: uid idle.
+     */
+    public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+    /**
+     * Oom Adj Reason: stop service.
+     */
+    public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+    /**
+     * Oom Adj Reason: executing service.
+     */
+    public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+    /**
+     * Oom Adj Reason: background restriction changes.
+     */
+    public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+    /**
+     * Oom Adj Reason: A package or its component is disabled.
+     */
+    public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+    @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+        OOM_ADJ_REASON_NONE,
+        OOM_ADJ_REASON_ACTIVITY,
+        OOM_ADJ_REASON_FINISH_RECEIVER,
+        OOM_ADJ_REASON_START_RECEIVER,
+        OOM_ADJ_REASON_BIND_SERVICE,
+        OOM_ADJ_REASON_UNBIND_SERVICE,
+        OOM_ADJ_REASON_START_SERVICE,
+        OOM_ADJ_REASON_GET_PROVIDER,
+        OOM_ADJ_REASON_REMOVE_PROVIDER,
+        OOM_ADJ_REASON_UI_VISIBILITY,
+        OOM_ADJ_REASON_ALLOWLIST,
+        OOM_ADJ_REASON_PROCESS_BEGIN,
+        OOM_ADJ_REASON_PROCESS_END,
+        OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+        OOM_ADJ_REASON_SYSTEM_INIT,
+        OOM_ADJ_REASON_BACKUP,
+        OOM_ADJ_REASON_SHELL,
+        OOM_ADJ_REASON_REMOVE_TASK,
+        OOM_ADJ_REASON_UID_IDLE,
+        OOM_ADJ_REASON_STOP_SERVICE,
+        OOM_ADJ_REASON_EXECUTING_SERVICE,
+        OOM_ADJ_REASON_RESTRICTION_CHANGE,
+        OOM_ADJ_REASON_COMPONENT_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OomAdjReason {}
+
+    /**
+     * Request to update oom adj.
+     */
+    public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
     public abstract void updateCpuStats();
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb..1dddf06 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1461,9 +1461,25 @@
      */
     public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
 
+    /**
+     * Hides camera indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_CAMERA_SANDBOXED =
+            AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+    /**
+     * Hides microphone indicator for sandboxed detection apps that directly access the service.
+     *
+     * @hide
+     */
+    public static final int OP_RECORD_AUDIO_SANDBOXED =
+            AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 134;
+    public static final int _NUM_OP = 136;
 
     /**
      * All app ops represented as strings.
@@ -1605,6 +1621,8 @@
             OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
             OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
             OPSTR_USE_FULL_SCREEN_INTENT,
+            OPSTR_CAMERA_SANDBOXED,
+            OPSTR_RECORD_AUDIO_SANDBOXED
     })
     public @interface AppOpString {}
 
@@ -2013,6 +2031,20 @@
     public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
 
     /**
+     * Camera is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+    /**
+     * Audio is being recorded in sandboxed detection process.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+    /**
      * Allow apps to create the requests to manage the media files without user confirmation.
      *
      * @see android.Manifest.permission#MANAGE_MEDIA
@@ -2738,7 +2770,11 @@
                 .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
         new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
                 "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
-                .build()
+                .build(),
+        new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+            "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+                "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e16..6d43ddf 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@
             return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
         }
     }
+    /**
+     * SoundTrigger model parameter types.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+            MODEL_PARAM_INVALID,
+            MODEL_PARAM_THRESHOLD_FACTOR
+    })
+    public @interface ModelParamTypes {}
+
+    /**
+     * See {@link ModelParams.INVALID}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+    /**
+     * See {@link ModelParams.THRESHOLD_FACTOR}
+     * @hide
+     */
+    @TestApi
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
 
     /**
      * Modes for key phrase recognition
@@ -1450,7 +1473,8 @@
      *
      *  @hide
      */
-    public static class RecognitionConfig implements Parcelable {
+    @TestApi
+    public static final class RecognitionConfig implements Parcelable {
         /** True if the DSP should capture the trigger sound and make it available for further
          * capture. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@
          * options for each keyphrase. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         @NonNull
+        @SuppressLint("ArrayReturn")
         public final KeyphraseRecognitionExtra keyphrases[];
         /** Opaque data for use by system applications who know about voice engine internals,
          * typically during enrollment. */
@@ -1479,8 +1504,8 @@
         public final int audioCapabilities;
 
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
-                int audioCapabilities) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data, int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@
 
         @UnsupportedAppUsage
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+                @Nullable byte[] data) {
             this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
@@ -1517,7 +1543,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeByte((byte) (captureRequested ? 1 : 0));
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28..828c062 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.PluralsMessageFormatter;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
@@ -310,86 +308,6 @@
         return buffer.toString();
     }
 
-    public Diff diff(ZenModeConfig to) {
-        final Diff d = new Diff();
-        if (to == null) {
-            return d.addLine("config", "delete");
-        }
-        if (user != to.user) {
-            d.addLine("user", user, to.user);
-        }
-        if (allowAlarms != to.allowAlarms) {
-            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
-        }
-        if (allowMedia != to.allowMedia) {
-            d.addLine("allowMedia", allowMedia, to.allowMedia);
-        }
-        if (allowSystem != to.allowSystem) {
-            d.addLine("allowSystem", allowSystem, to.allowSystem);
-        }
-        if (allowCalls != to.allowCalls) {
-            d.addLine("allowCalls", allowCalls, to.allowCalls);
-        }
-        if (allowReminders != to.allowReminders) {
-            d.addLine("allowReminders", allowReminders, to.allowReminders);
-        }
-        if (allowEvents != to.allowEvents) {
-            d.addLine("allowEvents", allowEvents, to.allowEvents);
-        }
-        if (allowRepeatCallers != to.allowRepeatCallers) {
-            d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
-        }
-        if (allowMessages != to.allowMessages) {
-            d.addLine("allowMessages", allowMessages, to.allowMessages);
-        }
-        if (allowCallsFrom != to.allowCallsFrom) {
-            d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
-        }
-        if (allowMessagesFrom != to.allowMessagesFrom) {
-            d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
-        }
-        if (suppressedVisualEffects != to.suppressedVisualEffects) {
-            d.addLine("suppressedVisualEffects", suppressedVisualEffects,
-                    to.suppressedVisualEffects);
-        }
-        final ArraySet<String> allRules = new ArraySet<>();
-        addKeys(allRules, automaticRules);
-        addKeys(allRules, to.automaticRules);
-        final int N = allRules.size();
-        for (int i = 0; i < N; i++) {
-            final String rule = allRules.valueAt(i);
-            final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
-            final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
-            ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
-        }
-        ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
-        if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
-            d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
-                    to.areChannelsBypassingDnd);
-        }
-        return d;
-    }
-
-    public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
-        if (from == null) {
-            final Diff d = new Diff();
-            if (to != null) {
-                d.addLine("config", "insert");
-            }
-            return d;
-        }
-        return from.diff(to);
-    }
-
-    private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
-        if (map != null) {
-            for (int i = 0; i < map.size(); i++) {
-                set.add(map.keyAt(i));
-            }
-        }
-    }
-
     public boolean isValid() {
         if (!isValidManualRule(manualRule)) return false;
         final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@
             proto.end(token);
         }
 
-        private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
-            if (d == null) return;
-            if (from == null) {
-                if (to != null) {
-                    d.addLine(item, "insert");
-                }
-                return;
-            }
-            from.appendDiff(d, item, to);
-        }
-
-        private void appendDiff(Diff d, String item, ZenRule to) {
-            if (to == null) {
-                d.addLine(item, "delete");
-                return;
-            }
-            if (enabled != to.enabled) {
-                d.addLine(item, "enabled", enabled, to.enabled);
-            }
-            if (snoozing != to.snoozing) {
-                d.addLine(item, "snoozing", snoozing, to.snoozing);
-            }
-            if (!Objects.equals(name, to.name)) {
-                d.addLine(item, "name", name, to.name);
-            }
-            if (zenMode != to.zenMode) {
-                d.addLine(item, "zenMode", zenMode, to.zenMode);
-            }
-            if (!Objects.equals(conditionId, to.conditionId)) {
-                d.addLine(item, "conditionId", conditionId, to.conditionId);
-            }
-            if (!Objects.equals(condition, to.condition)) {
-                d.addLine(item, "condition", condition, to.condition);
-            }
-            if (!Objects.equals(component, to.component)) {
-                d.addLine(item, "component", component, to.component);
-            }
-            if (!Objects.equals(configurationActivity, to.configurationActivity)) {
-                d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
-            }
-            if (!Objects.equals(id, to.id)) {
-                d.addLine(item, "id", id, to.id);
-            }
-            if (creationTime != to.creationTime) {
-                d.addLine(item, "creationTime", creationTime, to.creationTime);
-            }
-            if (!Objects.equals(enabler, to.enabler)) {
-                d.addLine(item, "enabler", enabler, to.enabler);
-            }
-            if (!Objects.equals(zenPolicy, to.zenPolicy)) {
-                d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
-            }
-            if (modified != to.modified) {
-                d.addLine(item, "modified", modified, to.modified);
-            }
-            if (!Objects.equals(pkg, to.pkg)) {
-                d.addLine(item, "pkg", pkg, to.pkg);
-            }
-        }
-
         @Override
         public boolean equals(@Nullable Object o) {
             if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@
         };
     }
 
-    public static class Diff {
-        private final ArrayList<String> lines = new ArrayList<>();
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("Diff[");
-            final int N = lines.size();
-            for (int i = 0; i < N; i++) {
-                if (i > 0) {
-                    sb.append(",\n");
-                }
-                sb.append(lines.get(i));
-            }
-            return sb.append(']').toString();
-        }
-
-        private Diff addLine(String item, String action) {
-            lines.add(item + ":" + action);
-            return this;
-        }
-
-        public Diff addLine(String item, String subitem, Object from, Object to) {
-            return addLine(item + "." + subitem, from, to);
-        }
-
-        public Diff addLine(String item, Object from, Object to) {
-            return addLine(item, from + "->" + to);
-        }
-
-        public boolean isEmpty() {
-            return lines.isEmpty();
-        }
-    }
-
     /**
      * Determines whether dnd behavior should mute all ringer-controlled sounds
      * This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 0000000..c7b89eb
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * 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 android.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+    /**
+     * Enum representing whether the existence of a config or rule has changed (added or removed,
+     * or "none" meaning there is no change, which may either mean both null, or there exists a
+     * diff in fields rather than add/remove).
+     */
+    @IntDef(value = {
+            NONE,
+            ADDED,
+            REMOVED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ExistenceChange{}
+
+    public static final int NONE = 0;
+    public static final int ADDED = 1;
+    public static final int REMOVED = 2;
+
+    /**
+     * Diff class representing an individual field diff.
+     * @param <T> The type of the field.
+     */
+    public static class FieldDiff<T> {
+        private final T mFrom;
+        private final T mTo;
+
+        /**
+         * Constructor to create a FieldDiff object with the given values.
+         * @param from from (old) value
+         * @param to to (new) value
+         */
+        public FieldDiff(@Nullable T from, @Nullable T to) {
+            mFrom = from;
+            mTo = to;
+        }
+
+        /**
+         * Get the "from" value
+         */
+        public T from() {
+            return mFrom;
+        }
+
+        /**
+         * Get the "to" value
+         */
+        public T to() {
+            return mTo;
+        }
+
+        /**
+         * Get the string representation of this field diff, in the form of "from->to".
+         */
+        @Override
+        public String toString() {
+            return mFrom + "->" + mTo;
+        }
+
+        /**
+         * Returns whether this represents an actual diff.
+         */
+        public boolean hasDiff() {
+            // note that Objects.equals handles null values gracefully.
+            return !Objects.equals(mFrom, mTo);
+        }
+    }
+
+    /**
+     * Base diff class that contains info about whether something was added, and a set of named
+     * fields that changed.
+     * Extend for diffs of specific types of objects.
+     */
+    private abstract static class BaseDiff {
+        // Whether the diff was added or removed
+        @ExistenceChange private int mExists = NONE;
+
+        // Map from field name to diffs for any standalone fields in the object.
+        private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+        // Functions for actually diffing objects and string representations have to be implemented
+        // by subclasses.
+
+        /**
+         * Return whether this diff represents any changes.
+         */
+        public abstract boolean hasDiff();
+
+        /**
+         * Return a string representation of the diff.
+         */
+        public abstract String toString();
+
+        /**
+         * Constructor that takes the two objects meant to be compared. This constructor sets
+         * whether there is an existence change (added or removed).
+         * @param from previous Object
+         * @param to new Object
+         */
+        BaseDiff(Object from, Object to) {
+            if (from == null) {
+                if (to != null) {
+                    mExists = ADDED;
+                }
+                // If both are null, there isn't an existence change; callers/inheritors must handle
+                // the both null case.
+            } else if (to == null) {
+                // in this case, we know that from != null
+                mExists = REMOVED;
+            }
+
+            // Subclasses should implement the actual diffing functionality in their own
+            // constructors.
+        }
+
+        /**
+         * Add a diff for a specific field to the map.
+         * @param name field name
+         * @param diff FieldDiff object representing the diff
+         */
+        final void addField(String name, FieldDiff diff) {
+            mFields.put(name, diff);
+        }
+
+        /**
+         * Returns whether this diff represents a config being newly added.
+         */
+        public final boolean wasAdded() {
+            return mExists == ADDED;
+        }
+
+        /**
+         * Returns whether this diff represents a config being removed.
+         */
+        public final boolean wasRemoved() {
+            return mExists == REMOVED;
+        }
+
+        /**
+         * Returns whether this diff represents an object being either added or removed.
+         */
+        public final boolean hasExistenceChange() {
+            return mExists != NONE;
+        }
+
+        /**
+         * Returns whether there are any individual field diffs.
+         */
+        public final boolean hasFieldDiffs() {
+            return mFields.size() > 0;
+        }
+
+        /**
+         * Returns the diff for the specific named field if it exists
+         */
+        public final FieldDiff getDiffForField(String name) {
+            return mFields.getOrDefault(name, null);
+        }
+
+        /**
+         * Get the set of all field names with some diff.
+         */
+        public final Set<String> fieldNamesWithDiff() {
+            return mFields.keySet();
+        }
+    }
+
+    /**
+     * Diff class representing a diff between two ZenModeConfigs.
+     */
+    public static class ConfigDiff extends BaseDiff {
+        // Rules. Automatic rule map is keyed by the rule name.
+        private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+        private RuleDiff mManualRuleDiff;
+
+        // Helpers for string generation
+        private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+        private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+        private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+        private static final Set<String> PEOPLE_TYPE_FIELDS =
+                Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+        /**
+         * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+         *
+         * @param from previous ZenModeConfig
+         * @param to   new ZenModeConfig
+         */
+        public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+            super(from, to);
+            // If both are null skip
+            if (from == null && to == null) {
+                return;
+            }
+            if (hasExistenceChange()) {
+                // either added or removed; return here. otherwise (they're not both null) there's
+                // field diffs.
+                return;
+            }
+
+            // Now we compare all the fields, knowing there's a diff and that neither is null
+            if (from.user != to.user) {
+                addField("user", new FieldDiff<>(from.user, to.user));
+            }
+            if (from.allowAlarms != to.allowAlarms) {
+                addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+            }
+            if (from.allowMedia != to.allowMedia) {
+                addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+            }
+            if (from.allowSystem != to.allowSystem) {
+                addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+            }
+            if (from.allowCalls != to.allowCalls) {
+                addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+            }
+            if (from.allowReminders != to.allowReminders) {
+                addField("allowReminders",
+                        new FieldDiff<>(from.allowReminders, to.allowReminders));
+            }
+            if (from.allowEvents != to.allowEvents) {
+                addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+            }
+            if (from.allowRepeatCallers != to.allowRepeatCallers) {
+                addField("allowRepeatCallers",
+                        new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+            }
+            if (from.allowMessages != to.allowMessages) {
+                addField("allowMessages",
+                        new FieldDiff<>(from.allowMessages, to.allowMessages));
+            }
+            if (from.allowConversations != to.allowConversations) {
+                addField("allowConversations",
+                        new FieldDiff<>(from.allowConversations, to.allowConversations));
+            }
+            if (from.allowCallsFrom != to.allowCallsFrom) {
+                addField("allowCallsFrom",
+                        new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+            }
+            if (from.allowMessagesFrom != to.allowMessagesFrom) {
+                addField("allowMessagesFrom",
+                        new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+            }
+            if (from.allowConversationsFrom != to.allowConversationsFrom) {
+                addField("allowConversationsFrom",
+                        new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+            }
+            if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+                addField("suppressedVisualEffects",
+                        new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+            }
+            if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+                addField("areChannelsBypassingDnd",
+                        new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+            }
+
+            // Compare automatic and manual rules
+            final ArraySet<String> allRules = new ArraySet<>();
+            addKeys(allRules, from.automaticRules);
+            addKeys(allRules, to.automaticRules);
+            final int num = allRules.size();
+            for (int i = 0; i < num; i++) {
+                final String rule = allRules.valueAt(i);
+                final ZenModeConfig.ZenRule
+                        fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+                        : null;
+                final ZenModeConfig.ZenRule
+                        toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+                RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+                if (ruleDiff.hasDiff()) {
+                    mAutomaticRulesDiff.put(rule, ruleDiff);
+                }
+            }
+            // If there's no diff this may turn out to be null, but that's also fine
+            RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+            if (manualRuleDiff.hasDiff()) {
+                mManualRuleDiff = manualRuleDiff;
+            }
+        }
+
+        private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+            if (map != null) {
+                for (int i = 0; i < map.size(); i++) {
+                    set.add(map.keyAt(i));
+                }
+            }
+        }
+
+        /**
+         * Returns whether this diff object contains any diffs in any field.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange()
+                    || hasFieldDiffs()
+                    || mManualRuleDiff != null
+                    || mAutomaticRulesDiff.size() > 0;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("Diff[");
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Handle top-level field change
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+
+                // Some special handling for people- and conversation-type fields for readability
+                if (PEOPLE_TYPE_FIELDS.contains(key)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+                } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+                    sb.append("->");
+                    sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+                } else {
+                    sb.append(key);
+                    sb.append(":");
+                    sb.append(diff);
+                }
+            }
+
+            // manual rule
+            if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",\n");
+                }
+                sb.append("manualRule:");
+                sb.append(mManualRuleDiff);
+            }
+
+            // automatic rules
+            for (String rule : mAutomaticRulesDiff.keySet()) {
+                RuleDiff diff = mAutomaticRulesDiff.get(rule);
+                if (diff != null && diff.hasDiff()) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        sb.append(",\n");
+                    }
+                    sb.append("automaticRule[");
+                    sb.append(rule);
+                    sb.append("]:");
+                    sb.append(diff);
+                }
+            }
+
+            return sb.append(']').toString();
+        }
+
+        /**
+         * Get the diff in manual rule, if it exists.
+         */
+        public RuleDiff getManualRuleDiff() {
+            return mManualRuleDiff;
+        }
+
+        /**
+         * Get the full map of automatic rule diffs, or null if there are no diffs.
+         */
+        public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+            return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+        }
+    }
+
+    /**
+     * Diff class representing a change between two ZenRules.
+     */
+    public static class RuleDiff extends BaseDiff {
+        /**
+         * Create a RuleDiff representing the difference between two ZenRule objects.
+         * @param from previous ZenRule
+         * @param to new ZenRule
+         * @return The diff between the two given ZenRules
+         */
+        public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+            super(from, to);
+            // Short-circuit the both-null case
+            if (from == null && to == null) {
+                return;
+            }
+            // Return if the diff was added or removed
+            if (hasExistenceChange()) {
+                return;
+            }
+
+            if (from.enabled != to.enabled) {
+                addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+            }
+            if (from.snoozing != to.snoozing) {
+                addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+            }
+            if (!Objects.equals(from.name, to.name)) {
+                addField("name", new FieldDiff<>(from.name, to.name));
+            }
+            if (from.zenMode != to.zenMode) {
+                addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+            }
+            if (!Objects.equals(from.conditionId, to.conditionId)) {
+                addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+            }
+            if (!Objects.equals(from.condition, to.condition)) {
+                addField("condition", new FieldDiff<>(from.condition, to.condition));
+            }
+            if (!Objects.equals(from.component, to.component)) {
+                addField("component", new FieldDiff<>(from.component, to.component));
+            }
+            if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+                addField("configurationActivity", new FieldDiff<>(
+                        from.configurationActivity, to.configurationActivity));
+            }
+            if (!Objects.equals(from.id, to.id)) {
+                addField("id", new FieldDiff<>(from.id, to.id));
+            }
+            if (from.creationTime != to.creationTime) {
+                addField("creationTime",
+                        new FieldDiff<>(from.creationTime, to.creationTime));
+            }
+            if (!Objects.equals(from.enabler, to.enabler)) {
+                addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+            }
+            if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+                addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+            }
+            if (from.modified != to.modified) {
+                addField("modified", new FieldDiff<>(from.modified, to.modified));
+            }
+            if (!Objects.equals(from.pkg, to.pkg)) {
+                addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+            }
+        }
+
+        /**
+         * Returns whether this object represents an actual diff.
+         */
+        @Override
+        public boolean hasDiff() {
+            return hasExistenceChange() || hasFieldDiffs();
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+            // If there's no diff, probably we haven't actually let this object continue existing
+            // but might as well handle this case.
+            if (!hasDiff()) {
+                sb.append("no changes");
+            }
+
+            // If added or deleted, then that's just the end of it
+            if (hasExistenceChange()) {
+                if (wasAdded()) {
+                    sb.append("added");
+                } else if (wasRemoved()) {
+                    sb.append("removed");
+                }
+            }
+
+            // Go through all of the individual fields
+            boolean first = true;
+            for (String key : fieldNamesWithDiff()) {
+                FieldDiff diff = getDiffForField(key);
+                if (diff == null) {
+                    // this shouldn't happen, but
+                    continue;
+                }
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(", ");
+                }
+
+                sb.append(key);
+                sb.append(":");
+                sb.append(diff);
+            }
+
+            return sb.append("}").toString();
+        }
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b0..68cce4a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@
 
     private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
 
+    // True if any of the createAOHD methods should use the test ST module.
+    @GuardedBy("mLock")
+    private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
 
     private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
         Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ false, /* options= */ null,
-                /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+                /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
      * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
      * module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -645,14 +649,14 @@
         Objects.requireNonNull(callback);
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
-                /* moduleProperties */ null, executor, callback);
+                /* moduleProperties= */ null, executor, callback);
     }
 
     /**
      * Same as {@link createAlwaysOnHotwordDetector(String, Locale,
      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
      * but allow explicit selection of the underlying ST module to attach to.
-     * Use {@link listModuleProperties} to get available modules to attach to.
+     * Use {@link #listModuleProperties()} to get available modules to attach to.
      * @hide
      */
     @TestApi
@@ -717,6 +721,10 @@
 
             try {
                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+                // Check if we are currently overridden, and should use the test module.
+                if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+                    moduleProperties = getTestModuleProperties();
+                }
                 // If moduleProperties is null, the default STModule is used.
                 dspDetector.initialize(options, sharedMemory, moduleProperties);
             } catch (Exception e) {
@@ -990,6 +998,44 @@
         return mKeyphraseEnrollmentInfo;
     }
 
+
+    /**
+     * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+     * and similar overloads to utilize the test SoundTrigger module instead of the
+     * actual DSP module.
+     * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+     * objects should attach to a test module. {@code false} if subsequently created
+     * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+     * @hide
+     */
+    @TestApi
+    public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+        synchronized (mLock) {
+            mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+        }
+    }
+
+    /**
+     * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+     * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+     * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+     * similar overloads for test purposes.
+     * @return ModuleProperties to use for test purposes.
+     */
+    private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+        var moduleProps = listModuleProperties()
+                .stream()
+                .filter((SoundTrigger.ModuleProperties prop)
+                        -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+                .findFirst()
+                .orElse(null);
+        if (moduleProps == null) {
+            throw new IllegalStateException("Fake ST HAL should always be available");
+        }
+        return moduleProps;
+    }
+
     /**
      * Checks if a given keyphrase and locale are supported to create an
      * {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f5cd54..055b5cb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@
                 return;
             }
             final boolean needsStylusPointerIcon = event.isStylusPointer()
+                    && event.isHoverEvent()
                     && mInputManager.isStylusPointerIconEnabled();
             if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                 if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602..ed751cb 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
 
 package com.android.internal.app;
 
-import android.media.permission.Identity;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import com.android.internal.app.ISoundTriggerSession;
 
 /**
@@ -74,4 +75,8 @@
      */
     List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
 
+    /**
+     * Attach an HAL injection interface.
+     */
+     void attachInjection(ISoundTriggerInjection injection);
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d98..24d5afc 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@
      * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
      */
     int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} to enable the test database. {@code false} to enable
+     * the real, persistent database.
+     * @param token - IBinder used to register a death listener to clean-up the override
+     * if tests do not clean up gracefully.
+     */
+    @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+    @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+            "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+    void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
     /**
      * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
      * user ID of the caller.
@@ -106,6 +121,7 @@
      * @param bcp47Locale The BCP47 language tag  for the keyphrase's locale.
      */
     boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
     /**
      * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
      * and the user ID of the caller.
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 980211f..c6bb07b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.os.Bundle;
+import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.util.PollingCheck;
@@ -84,6 +85,7 @@
         }
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testFontsScaleNonLinearly() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -114,6 +116,7 @@
         )));
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testOnConfigurationChanged_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -127,6 +130,7 @@
         });
     }
 
+    @IwTest(focusArea = "accessibility")
     @Test
     public void testUpdateConfiguration_doesNotCrash() {
         final ActivityScenario<TestActivity> scenario = rule.getScenario();
diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING
index 4ea6e40..ab14950 100644
--- a/core/tests/coretests/src/android/content/res/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING
@@ -39,5 +39,18 @@
         }
       ]
     }
+  ],
+  "ironwood-postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options":[
+        {
+            "include-annotation": "android.platform.test.annotations.IwTest"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
   ]
 }
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 25b074d..2307d60 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@
     /**
      * This is called by methods that want to throw an exception if the bitmap
      * has already been recycled.
+     * @hide
      */
-    private void checkRecycled(String errorMessage) {
+    void checkRecycled(String errorMessage) {
         if (mRecycled) {
             throw new IllegalStateException(errorMessage);
         }
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd46..5c06577 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@
         if (bitmap == null) {
             throw new IllegalArgumentException("Bitmap must be non-null");
         }
+        bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
         mBitmap = bitmap;
         mTileX = tileX;
         mTileY = tileY;
@@ -188,6 +189,8 @@
     /** @hide */
     @Override
     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+        mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
         boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
         if (mFilterMode == FILTER_MODE_DEFAULT) {
             mFilterFromPaint = filterFromPaint;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index dd91a37..04cb17c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -690,6 +690,10 @@
         if (options1 == null) options1 = new Bundle();
         if (taskId2 == INVALID_TASK_ID) {
             // Launching a solo task.
+            // Exit split first if this task under split roots.
+            if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+            }
             ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
             activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
             options1 = activityOptions.toBundle();
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2a8cb42..c4d3f5c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
 }
 
 MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+    bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
     // In case the surface was destroyed (e.g. a previous trimMemory call) we
     // need to recreate it here.
     if (mHardwareBuffer) {
@@ -65,6 +67,37 @@
     if (!mEglManager.makeCurrent(mEglSurface, &error)) {
         return MakeCurrentResult::AlreadyCurrent;
     }
+
+    // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+    // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+    // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+    // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+    // The discussion was not resolved with a clear consensus
+    if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+        GLint curReadFB = 0;
+        GLint curDrawFB = 0;
+        glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+        GLint buffer = GL_NONE;
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+        if (buffer == GL_NONE) {
+            const GLenum drawBuffer = GL_BACK;
+            glDrawBuffers(1, &drawBuffer);
+        }
+
+        glGetIntegerv(GL_READ_BUFFER, &buffer);
+        if (buffer == GL_NONE) {
+            glReadBuffer(GL_BACK);
+        }
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+        GL_CHECKPOINT(LOW);
+    }
+
     return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
 }
 
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 0000000..80bc5c0
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * 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 android.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+    @GuardedBy("mLock")
+    private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+    @GuardedBy("mLock")
+    private IBinder mClientToken = null;
+
+    private final GlobalCallback mClientCallback;
+    private final Executor mGlobalCallbackExecutor;
+
+    /**
+     * Callback interface for un-sessioned events observed from the fake STHAL.
+     * Registered upon construction of {@link SoundTriggerInstrumentation}
+     * @hide
+     */
+    @TestApi
+    public interface GlobalCallback {
+        /**
+         * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+         * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+         * fake STHAL. Only one client may inject at a time.
+         * All sessions are invalidated, no further events will be received, and no
+         * injected events will be delivered.
+         */
+        default void onPreempted() {}
+        /**
+         * Called when the STHAL has been restarted by the framework, due to unexpected
+         * error conditions.
+         * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+         */
+        default void onRestarted() {}
+        /**
+         * Called when the framework detaches from the fake HAL.
+         * This is not transmitted to real HALs, but it indicates that the
+         * framework has flushed its global state.
+         */
+        default void onFrameworkDetached() {}
+        /**
+         * Called when a client application attaches to the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientAttached() {}
+        /**
+         * Called when a client application detaches from the framework.
+         * This is not transmitted to real HALs, but it represents the state of
+         * the framework.
+         */
+        default void onClientDetached() {}
+        /**
+         * Called when the fake HAL receives a model load from the framework.
+         * @param modelSession - A session which exposes additional injection
+         *                       functionality associated with the newly loaded
+         *                       model. See {@link ModelSession}.
+         */
+        void onModelLoaded(@NonNull ModelSession modelSession);
+    }
+
+    /**
+     * Callback for HAL events related to a loaded model. Register with
+     * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface ModelCallback {
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for was unloaded by the framework.
+         */
+        default void onModelUnloaded() {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a set parameter call from the framework.
+         * @param param - Parameter being set.
+         *                 See {@link SoundTrigger.ModelParamTypes}
+         * @param value - Value the model parameter was set to.
+         */
+        default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+        /**
+         * Called when the model associated with the {@link ModelSession} this callback
+         * was registered for receives a recognition start request.
+         * @param recognitionSession - A session which exposes additional injection
+         *                             functionality associated with the newly started
+         *                             recognition. See {@link RecognitionSession}
+         */
+        void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+    }
+
+    /**
+     * Callback for HAL events related to a started recognition. Register with
+     * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+     * Note, callbacks will not be delivered for events triggered by the injection.
+     * @hide
+     */
+    @TestApi
+    public interface RecognitionCallback {
+        /**
+         * Called when the recognition associated with the {@link RecognitionSession} this
+         * callback was registered for was stopped by the framework.
+         */
+        void onRecognitionStopped();
+    }
+
+    /**
+     * Session associated with a loaded model in the fake STHAL.
+     * Can be used to query details about the loaded model, register a callback for future
+     * model events, or trigger HAL events associated with a loaded model.
+     * This session is invalid once the model is unloaded, caused by a
+     * {@link ModelSession#triggerUnloadModel()},
+     * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+     * received.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class ModelSession {
+
+        /**
+         * Trigger the HAL to preemptively unload the model associated with this session.
+         * Typically occurs when a higher priority model is loaded which utilizes the same
+         * resources.
+         */
+        public void triggerUnloadModel() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                try {
+                    mInjectModelEvent.triggerUnloadModel();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mModelSessionMap.remove(mInjectModelEvent.asBinder());
+            }
+        }
+
+        /**
+         * Get the {@link SoundTriggerManager.Model} associated with this session.
+         * @return - The model associated with this session.
+         */
+        public @NonNull SoundTriggerManager.Model getSoundModel() {
+            return mModel;
+        }
+
+        /**
+         * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+         * @return - The keyphrases associated with this session.
+         */
+        public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+            if (mPhrases == null) {
+                return new ArrayList<>();
+            } else {
+                return new ArrayList<>(Arrays.asList(mPhrases));
+            }
+        }
+
+        /**
+         * Get whether this model is of keyphrase type.
+         * @return - true if the model is a keyphrase model, false otherwise
+         */
+        public boolean isKeyphrase() {
+            return (mPhrases != null);
+        }
+
+        /**
+         * Registers the model callback associated with this session. Events associated
+         * with this model session will be reported via this callback.
+         * See {@link ModelCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Model callback for reporting model session events.
+         */
+        public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+                ModelCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = Objects.requireNonNull(callback);
+                mModelExecutor = Objects.requireNonNull(executor);
+            }
+        }
+
+        /**
+         * Clear the model callback associated with this session, if any has been
+         * set by {@link #setModelCallback(Executor, ModelCallback)}.
+         */
+        public void clearModelCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mModelCallback = null;
+                mModelExecutor = null;
+            }
+        }
+
+        private ModelSession(SoundModel model, Phrase[] phrases,
+                IInjectModelEvent injection) {
+            mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+                    UUID.fromString(model.vendorUuid),
+                    ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+            if (phrases != null) {
+                mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+                int i = 0;
+                for (var phrase : phrases) {
+                    mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+                }
+            } else {
+                mPhrases = null;
+            }
+            mInjectModelEvent = injection;
+        }
+
+        private void wrap(Consumer<ModelCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mModelCallback != null && mModelExecutor != null) {
+                    final ModelCallback callback = mModelCallback;
+                    mModelExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final SoundTriggerManager.Model mModel;
+        private final SoundTrigger.Keyphrase[] mPhrases;
+        private final IInjectModelEvent mInjectModelEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private ModelCallback mModelCallback = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mModelExecutor = null;
+    }
+
+    /**
+     * Session associated with a recognition start in the fake STHAL.
+     * Can be used to get information about the started recognition, register a callback
+     * for future events associated with this recognition, and triggering
+     * recognition events or aborts.
+     * This session is invalid once the recognition is stopped, caused by a
+     * {@link RecognitionSession#triggerAbortRecognition()},
+     * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+     * the client stopping recognition, or any operation which invalidates the
+     * {@link ModelSession} which the session was created from.
+     * Further injections on an invalidated session will not be respected, and no future
+     * callbacks will be delivered.
+     * @hide
+     */
+    @TestApi
+    public class RecognitionSession {
+
+        /**
+         * Get an integer token representing the audio session associated with this
+         * recognition in the STHAL.
+         * @return - The session token.
+         */
+        public int getAudioSession() {
+            return mAudioSession;
+        }
+
+        /**
+         * Get the recognition config used to start this recognition.
+         * @return - The config passed to the HAL for startRecognition.
+         */
+        public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+            return mRecognitionConfig;
+        }
+
+        /**
+         * Trigger a recognition in the fake STHAL.
+         * @param data - The opaque data buffer included in the recognition event.
+         * @param phraseExtras - Keyphrase metadata included in the event. The
+         *                       event must include metadata for the keyphrase id
+         *                       associated with this model to be received by the
+         *                       client application.
+         */
+        public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+                List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+            PhraseRecognitionExtra[] converted = null;
+            if (phraseExtras != null) {
+                converted = new PhraseRecognitionExtra[phraseExtras.size()];
+                int i = 0;
+                for (var phraseExtra : phraseExtras) {
+                    converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+                }
+            }
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Trigger an abort recognition event in the fake HAL. This represents a
+         * preemptive ending of the recognition session by the HAL, despite no
+         * recognition detection. Typically occurs during contention for microphone
+         * usage, or if model limits are hit.
+         * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+         * subsequent downward calls for contention reasons.
+         */
+        public void triggerAbortRecognition() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+                try {
+                    mInjectRecognitionEvent.triggerAbortRecognition();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+         /**
+         * Registers the recognition callback associated with this session. Events associated
+         * with this recognition session will be reported via this callback.
+         * See {@link RecognitionCallback}
+         * @param executor - Executor which the callback is dispatched on
+         * @param callback - Recognition callback for reporting recognition session events.
+         */
+        public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+                @NonNull RecognitionCallback callback) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = callback;
+                mRecognitionExecutor = executor;
+            }
+        }
+
+        /**
+         * Clear the recognition callback associated with this session, if any has been
+         * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+         */
+        public void clearRecognitionCallback() {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mRecognitionCallback = null;
+                mRecognitionExecutor = null;
+            }
+        }
+
+        private RecognitionSession(int audioSession,
+                RecognitionConfig recognitionConfig,
+                IInjectRecognitionEvent injectRecognitionEvent) {
+            mAudioSession = audioSession;
+            mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+            mInjectRecognitionEvent = injectRecognitionEvent;
+        }
+
+        private void wrap(Consumer<RecognitionCallback> consumer) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+                    final RecognitionCallback callback = mRecognitionCallback;
+                    mRecognitionExecutor.execute(() -> consumer.accept(callback));
+                }
+            }
+        }
+
+        private final int mAudioSession;
+        private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+        private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private Executor mRecognitionExecutor = null;
+        @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+        private RecognitionCallback mRecognitionCallback = null;
+    }
+
+    // Implementation of injection interface passed to the HAL.
+    // This class will re-associate events received on this callback interface
+    // with sessions, to avoid staleness issues.
+    private class Injection extends ISoundTriggerInjection.Stub {
+        @Override
+        public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                mInjectGlobalEvent = globalInjection;
+            }
+        }
+
+        @Override
+        public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+                            IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+                mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+            }
+        }
+
+        @Override
+        public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+            }
+        }
+
+        @Override
+        public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+                IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                RecognitionSession recogSession = new RecognitionSession(
+                        audioSessionHandle, config, recognitionInjection);
+                mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+                clientModelSession.wrap((ModelCallback cb) ->
+                        cb.onRecognitionStarted(recogSession));
+            }
+        }
+
+        @Override
+        public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                RecognitionSession clientRecognitionSession =
+                        mRecognitionSessionMap.remove(recognitionSession.asBinder());
+                if (clientRecognitionSession == null) return;
+                clientRecognitionSession.wrap((RecognitionCallback cb)
+                        -> cb.onRecognitionStopped());
+            }
+        }
+
+        @Override
+        public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+                if (clientModelSession == null) return;
+                clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+            }
+        }
+
+
+        @Override
+        public void onRestarted(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mRecognitionSessionMap.clear();
+                mModelSessionMap.clear();
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+            }
+        }
+
+        @Override
+        public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+            }
+        }
+
+        @Override
+        public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+                mClientToken = token;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+            }
+        }
+
+        @Override
+        public void onClientDetached(IBinder token) {
+            synchronized (SoundTriggerInstrumentation.this.mLock) {
+                if (token != mClientToken) return;
+                mClientToken = null;
+                mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+            }
+        }
+
+        @Override
+        public void onPreempted() {
+            // This is always valid, independent of session
+            mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+            // Callbacks will no longer be delivered, and injection will be silently dropped.
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    public SoundTriggerInstrumentation(ISoundTriggerService service,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull GlobalCallback callback) {
+        mClientCallback = Objects.requireNonNull(callback);
+        mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+        try {
+            service.attachInjection(new Injection());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+     * or a restart of the core audio HAL.
+     * Application sessions will be detached, and all state will be cleared. The framework
+     * will re-attach to the HAL following restart.
+     * @hide
+     */
+    @TestApi
+    public void triggerRestart() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL restart before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerRestart();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+     * This callback notifies the framework that methods which previously failed due to
+     * resource contention may now succeed.
+     * @hide
+     */
+    @TestApi
+    public void triggerOnResourcesAvailable() {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException(
+                        "Attempted to trigger HAL resources available before registration");
+            }
+            try {
+                mInjectGlobalEvent.triggerOnResourcesAvailable();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Simulate resource contention, similar to when HAL which does not
+     * support concurrent capture opens a capture stream, or when a HAL
+     * has reached its maximum number of models.
+     * Subsequent model loads and recognition starts will gracefully error.
+     * Since this call does not trigger a callback through the framework, the
+     * call will block until the fake HAL has acknowledged the state change.
+     * @param isResourceContended - true to enable contention, false to return
+     *                              to normal functioning.
+     * @hide
+     */
+    @TestApi
+    public void setResourceContention(boolean isResourceContended) {
+        synchronized (mLock) {
+            if (mInjectGlobalEvent == null) {
+                throw new IllegalStateException("Injection interface not set up");
+            }
+            IInjectGlobalEvent current = mInjectGlobalEvent;
+            final CountDownLatch signal = new CountDownLatch(1);
+            try {
+                current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+                    @Override
+                    public void eventReceived() {
+                        signal.countDown();
+                    }
+                });
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+
+            // Block until we get a callback from the service that our request was serviced.
+            try {
+                // Rely on test timeout if we don't get a response.
+                signal.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a..c41bd1b 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@
 
 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -45,6 +47,7 @@
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -53,9 +56,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.Executor;
 
 /**
  * This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+     * STHAL. Clients must attach to the appropriate underlying ST module.
+     * @param executor - Executor to dispatch global callbacks on
+     * @param callback - Callback for unsessioned events received by the fake STHAL
+     * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @NonNull
+    public static SoundTriggerInstrumentation attachInstrumentation(
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+        ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+                    ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+        return new SoundTriggerInstrumentation(service, executor, callback);
+    }
+
 }
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967..5a690a5 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Slog;
@@ -154,4 +156,23 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Override the persistent enrolled model database with an in-memory
+     * fake for testing purposes.
+     *
+     * @param enabled - {@code true} if the model enrollment database should be overridden with an
+     * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+     * used.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+    @TestApi
+    public void setModelDatabaseForTestEnabled(boolean enabled) {
+        try {
+            mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 47ac2df..e07a629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -69,6 +69,7 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
@@ -460,6 +461,9 @@
                 ProvideTextStyle(value = titleTextStyle) {
                     CompositionLocalProvider(
                         LocalContentColor provides titleContentColor,
+                        // Disable the title font scaling by only passing the density but not the
+                        // font scale.
+                        LocalDensity provides Density(density = LocalDensity.current.density),
                         content = title
                     )
                 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index a3db6d7..e28ada4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -120,9 +120,9 @@
      * @return true if the request succeeded.
      */
     public static synchronized boolean setPowerSaveMode(Context context,
-            boolean enable, boolean needFirstTimeWarning) {
+            boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
         if (DEBUG) {
-            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
         }
         final ContentResolver cr = context.getContentResolver();
 
@@ -152,6 +152,7 @@
                     sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
                             confirmationExtras);
                 }
+                recordBatterySaverEnabledReason(context, reason);
             }
 
             return true;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a6..cb386fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
 import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
 
@@ -72,7 +73,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isFalse();
 
         verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@
         Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
         Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@
         Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+                SAVER_ENABLED_UNKNOWN)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 0000000..61a1cb5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<animated-vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+                    <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+                    <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+                    <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+                    <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+                    <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_3_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fe..f3325ec 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
         android:fromId="@id/unlocked"
         android:toId="@id/locked"
         android:drawable="@drawable/unlocked_to_locked" />
+
+    <transition
+        android:fromId="@id/locked_fp"
+        android:toId="@id/locked"
+        android:drawable="@drawable/fp_to_locked" />
 </animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4..edd3047 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
     <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pin">Enter your PIN</string>
 
-    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pin">Enter PIN</string>
 
     <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_pattern">Enter your pattern</string>
 
-    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_pattern">Draw pattern</string>
 
     <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="keyguard_enter_your_password">Enter your password</string>
 
-    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+    <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
     <string name="keyguard_enter_password">Enter password</string>
 
     <!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
     <!-- Message shown when user enters wrong pattern -->
     <string name="kg_wrong_pattern">Wrong pattern</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
 
     <!-- Message shown when user enters wrong password -->
     <string name="kg_wrong_password">Wrong password</string>
 
-    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
     <string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
 
     <!-- Message shown when user enters wrong PIN -->
     <string name="kg_wrong_pin">Wrong PIN</string>
 
-    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=26] -->
+    <!-- Message shown when user enters wrong PIN  [CHAR LIMIT=48] -->
     <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
 
-    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=52] -->
+    <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2.  [CHAR LIMIT=70] -->
     <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
 
-    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=26] -->
+    <!-- Message shown when user fingerprint is not recognized  [CHAR LIMIT=48] -->
     <string name="kg_fp_not_recognized">Fingerprint not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=26] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=48] -->
     <string name="bouncer_face_not_recognized">Face not recognized</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_password">Try again or enter password</string>
 
-    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=52] -->
+    <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password  [CHAR LIMIT=70] -->
     <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
 
-    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint  [CHAR LIMIT=70] -->
     <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
 
-    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26]  -->
+    <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48]  -->
     <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
 
-    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown when we are on bouncer after Device admin requested lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
 
-    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=52] -->
+    <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
 
-    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=52] -->
+    <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
 
-    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=52] -->
+    <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=76] -->
     <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
 
-    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=52] -->
+    <!-- Message shown when device hasn't been unlocked for a while.  [CHAR LIMIT=82] -->
     <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
 
-    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when face unlock is not available after too many failed face authentication attempts.  [CHAR LIMIT=70] -->
     <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
 
-    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=52] -->
+    <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts.  [CHAR LIMIT=75] -->
     <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
 
-    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=52] -->
+    <!-- Message shown when Trust Agent is disabled.  [CHAR LIMIT=70] -->
     <string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
 
-    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52]  -->
+    <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70]  -->
     <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
 
-    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+    <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
     <string name="kg_too_many_failed_attempts_countdown">{count, plural,
         =1 {Try again in # second.}
         other {Try again in # seconds.}
@@ -296,13 +296,13 @@
     <!-- Description of airplane mode -->
     <string name="airplane_mode">Airplane mode</string>
 
-    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
 
-    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
 
-    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+    <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
     <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
 
     <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f1fca76..d710676 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -152,10 +152,4 @@
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
-
-    <FrameLayout
-        android:id="@+id/preview_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </FrameLayout>
 </com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bc..5f2afe8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@
 
         final CharSequence prevContentDescription = mView.getContentDescription();
         if (mShowLockIcon) {
+            if (wasShowingFpIcon) {
+                // fp icon was shown by UdfpsView, and now we still want to animate the transition
+                // in this drawable
+                mView.updateIcon(ICON_FINGERPRINT, false);
+            }
             mView.updateIcon(ICON_LOCK, false);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 69d7582..012c8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -419,9 +419,6 @@
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
-    // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 56e7398..0abce82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -92,6 +92,9 @@
     /** Current state of whether face authentication is running. */
     val isAuthRunning: Flow<Boolean>
 
+    /** Whether bypass is currently enabled */
+    val isBypassEnabled: Flow<Boolean>
+
     /**
      * Trigger face authentication.
      *
@@ -166,7 +169,7 @@
     override val isAuthenticated: Flow<Boolean>
         get() = _isAuthenticated
 
-    private val bypassEnabled: Flow<Boolean> =
+    override val isBypassEnabled: Flow<Boolean> =
         keyguardBypassController?.let {
             conflatedCallbackFlow {
                 val callback =
@@ -222,7 +225,7 @@
         // & detection is supported & biometric unlock is not allowed.
         listOf(
                 canFaceAuthOrDetectRun(),
-                logAndObserve(bypassEnabled, "bypassEnabled"),
+                logAndObserve(isBypassEnabled, "isBypassEnabled"),
                 logAndObserve(
                     biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
                     "nonStrongBiometricIsNotAllowed"
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306..a765702 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@
 
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -691,7 +695,7 @@
             d.setTitle(R.string.battery_saver_confirmation_title);
             d.setPositiveButton(R.string.battery_saver_confirmation_ok,
                     (dialog, which) -> {
-                        setSaverMode(true, false);
+                        setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
                         logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
                     });
             d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@
         return builder;
     }
 
-    private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
-        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+    private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+            @SaverManualEnabledReason int reason) {
+        BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
     }
 
     private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@
             } else if (action.equals(ACTION_START_SAVER)) {
                 logEvent(BatteryWarningEvents
                         .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
-                setSaverMode(true, true);
+                setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
                 dismissLowBatteryNotification();
             } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
                 dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9b1e2fa..142689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1423,7 +1423,7 @@
 
     private boolean canUnlockWithFingerprint() {
         return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser());
+                getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
     }
 
     private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e6715a1..d1c6aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -96,6 +97,8 @@
     private NotificationShelfController mController;
     private float mActualWidth = -1;
     private boolean mSensitiveRevealAnimEndabled;
+    private boolean mShelfRefactorFlagEnabled;
+    private boolean mCanModifyColorOfNotifications;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -425,7 +428,7 @@
                     transitionAmount = inShelfAmount;
                 }
                 // We don't want to modify the color if the notification is hun'd
-                if (isLastChild && mController.canModifyColorOfNotifications()) {
+                if (isLastChild && canModifyColorOfNotifications()) {
                     if (colorOfViewBeforeLast == NO_COLOR) {
                         colorOfViewBeforeLast = ownColorUntinted;
                     }
@@ -490,6 +493,14 @@
         }
     }
 
+    private boolean canModifyColorOfNotifications() {
+        if (mShelfRefactorFlagEnabled) {
+            return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+        } else {
+            return mController.canModifyColorOfNotifications();
+        }
+    }
+
     private void updateCornerRoundnessOnScroll(
             ActivatableNotificationView anv,
             float viewStart,
@@ -959,10 +970,31 @@
         return false;
     }
 
+    private void assertRefactorFlagDisabled() {
+        if (mShelfRefactorFlagEnabled) {
+            throw new IllegalStateException(
+                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
+        }
+    }
+
+    private boolean checkRefactorFlagEnabled() {
+        if (!mShelfRefactorFlagEnabled) {
+            Log.wtf(TAG,
+                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+        }
+        return mShelfRefactorFlagEnabled;
+    }
+
     public void setController(NotificationShelfController notificationShelfController) {
+        assertRefactorFlagDisabled();
         mController = notificationShelfController;
     }
 
+    public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+        if (!checkRefactorFlagEnabled()) return;
+        mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+    }
+
     public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
@@ -975,6 +1007,10 @@
         mSensitiveRevealAnimEndabled = enabled;
     }
 
+    public void setRefactorFlagEnabled(boolean enabled) {
+        mShelfRefactorFlagEnabled = enabled;
+    }
+
     /**
      * This method resets the OnScroll roundness of a view to 0f
      * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 0000000..db550c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.statusbar.notification.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+    private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+    /** Is the system in a state where the shelf is just a static display of notification icons? */
+    val isShelfStatic: Flow<Boolean>
+        get() =
+            combine(
+                keyguardRepository.isKeyguardShowing,
+                deviceEntryFaceAuthRepository.isBypassEnabled,
+            ) { isKeyguardShowing, isBypassEnabled ->
+                isKeyguardShowing && isBypassEnabled
+            }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index a235157..bd531ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.shelf.view
+package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
 
 import android.view.View
 import android.view.View.OnAttachStateChangeListener
 import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
 import com.android.systemui.statusbar.NotificationShelf
@@ -31,21 +34,16 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
 import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
 import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.phone.NotificationTapHelper
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import dagger.Binds
-import dagger.Module
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import javax.inject.Inject
 
-/** Binds a [NotificationShelf] to its backend. */
-interface NotificationShelfViewBinder {
-    fun bind(shelf: NotificationShelf)
-}
-
 /**
  * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
  * around a [NotificationShelfViewBinder], so that external code can continue to depend on the
@@ -57,8 +55,7 @@
 @Inject
 constructor(
     private val shelf: NotificationShelf,
-    private val viewBinder: NotificationShelfViewBinder,
-    private val keyguardBypassController: KeyguardBypassController,
+    private val viewModel: NotificationShelfViewModel,
     featureFlags: FeatureFlags,
     private val notifTapHelperFactory: NotificationTapHelper.Factory,
     private val a11yManager: AccessibilityManager,
@@ -67,20 +64,19 @@
     private val statusBarStateController: SysuiStatusBarStateController,
 ) : NotificationShelfController {
 
-    private var ambientState: AmbientState? = null
-
     override val view: NotificationShelf
         get() = shelf
 
     init {
         shelf.apply {
+            setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
             useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
             setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
         }
     }
 
     fun init() {
-        viewBinder.bind(shelf)
+        NotificationShelfViewBinder.bind(viewModel, shelf)
 
         ActivatableNotificationViewController(
                 shelf,
@@ -91,7 +87,6 @@
                 falsingCollector,
             )
             .init()
-        shelf.setController(this)
         val onAttachStateListener =
             object : OnAttachStateChangeListener {
                 override fun onViewAttachedToWindow(v: View) {
@@ -117,10 +112,7 @@
     override val shelfIcons: NotificationIconContainer
         get() = shelf.shelfIcons
 
-    override fun canModifyColorOfNotifications(): Boolean {
-        return (ambientState?.isShadeExpanded == true &&
-            !(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled))
-    }
+    override fun canModifyColorOfNotifications(): Boolean = unsupported
 
     override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
         shelf.setOnActivatedListener(listener)
@@ -128,25 +120,28 @@
 
     override fun bind(
         ambientState: AmbientState,
-        notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+        notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
     ) {
         shelf.bind(ambientState, notificationStackScrollLayoutController)
-        this.ambientState = ambientState
     }
 
     override fun setOnClickListener(listener: View.OnClickListener) {
         shelf.setOnClickListener(listener)
     }
+
+    private val unsupported: Nothing
+        get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
 }
 
-@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule
-
-@Module
-private interface PrivateShelfViewBinderModule {
-    @Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder
-}
-
-@CentralSurfacesScope
-private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder {
-    override fun bind(shelf: NotificationShelf) {}
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+    fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+        shelf.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.canModifyColorOfNotifications
+                    .onEach(shelf::setCanModifyColorOfNotifications)
+                    .launchIn(this)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 0000000..b84834a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.statusbar.notification.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+    private val interactor: NotificationShelfInteractor,
+) {
+    /** Is the shelf allowed to modify the color of notifications in the host layout? */
+    val canModifyColorOfNotifications: Flow<Boolean>
+        get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
-    private Drawable mBlackBarDrawable = new Drawable() {
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.save();
-            canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
-            canvas.drawColor(Color.BLACK);
-            canvas.restore();
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-            // noop
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-            // noop
-        }
-
-        @Override
-        public int getOpacity() {
-            return android.graphics.PixelFormat.OPAQUE;
-        }
-    };
-
-    public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setBackground(mBlackBarDrawable);
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        setPadding(0, 0, 0, insets.getStableInsetBottom());
-        return super.onApplyWindowInsets(insets);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index cc2a0ba..5d4adda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -52,8 +52,7 @@
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule;
-import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -87,8 +86,7 @@
 import dagger.Provides;
 import dagger.multibindings.IntoSet;
 
-@Module(subcomponents = StatusBarFragmentComponent.class,
-        includes = { NotificationShelfViewBinderModule.class })
+@Module(subcomponents = StatusBarFragmentComponent.class)
 public abstract class StatusBarViewModule {
 
     public static final String SHADE_HEADER = "large_screen_shade_header";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04..1e63b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@
 import static android.os.BatteryManager.EXTRA_HEALTH;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
 import android.annotation.WorkerThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -169,7 +171,8 @@
     @Override
     public void setPowerSaveMode(boolean powerSave, View view) {
         if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
-        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+                SAVER_ENABLED_QS);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e862..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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 androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
-    class TestAnimationHandler extends AnimationHandler {
-        TestAnimationHandler() {
-            super(new TestProvider());
-        }
-
-        List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
-        @Override
-        void addAnimationFrameCallback(AnimationFrameCallback callback) {
-            animationCallbacks.add(callback);
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void removeCallback(AnimationFrameCallback callback) {
-            int id = animationCallbacks.indexOf(callback);
-            if (id >= 0) {
-                animationCallbacks.set(id, null);
-            }
-        }
-
-        void onAnimationFrame(long frameTime) {
-            for (int i = 0; i < animationCallbacks.size(); i++) {
-                final AnimationFrameCallback callback = animationCallbacks.get(i);
-                if (callback == null) {
-                    continue;
-                }
-                callback.doAnimationFrame(frameTime);
-            }
-        }
-
-        @Override
-        void autoCancelBasedOn(ObjectAnimator objectAnimator) {
-            for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
-                AnimationFrameCallback cb = animationCallbacks.get(i);
-                if (cb == null) {
-                    continue;
-                }
-                if (objectAnimator.shouldAutoCancel(cb)) {
-                    ((Animator) animationCallbacks.get(i)).cancel();
-                }
-            }
-        }
-    }
-
-    final TestAnimationHandler mTestHandler;
-    final long mStartTime;
-    private long mTotalTimeDelta = 0;
-    private final Object mLock = new Object();
-
-    public AnimatorTestRule2() {
-        mStartTime = SystemClock.uptimeMillis();
-        mTestHandler = new TestAnimationHandler();
-    }
-
-    @NonNull
-    @Override
-    public Statement apply(@NonNull final Statement base, @NonNull Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                AnimationHandler.setTestHandler(mTestHandler);
-                try {
-                    base.evaluate();
-                } finally {
-                    AnimationHandler.setTestHandler(null);
-                }
-            }
-        };
-    }
-
-    /**
-     * Advances the animation clock by the given amount of delta in milliseconds. This call will
-     * produce an animation frame to all the ongoing animations. This method needs to be
-     * called on the same thread as {@link Animator#start()}.
-     *
-     * @param timeDelta the amount of milliseconds to advance
-     */
-    public void advanceTimeBy(long timeDelta) {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            // Advance time & pulse a frame
-            mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
-        }
-        // produce a frame
-        mTestHandler.onAnimationFrame(getCurrentTime());
-    }
-
-
-    /**
-     * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
-     * different time than the time tracked by {@link SystemClock} This method needs to be called on
-     * the same thread as {@link Animator#start()}.
-     */
-    public long getCurrentTime() {
-        if (Looper.myLooper() == null) {
-            // Throw an exception
-            throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
-                    + "called on Looper threads");
-        }
-        synchronized (mLock) {
-            return mStartTime + mTotalTimeDelta;
-        }
-    }
-
-
-    private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
-        TestProvider() {
-        }
-
-        @Override
-        public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
-            callback.doAnimationFrame(getCurrentTime());
-        }
-
-        @Override
-        public void postFrameCallback() {
-        }
-
-        @Override
-        public void setFrameDelay(long delay) {
-        }
-
-        @Override
-        public long getFrameDelay() {
-            return 0;
-        }
-    }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b..e7738af 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@
 @RunWithLooper(setAsMainLooper = true)
 class AnimatorTestRuleTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Test
     fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6e002f5..2489e04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -59,11 +59,10 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,6 +80,7 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.mock
@@ -88,6 +88,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -120,6 +122,7 @@
     private lateinit var authStatus: FlowValue<AuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<DetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
+    private lateinit var bypassEnabled: FlowValue<Boolean?>
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
@@ -726,6 +729,23 @@
         }
 
     @Test
+    fun isBypassEnabledReflectsBypassControllerState() =
+        testScope.runTest {
+            initCollectors()
+            runCurrent()
+            val listeners = captureMany {
+                verify(bypassController, atLeastOnce())
+                    .registerOnBypassStateChangedListener(capture())
+            }
+
+            listeners.forEach { it.onBypassStateChanged(true) }
+            assertThat(bypassEnabled()).isTrue()
+
+            listeners.forEach { it.onBypassStateChanged(false) }
+            assertThat(bypassEnabled()).isFalse()
+        }
+
+    @Test
     fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -844,6 +864,7 @@
         lockedOut = collectLastValue(underTest.isLockedOut)
         canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
         authenticated = collectLastValue(underTest.isAuthenticated)
+        bypassEnabled = collectLastValue(underTest.isBypassEnabled)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b..4438b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@
     public void onBiometricHelp_coEx_faceFailure() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -641,9 +640,8 @@
     public void onBiometricHelp_coEx_faceUnavailable() {
         createController();
 
-        // GIVEN unlocking with fingerprint is possible
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
-                .thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         String message = "A message";
         mController.setVisible(true);
@@ -664,6 +662,35 @@
                 mContext.getString(R.string.keyguard_suggest_fingerprint));
     }
 
+
+    @Test
+    public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible but not allowed
+        setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+                .thenReturn(false);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
     @Test
     public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
         createController();
@@ -818,8 +845,7 @@
     @Test
     public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
         createController();
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        fingerprintUnlockIsPossibleAndAllowed();
         String message = "A message";
 
         mController.setVisible(true);
@@ -832,9 +858,8 @@
     public void sendFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that are allowed to show
         final String helpString = "helpString";
@@ -859,9 +884,8 @@
     public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help messages received that aren't supposed to show
         final String helpString = "helpString";
@@ -886,9 +910,8 @@
     public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint NOT possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help messages received
         final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
         createController();
 
-        // GIVEN fingerprint NOT enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(false);
+        // GIVEN fingerprint not possible
+        fingerprintUnlockIsNotPossible();
 
         // WHEN help message received and deferred message is valid
         final String helpString = "helpMsg";
@@ -948,9 +970,8 @@
     public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
         createController();
 
-        // GIVEN fingerprint enrolled
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                getCurrentUser())).thenReturn(true);
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
 
         // WHEN help message received and deferredMessage is valid
         final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@
     @Test
     public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
 
         verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@
     @Test
     public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
         createController();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
         onFaceLockoutError("first lockout");
         clearInvocations(mRotateTextViewController);
 
@@ -1668,7 +1689,7 @@
     public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
         createController();
         screenIsTurningOn();
-        fingerprintUnlockIsPossible();
+        fingerprintUnlockIsPossibleAndAllowed();
 
         onFaceLockoutError("lockout error");
         verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@
         setupFingerprintUnlockPossible(false);
     }
 
-    private void fingerprintUnlockIsPossible() {
+    private void fingerprintUnlockIsPossibleAndAllowed() {
         setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
     }
 
     private void setupFingerprintUnlockPossible(boolean possible) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 7b59cc2..08a9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
     private val fakeFeatureFlags = FakeFeatureFlags()
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index be3b723..aff705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,7 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule2()
+    @get:Rule val animatorTestRule = AnimatorTestRule()
 
     private val dumpManager: DumpManager = mock()
     private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 0000000..14e5f9e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val underTest =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(shelfStatic).isFalse()
+    }
+
+    @Test
+    fun shelfIsStatic_whenBypass() = runTest {
+        val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(shelfStatic).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 0000000..6c5fb8b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+    private val interactor =
+        NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+    private val underTest = NotificationShelfViewModel(interactor)
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(false)
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+        assertThat(canModifyNotifColor).isTrue()
+    }
+
+    @Test
+    fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+        val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+        keyguardRepository.setKeyguardShowing(true)
+        deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+        assertThat(canModifyNotifColor).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c..91c88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
 
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@
         mBatteryController.setPowerSaveMode(false, mView);
 
         StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
-        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+                SAVER_ENABLED_QS));
+        inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+                SAVER_ENABLED_QS));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94ba..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
     @ClassRule
-    public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     @Before
     public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..c08ecd0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+    override val isAuthenticated = MutableStateFlow(false)
+    override val canRunFaceAuth = MutableStateFlow(false)
+    private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<AuthenticationStatus> =
+        _authenticationStatus.filterNotNull()
+    fun setAuthenticationStatus(status: AuthenticationStatus) {
+        _authenticationStatus.value = status
+    }
+    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+    override val detectionStatus: Flow<DetectionStatus>
+        get() = _detectionStatus.filterNotNull()
+    fun setDetectionStatus(status: DetectionStatus) {
+        _detectionStatus.value = status
+    }
+    override val isLockedOut = MutableStateFlow(false)
+    private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+    val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+        _runningAuthRequest.asStateFlow()
+    override val isAuthRunning = _runningAuthRequest.map { it != null }
+    override val isBypassEnabled = MutableStateFlow(false)
+
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        _runningAuthRequest.value = uiEvent to fallbackToDetection
+    }
+
+    override fun cancel() {
+        _runningAuthRequest.value = null
+    }
+}
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c553..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@
         ":services.searchui-sources",
         ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
+        ":services.soundtrigger-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
         ":services.texttospeech-sources",
@@ -169,6 +170,7 @@
         "services.searchui",
         "services.selectiontoolbar",
         "services.smartspace",
+        "services.soundtrigger",
         "services.systemcaptions",
         "services.translation",
         "services.texttospeech",
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c8caab9..cfdf3ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@
         "android.hardware.configstore-V1.1-java",
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
-        "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
similarity index 97%
rename from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
rename to services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d9..e6c1750 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.soundtrigger;
+package com.android.server;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,15 +29,13 @@
 import android.media.permission.Identity;
 import android.os.IBinder;
 
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
 
 /**
  * Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
  */
 public interface SoundTriggerInternal {
     /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7b618b1..e248007 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@
 import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
 import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -117,6 +128,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -1146,7 +1158,7 @@
                                             } finally {
                                                 /* Will be a no-op if nothing pending */
                                                 mAm.updateOomAdjPendingTargetsLocked(
-                                                        OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                                                        OOM_ADJ_REASON_START_SERVICE);
                                             }
                                         } else {
                                             unbindServiceLocked(connection);
@@ -1236,8 +1248,7 @@
                             /* ignore - local call */
                         } finally {
                             /* Will be a no-op if nothing pending */
-                            mAm.updateOomAdjPendingTargetsLocked(
-                                    OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                         }
                     } else { // Starting a service
                         try {
@@ -1311,7 +1322,7 @@
                 false /* packageFrozen */,
                 true /* enqueueOomAdj */);
         /* Will be a no-op if nothing pending */
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         if (error != null) {
             return new ComponentName("!!", error);
         }
@@ -1496,7 +1507,7 @@
                     stopServiceLocked(service, true);
                 }
                 if (size > 0) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
                 }
             }
         }
@@ -3296,7 +3307,7 @@
 
             Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
 
-            mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+            mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
         }
     }
 
@@ -3630,7 +3641,7 @@
                 needOomAdj = true;
                 if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
                         permissionsReviewRequired, packageFrozen, true) != null) {
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
                     return 0;
                 }
             }
@@ -3655,7 +3666,7 @@
                 mAm.enqueueOomAdjTargetLocked(s.app);
             }
             if (needOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
             }
 
             final int packageState = wasStopped
@@ -3787,7 +3798,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+                        OOM_ADJ_REASON_EXECUTING_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3878,7 +3890,7 @@
                 }
             }
 
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
 
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3925,7 +3937,8 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, inDestroying, false, false);
+                serviceDoneExecutingLocked(r, inDestroying, false, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4360,11 +4373,11 @@
     /**
      * Bump the given service record into executing state.
      * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
-     *         OomAdjuster#OOM_ADJ_REASON_NONE}.
+     *         ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
      * @return {@code true} if it performed oomAdjUpdate.
      */
     private boolean bumpServiceExecutingLocked(
-            ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+            ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                 + why + " of " + r + " in app " + r.app);
         else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4416,7 +4429,7 @@
             }
         }
         boolean oomAdjusted = false;
-        if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+        if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
                 && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
             // Force an immediate oomAdjUpdate, so the client app could be in the correct process
             // state before doing any service related transactions
@@ -4440,8 +4453,7 @@
                 + " rebind=" + rebind);
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                bumpServiceExecutingLocked(r, execInFg, "bind",
-                        OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                     Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
                             + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4457,13 +4469,15 @@
                 // Keep the executeNesting count accurate.
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_UNBIND_SERVICE);
                 return false;
             }
         }
@@ -4841,7 +4855,7 @@
             // Ignore, it's been logged and nothing upstack cares.
         } finally {
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
     }
 
@@ -5193,13 +5207,14 @@
 
         final ProcessServiceRecord psr = app.mServices;
         final boolean newService = psr.startService(r);
-        bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+        bumpServiceExecutingLocked(r, execInFg, "create",
+                OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
         mAm.updateLruProcessLocked(app, false, null);
         updateServiceForegroundLocked(psr, /* oomAdj= */ false);
         // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
         // before doing any service related transactions
         mAm.enqueueOomAdjTargetLocked(app);
-        mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
 
         boolean created = false;
         try {
@@ -5233,7 +5248,8 @@
             if (!created) {
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+                        OOM_ADJ_REASON_STOP_SERVICE);
 
                 // Cleanup.
                 if (newService) {
@@ -5319,7 +5335,8 @@
             mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
                     UserHandle.getAppId(r.appInfo.uid)
             );
-            bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+            bumpServiceExecutingLocked(r, execInFg, "start",
+                    OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
             if (r.fgRequired && !r.fgWaiting) {
                 if (!r.isForeground) {
                     if (DEBUG_BACKGROUND_CHECK) {
@@ -5345,7 +5362,7 @@
 
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
         }
         ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
         slice.setInlineCountLimit(4);
@@ -5371,10 +5388,11 @@
             // Keep nesting count correct
             final boolean inDestroying = mDestroyingServices.contains(r);
             for (int i = 0, size = args.size(); i < size; i++) {
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+                        OOM_ADJ_REASON_STOP_SERVICE);
             }
             /* Will be a no-op if nothing pending */
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             if (caughtException instanceof TransactionTooLargeException) {
                 throw (TransactionTooLargeException)caughtException;
             }
@@ -5461,7 +5479,7 @@
                 if (ibr.hasBound) {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
-                                OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                OOM_ADJ_REASON_UNBIND_SERVICE);
                         ibr.hasBound = false;
                         ibr.requested = false;
                         r.app.getThread().scheduleUnbindService(r,
@@ -5615,7 +5633,7 @@
                 } else {
                     try {
                         oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
-                                oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                                oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
                         mDestroyingServices.add(r);
                         r.destroying = true;
                         r.app.getThread().scheduleStopService(r);
@@ -5637,7 +5655,7 @@
         if (!oomAdjusted) {
             mAm.enqueueOomAdjTargetLocked(r.app);
             if (!enqueueOomAdj) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
             }
         }
         if (r.bindings.size() > 0) {
@@ -5762,8 +5780,7 @@
             if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
-                    bumpServiceExecutingLocked(s, false, "unbind",
-                            OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
                     if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
                             && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
@@ -5886,7 +5903,8 @@
                 }
             }
             final long origId = Binder.clearCallingIdentity();
-            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+                    OOM_ADJ_REASON_EXECUTING_SERVICE);
             Binder.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
@@ -5905,11 +5923,11 @@
                 r.tracker.setStarted(false, memFactor, now);
             }
         }
-        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
     }
 
     private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
-            boolean finishing, boolean enqueueOomAdj) {
+            boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
                 + ": nesting=" + r.executeNesting
                 + ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5945,7 +5963,7 @@
                 if (enqueueOomAdj) {
                     mAm.enqueueOomAdjTargetLocked(r.app);
                 } else {
-                    mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    mAm.updateOomAdjLocked(r.app, oomAdjReason);
                 }
             }
             r.executeFg = false;
@@ -6015,7 +6033,7 @@
                         bringDownServiceLocked(sr, true);
                     }
                     /* Will be a no-op if nothing pending */
-                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+                    mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception in new application when starting service "
@@ -6075,7 +6093,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -6146,7 +6164,7 @@
                 bringDownServiceLocked(mTmpCollectionResults.get(i), true);
             }
             if (size > 0) {
-                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
             }
             if (fullStop && !mTmpCollectionResults.isEmpty()) {
                 // if we're tearing down the app's entire service state, account for possible
@@ -6273,7 +6291,7 @@
             }
         }
         if (needOomAdj) {
-            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
         }
     }
 
@@ -6444,7 +6462,7 @@
             }
         }
 
-        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+        mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
 
         if (!allowRestart) {
             psr.stopAllServices();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9752ade..4a6c9b2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@
 import android.app.ActivityManagerInternal.BroadcastEventListener;
 import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
 import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
 import android.app.AnrController;
@@ -368,7 +377,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
-import android.util.LogWriter;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -1968,7 +1976,7 @@
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
                 updateLruProcessLocked(app, false, null);
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(
@@ -2502,7 +2510,7 @@
         // bind background threads to little cores
         // this is expected to fail inside of framework tests because apps can't touch cpusets directly
         // make sure we've already adjusted system_server's internal view of itself first
-        updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
         try {
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@
             handleAppDiedLocked(app, pid, false, true, fromBinderDied);
 
             if (doOomAdj) {
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+                updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
             }
             if (doLowMem) {
                 mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4843,7 +4851,7 @@
             }
 
             if (!didSomething) {
-                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+                updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
                 checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
             }
 
@@ -5485,7 +5493,7 @@
                 "setProcessLimit()");
         synchronized (this) {
             mConstants.setOverrideMaxCachedProcesses(max);
-            trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+            trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
         }
     }
 
@@ -5513,7 +5521,7 @@
                 pr.mState.setForcingToImportant(null);
                 clearProcessForegroundLocked(pr);
             }
-            updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -5560,7 +5568,7 @@
             }
 
             if (changed) {
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -6869,7 +6877,7 @@
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
             updateLruProcessLocked(app, false, null);
-            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+            updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
 
         // Report usage as process is persistent and being started.
@@ -6986,7 +6994,7 @@
                 mOomAdjProfiler.onWakefulnessChanged(wakefulness);
                 mOomAdjuster.onWakefulnessChanged(wakefulness);
 
-                updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
     }
@@ -7748,7 +7756,7 @@
                     }
                 }
                 if (changed) {
-                    updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                    updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
                 }
             }
         } finally {
@@ -9508,7 +9516,7 @@
 
             mAppProfiler.setMemFactorOverrideLocked(level);
             // Kick off an oom adj update since we forced a mem factor update.
-            updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
         }
     }
 
@@ -13408,7 +13416,7 @@
             proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
             // Try not to kill the process during backup
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
 
             // If the process is already attached, schedule the creation of the backup agent now.
             // If it is not yet live, this will be done when it attaches to the framework.
@@ -13532,7 +13540,7 @@
 
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = backupTarget.app;
-                updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+                updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
                 proc.setInFullBackup(false);
                 proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
 
@@ -13923,7 +13931,7 @@
                 // If we actually concluded any broadcasts, we might now be able
                 // to trim the recipients' apps from our working set
                 if (doTrim) {
-                    trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
                     return;
                 }
             }
@@ -15185,7 +15193,7 @@
                 queue.finishReceiverLocked(callerApp, resultCode,
                         resultData, resultExtras, resultAbort, true);
                 // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
             }
 
         } finally {
@@ -16130,7 +16138,7 @@
             item.foregroundServiceTypes = fgServiceTypes;
         }
         if (oomAdj) {
-            updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
         }
     }
 
@@ -16196,7 +16204,7 @@
      * {@link #enqueueOomAdjTargetLocked}.
      */
     @GuardedBy("this")
-    void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
     }
 
@@ -16215,7 +16223,7 @@
     }
 
     @GuardedBy("this")
-    final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
         mOomAdjuster.updateOomAdjLocked(oomAdjReason);
     }
 
@@ -16227,8 +16235,7 @@
      * @return whether updateOomAdjLocked(app) was successful.
      */
     @GuardedBy("this")
-    final boolean updateOomAdjLocked(
-            ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
     }
 
@@ -16461,16 +16468,14 @@
         mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
     }
 
-    private void trimApplications(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         synchronized (this) {
             trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
         }
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(
-            boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -17442,7 +17447,7 @@
                 }
                 pr.mState.setHasOverlayUi(hasOverlayUi);
                 //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
-                updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+                updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
             }
         }
 
@@ -17577,7 +17582,7 @@
 
         @Override
         public void trimApplications() {
-            ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+            ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
         }
 
         public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17626,9 +17631,9 @@
         }
 
         @Override
-        public void updateOomAdj() {
+        public void updateOomAdj(@OomAdjReason int oomAdjReason) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+                ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
             }
         }
 
@@ -18288,8 +18293,7 @@
             // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
             // workaround can be removed. (b/213288355)
             if (isNewPending) {
-                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
-                        OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
             }
             // We need to update the network rules for the app coming to the top state so that
             // it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3f..5a4d315 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a4bdf61..c2bd84f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
 
@@ -38,7 +39,6 @@
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 4328efe..f42087f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@
 
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
 import android.app.IApplicationThread;
@@ -139,6 +162,26 @@
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
     static final int UNFREEZE_REASON_FEATURE_FLAGS =
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+    static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+    static final int UNFREEZE_REASON_SYSTEM_INIT =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+    static final int UNFREEZE_REASON_BACKUP =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+    static final int UNFREEZE_REASON_SHELL =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+    static final int UNFREEZE_REASON_REMOVE_TASK =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+    static final int UNFREEZE_REASON_UID_IDLE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+    static final int UNFREEZE_REASON_STOP_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+    static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+    static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+    static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
 
     @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
         UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@
         UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
         UNFREEZE_REASON_BINDER_TXNS,
         UNFREEZE_REASON_FEATURE_FLAGS,
+        UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+        UNFREEZE_REASON_SYSTEM_INIT,
+        UNFREEZE_REASON_BACKUP,
+        UNFREEZE_REASON_SHELL,
+        UNFREEZE_REASON_REMOVE_TASK,
+        UNFREEZE_REASON_UID_IDLE,
+        UNFREEZE_REASON_STOP_SERVICE,
+        UNFREEZE_REASON_EXECUTING_SERVICE,
+        UNFREEZE_REASON_RESTRICTION_CHANGE,
+        UNFREEZE_REASON_COMPONENT_DISABLED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UnfreezeReason {}
@@ -1365,7 +1418,7 @@
      * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
      * @param pid pid of the process to be unfrozen
      */
-    void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeProcess(int pid, @OomAdjReason int reason) {
         synchronized (mFreezerLock) {
             ProcessRecord app = mFrozenProcesses.get(pid);
             if (app == null) {
@@ -1546,12 +1599,12 @@
         public long mOrigAnonRss;
         public int mProcState;
         public int mOomAdj;
-        public @OomAdjuster.OomAdjReason int mOomAdjReason;
+        public @OomAdjReason int mOomAdjReason;
 
         SingleCompactionStats(long[] rss, CompactSource source, String processName,
                 long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
                 long cpuTimeMillis, int procState, int oomAdj,
-                @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+                @OomAdjReason int oomAdjReason, int uid) {
             mRssAfterCompaction = rss;
             mSourceType = source;
             mProcessName = processName;
@@ -2207,32 +2260,52 @@
         }
     }
 
-    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+    static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
         switch (oomAdjReason) {
-            case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+            case OOM_ADJ_REASON_ACTIVITY:
                 return UNFREEZE_REASON_ACTIVITY;
-            case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+            case OOM_ADJ_REASON_FINISH_RECEIVER:
                 return UNFREEZE_REASON_FINISH_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+            case OOM_ADJ_REASON_START_RECEIVER:
                 return UNFREEZE_REASON_START_RECEIVER;
-            case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+            case OOM_ADJ_REASON_BIND_SERVICE:
                 return UNFREEZE_REASON_BIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+            case OOM_ADJ_REASON_UNBIND_SERVICE:
                 return UNFREEZE_REASON_UNBIND_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+            case OOM_ADJ_REASON_START_SERVICE:
                 return UNFREEZE_REASON_START_SERVICE;
-            case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+            case OOM_ADJ_REASON_GET_PROVIDER:
                 return UNFREEZE_REASON_GET_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+            case OOM_ADJ_REASON_REMOVE_PROVIDER:
                 return UNFREEZE_REASON_REMOVE_PROVIDER;
-            case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+            case OOM_ADJ_REASON_UI_VISIBILITY:
                 return UNFREEZE_REASON_UI_VISIBILITY;
-            case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+            case OOM_ADJ_REASON_ALLOWLIST:
                 return UNFREEZE_REASON_ALLOWLIST;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+            case OOM_ADJ_REASON_PROCESS_BEGIN:
                 return UNFREEZE_REASON_PROCESS_BEGIN;
-            case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+            case OOM_ADJ_REASON_PROCESS_END:
                 return UNFREEZE_REASON_PROCESS_END;
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+                return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return UNFREEZE_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return UNFREEZE_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return UNFREEZE_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return UNFREEZE_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return UNFREEZE_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return UNFREEZE_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return UNFREEZE_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return UNFREEZE_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return UNFREEZE_REASON_COMPONENT_DISABLED;
             default:
                 return UNFREEZE_REASON_NONE;
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a1fcd42..d8cb094 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
 import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
 import static android.os.Process.PROC_CHAR;
 import static android.os.Process.PROC_OUT_LONG;
@@ -292,7 +294,7 @@
                     checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                     final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
                     boolean success = mService.updateOomAdjLocked(cpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                            OOM_ADJ_REASON_GET_PROVIDER);
                     // XXX things have changed so updateOomAdjLocked doesn't actually tell us
                     // if the process has been successfully adjusted.  So to reduce races with
                     // it, we will check whether the process still exists.  Note that this doesn't
@@ -757,7 +759,7 @@
 
             // update the app's oom adj value and each provider's usage stats
             if (providersPublished) {
-                mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+                mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
                 for (int i = 0, size = providers.size(); i < size; i++) {
                     ContentProviderHolder src = providers.get(i);
                     if (src == null || src.info == null || src.provider == null) {
@@ -835,8 +837,7 @@
             ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
             if (localCpr.hasExternalProcessHandles()) {
                 if (localCpr.removeExternalProcessHandleLocked(token)) {
-                    mService.updateOomAdjLocked(localCpr.proc,
-                            OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                    mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
                 } else {
                     Slog.e(TAG, "Attempt to remove content provider " + localCpr
                             + " with no external reference for token: " + token + ".");
@@ -1506,8 +1507,7 @@
             mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
                     cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(conn.provider.proc,
-                        OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+                mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b..365dcd9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
 import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.AppProtoEnums;
 import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@
 import com.android.server.wm.WindowProcessController;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -154,32 +175,6 @@
 public class OomAdjuster {
     static final String TAG = "OomAdjuster";
 
-    static final int OOM_ADJ_REASON_NONE = 0;
-    static final int OOM_ADJ_REASON_ACTIVITY = 1;
-    static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
-    static final int OOM_ADJ_REASON_START_RECEIVER = 3;
-    static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
-    static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
-    static final int OOM_ADJ_REASON_START_SERVICE = 6;
-    static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
-    static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
-    static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
-    static final int OOM_ADJ_REASON_ALLOWLIST = 10;
-    static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
-    static final int OOM_ADJ_REASON_PROCESS_END = 12;
-    static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
-    @IntDef(prefix = {"OOM_ADJ_REASON_"},
-            value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
-                    OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
-                    OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
-                    OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
-                    OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
-                    OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
-                    OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface OomAdjReason {}
-
     public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
         switch (oomReason) {
             case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+            case OOM_ADJ_REASON_BACKUP:
+                return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+            case OOM_ADJ_REASON_SHELL:
+                return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+            case OOM_ADJ_REASON_UID_IDLE:
+                return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
@@ -246,6 +259,24 @@
                 return OOM_ADJ_REASON_METHOD + "_processEnd";
             case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
                 return OOM_ADJ_REASON_METHOD + "_shortFgs";
+            case OOM_ADJ_REASON_SYSTEM_INIT:
+                return OOM_ADJ_REASON_METHOD + "_systemInit";
+            case OOM_ADJ_REASON_BACKUP:
+                return OOM_ADJ_REASON_METHOD + "_backup";
+            case OOM_ADJ_REASON_SHELL:
+                return OOM_ADJ_REASON_METHOD + "_shell";
+            case OOM_ADJ_REASON_REMOVE_TASK:
+                return OOM_ADJ_REASON_METHOD + "_removeTask";
+            case OOM_ADJ_REASON_UID_IDLE:
+                return OOM_ADJ_REASON_METHOD + "_uidIdle";
+            case OOM_ADJ_REASON_STOP_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_stopService";
+            case OOM_ADJ_REASON_EXECUTING_SERVICE:
+                return OOM_ADJ_REASON_METHOD + "_executingService";
+            case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+                return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+            case OOM_ADJ_REASON_COMPONENT_DISABLED:
+                return OOM_ADJ_REASON_METHOD + "_componentDisabled";
             default:
                 return "_unknown";
         }
@@ -874,8 +905,7 @@
     }
 
     @GuardedBy("mService")
-    private void performUpdateOomAdjPendingTargetsLocked(
-            @OomAdjuster.OomAdjReason int oomAdjReason) {
+    private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@
     }
 
     @GuardedBy("mService")
-    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc533..f233107 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManagerInternal.OomAdjReason;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -51,7 +53,7 @@
     /**
      * Last oom adjust change reason for this app.
      */
-    @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+    @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
 
     /**
      * The most recent compaction action performed for this app.
@@ -139,12 +141,12 @@
     }
 
     @GuardedBy("mProcLock")
-    void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+    void setLastOomAdjChangeReason(@OomAdjReason int reason) {
         mLastOomAdjChangeReason = reason;
     }
 
     @GuardedBy("mProcLock")
-    @OomAdjuster.OomAdjReason
+    @OomAdjReason
     int getLastOomAdjChangeReason() {
         return mLastOomAdjChangeReason;
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef..a237a07 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@
                     reasonCode, subReason, reason, !doFreeze /* async */);
         }
         killAppZygotesLocked(packageName, appId, userId, false /* force */);
-        mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+        mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
         if (doFreeze) {
             freezePackageCgroup(packageUID, false);
         }
@@ -5140,7 +5142,7 @@
                 }
             });
             /* Will be a no-op if nothing pending */
-            mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623..7aae4d5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1450,7 +1452,7 @@
             }
             mService.updateLruProcessLocked(this, activityChange, null /* client */);
             if (updateOomAdj) {
-                mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+                mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39..8eaf70e 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -766,7 +767,7 @@
             Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
                     + " for pid=" + mApp.getPid());
         }
-        mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+        mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
     }
 
     @GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 22e2c9f..8c227f5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -100,7 +100,6 @@
         DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
-        DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 81ba4b8..a110169 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3027,17 +3029,29 @@
                     packageName);
         }
 
-        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
-        // purposes and not as a check, also make sure that the caller is allowed to access
-        // the data gated by OP_RECORD_AUDIO.
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+        // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+        // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
         //
         // TODO: Revert this change before Android 12.
-        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
-            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+        int result = MODE_DEFAULT;
+        if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+                || code == OP_RECORD_AUDIO_SANDBOXED) {
+            result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+            // Check result
             if (result != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(result, code, attributionTag, packageName);
             }
         }
+        // As a special case for OP_CAMERA_SANDBOXED.
+        if (code == OP_CAMERA_SANDBOXED) {
+            result = checkOperation(OP_CAMERA, uid, packageName);
+            // Check result
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return new SyncNotedAppOp(result, code, attributionTag, packageName);
+            }
+        }
+
         return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
                 Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
                 shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df..5127d26 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
@@ -519,6 +521,9 @@
 
         try {
             mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+            final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+                    ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+            mClientReceiver.onAcquired(aAcquiredInfo, message);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
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 128ef0b..6c26e2b 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
@@ -413,6 +413,11 @@
                                 Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
                             }
                         }
+
+                        @Override
+                        public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+                            onAuthenticationAcquired(acquireInfo);
+                        }
                     };
 
             return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a..2d40661 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@
      */
     void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
             LocaleList locales) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
                     appPackageName, locales);
@@ -464,7 +464,7 @@
      * Checks if the calling app is the installer of the app whose locale changed.
      */
     private boolean isCallerInstaller(String appPackageName, int userId) {
-        String installingPackageName = getInstallingPackageName(appPackageName);
+        String installingPackageName = getInstallingPackageName(appPackageName, userId);
         if (installingPackageName != null) {
             // Get the uid of installer-on-record to compare with the calling uid.
             int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@
     }
 
     @Nullable
-    String getInstallingPackageName(String packageName) {
+    String getInstallingPackageName(String packageName, int userId) {
         try {
-            return mContext.getPackageManager()
-                    .getInstallSourceInfo(packageName).getInstallingPackageName();
+            return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+                    0).getPackageManager().getInstallSourceInfo(
+                    packageName).getInstallingPackageName();
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Package not found " + packageName);
         }
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653..373d355 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@
     void onPackageUpdateFinished(String packageName, int uid) {
         try {
             if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+                int userId = UserHandle.getUserId(uid);
                 // If a system app is updated, verify that it has an installer-on-record.
                 String installingPackageName = mLocaleManagerService.getInstallingPackageName(
-                        packageName);
+                        packageName, userId);
                 if (installingPackageName == null) {
                     // We want to broadcast the locales info to the installer.
                     // If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@
                 }
 
                 try {
-                    int userId = UserHandle.getUserId(uid);
                     // Fetch the app-specific locales.
                     // If non-empty then send the info to the installer.
                     LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7..88d23ce 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
 import android.util.Log;
 import android.util.Slog;
 
@@ -146,13 +147,13 @@
 
     public static void traceConfig(String reason, ZenModeConfig oldConfig,
             ZenModeConfig newConfig) {
-        ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
-        if (diff.isEmpty()) {
+        ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+        if (diff == null || !diff.hasDiff()) {
             append(TYPE_CONFIG, reason + " no changes");
         } else {
             append(TYPE_CONFIG, reason
                     + ",\n" + (newConfig != null ? newConfig.toString() : null)
-                    + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+                    + ",\n" + diff);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@
     private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
         code = resolveRecordAudioOp(code, uid);
+        code = resolveSandboxedServiceOp(code, uid);
         if (attributionTag == null) {
             return code;
         }
@@ -439,6 +440,28 @@
         return code;
     }
 
+    private int resolveSandboxedServiceOp(int code, int uid) {
+        if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+                 || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+            return code;
+        }
+        final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+                mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+        if (hotwordDetectionServiceIdentity != null
+                && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+            // Upgrade the op such that no indicators is shown for camera or audio service. This
+            // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+            switch (code) {
+                case AppOpsManager.OP_RECORD_AUDIO:
+                    return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+                case AppOpsManager.OP_CAMERA:
+                    return AppOpsManager.OP_CAMERA_SANDBOXED;
+            }
+        }
+        return code;
+    }
+
+
     private int resolveUid(int code, int uid) {
         // The HotwordDetectionService is an isolated service, which ordinarily cannot hold
         // permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 12fe6a0..8123c07 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -831,7 +831,7 @@
     private final Runnable mUpdateOomAdjRunnable = new Runnable() {
         @Override
         public void run() {
-            mAmInternal.updateOomAdj();
+            mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
         }
     };
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 492d477..b1d6131 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
 import com.android.server.connectivity.PacProxyService;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.devicestate.DeviceStateManagerService;
 import com.android.server.display.DisplayManagerService;
@@ -1405,6 +1406,15 @@
         mSystemServiceManager.startService(RemoteProvisioningService.class);
         t.traceEnd();
 
+        // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+        // builds once the Android JobScheduler starts using this service.
+        if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+          // Service for CPU monitor.
+          t.traceBegin("CpuMonitorService");
+          mSystemServiceManager.startService(CpuMonitorService.class);
+          t.traceEnd();
+        }
+
         t.traceEnd(); // startCoreServices
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a..64a95ca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -961,7 +962,7 @@
             } else {
                 // Confirm that app was thawed
                 verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
-                        eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+                        eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
 
                 // Confirm that we added package to process
                 verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1404,7 +1405,7 @@
 
         // Finally, verify that we thawed the final receiver
         verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
-                eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+                eq(OOM_ADJ_REASON_FINISH_RECEIVER));
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 485ce33..cda5456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
 import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@
      * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
      * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
      */
+    @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
         if (apps.length == 1) {
-            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
         } else {
             setProcessesToLru(apps);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
             sService.mProcessList.getLruProcessesLOSP().clear();
         }
     }
@@ -658,7 +660,7 @@
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                 PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
         }
@@ -1243,7 +1245,7 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@
 
         client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
 
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
         assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
         assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
     }
@@ -2029,7 +2031,7 @@
             setServiceMap(s3, MOCKAPP5_UID, cn3);
             setServiceMap(c2s, MOCKAPP3_UID, cn4);
             app2UidRecord.setIdle(false);
-            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
             assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                     SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@
                     anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
             doNothing().when(sService.mServices)
                     .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
-            sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+            sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
 
             assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
             assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@
         app2.mState.setHasShownUi(false);
 
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
 
@@ -2445,7 +2447,7 @@
         app.mState.setAdjType(null);
         app.mState.setSetAdj(UNKNOWN_ADJ);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
@@ -2463,7 +2465,7 @@
         s.lastActivity = now;
 
         app.mServices.startService(s);
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@
         app.mState.setSetAdj(UNKNOWN_ADJ);
         app.mState.setHasShownUi(false);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@
         doReturn(userOther).when(sService.mUserController).getCurrentUserId();
         sService.mOomAdjuster.handleUserSwitchedLocked();
 
-        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 0000000..e1fa8f52
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+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.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IBinder mBinder;
+    private GnssNative mGnssNative;
+
+    private GnssAntennaInfoProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testOnHalStarted() {
+        verify(mGnssNative, times(1)).startAntennaInfoListening();
+    }
+
+    @Test
+    public void testOnHalRestarted() {
+        mTestProvider.onHalRestarted();
+        verify(mGnssNative, times(2)).startAntennaInfoListening();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe8..bf96b1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@
     private @Mock Context mContext;
     private @Mock LocationManagerInternal mInternal;
     private @Mock GnssConfiguration mMockConfiguration;
-    private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
     private @Mock IGnssMeasurementsListener mListener1;
     private @Mock IGnssMeasurementsListener mListener2;
     private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@
         Injector injector = new TestInjector(mContext);
         mGnssNative = spy(Objects.requireNonNull(
                 GnssNative.create(injector, mMockConfiguration)));
-        mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
         mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
         mGnssNative.register();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..64aa4b3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNavigationMessageListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNavigationMessageProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 0000000..49e5e69
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+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.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssNmeaListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssNmeaProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 0000000..ce2aec7f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+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.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+    private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+            "mypackage", "attribution", "listener");
+    private @Mock Context mContext;
+    private @Mock LocationManagerInternal mInternal;
+    private @Mock GnssConfiguration mMockConfiguration;
+    private @Mock IGnssStatusListener mListener;
+    private @Mock IBinder mBinder;
+
+    private GnssNative mGnssNative;
+
+    private GnssStatusProvider mTestProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mBinder).when(mListener).asBinder();
+        doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+                anyInt());
+        LocalServices.addService(LocationManagerInternal.class, mInternal);
+        FakeGnssHal fakeGnssHal = new FakeGnssHal();
+        GnssNative.setGnssHalForTest(fakeGnssHal);
+        Injector injector = new TestInjector(mContext);
+        mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+        mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+        mGnssNative.register();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(LocationManagerInternal.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        // add a request
+        mTestProvider.addListener(IDENTITY, mListener);
+        verify(mGnssNative, times(1)).startSvStatusCollection();
+
+        // remove a request
+        mTestProvider.removeListener(mListener);
+        verify(mGnssNative, times(1)).stopSvStatusCollection();
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80..2d962ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@
     }
 
     @Override
+    protected boolean startSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    protected boolean stopSvStatusCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean startNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
+    public boolean stopNmeaMessageCollection() {
+        return true;
+    }
+
+    @Override
     protected int getBatchSize() {
         return mBatchSize;
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index dbf5021..26a3ae1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,6 +43,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -52,6 +53,7 @@
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
@@ -83,6 +85,7 @@
     private static final long TEST_REQUEST_ID = 22;
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private BiometricContext mBiometricContext;
     @Mock private ITrustManager mTrustManager;
     @Mock private DevicePolicyManager mDevicePolicyManager;
@@ -104,6 +107,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
         when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
         when(mBiometricContext.updateContext(any(), anyBoolean()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
@@ -342,6 +346,33 @@
         testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
     }
 
+    @Test
+    public void testCallbackOnAcquired() throws RemoteException {
+        final String acquiredStr = "test_acquired_info_callback";
+        final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+        setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                TEST_REQUEST_ID,
+                0 /* operationId */,
+                0 /* userId */);
+
+        when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+            .thenReturn(acquiredStr);
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+        verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+        when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+            .thenReturn(new String[]{acquiredStrVendor});
+        session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+        verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+        verify(mClientReceiver).onAcquired(
+                eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+    }
+
     // TODO (b/208484275) : Enable these tests
     // @Test
     // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b..4cfbb95 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@
         mMockPackageManager = mock(PackageManager.class);
         mMockPackageMonitor = mock(PackageMonitor.class);
 
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
         // For unit tests, set the default installer info
         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de25..e20f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
         doReturn(InstrumentationRegistry.getContext().getContentResolver())
                 .when(mMockContext).getContentResolver();
+        doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
 
         mStoragefile = new AtomicFile(new File(
                 Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 0000000..bcd807a
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+    // version is not included in the diff; manual & automatic rules have special handling
+    public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+            Set.of("version", "manualRule", "automaticRules");
+
+    @Test
+    public void testRuleDiff_addRemoveSame() {
+        // Test add, remove, and both sides same
+        ZenModeConfig.ZenRule r = makeRule();
+
+        // Both sides same rule
+        ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+        assertFalse(dSame.hasDiff());
+
+        // from existent rule to null: expect deleted
+        ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+        assertTrue(deleted.hasDiff());
+        assertTrue(deleted.wasRemoved());
+
+        // from null to new rule: expect added
+        ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+    }
+
+    @Test
+    public void testRuleDiff_fieldDiffs() throws Exception {
+        // Start these the same
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+        generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_addRemoveSame() {
+        // Default config, will test add, remove, and no change
+        ZenModeConfig c = new ZenModeConfig();
+
+        ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+        assertFalse(dSame.hasDiff());
+
+        ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+        assertTrue(added.hasDiff());
+        assertTrue(added.wasAdded());
+
+        ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+        assertTrue(removed.hasDiff());
+        assertTrue(removed.wasRemoved());
+    }
+
+    @Test
+    public void testConfigDiff_fieldDiffs() throws Exception {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // maps mapping field name -> expected output value as we set diffs
+        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+        List<Field> fieldsForDiff = getFieldsForDiffCheck(
+                ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+        generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Now diff them and check that each of the fields has a diff
+        for (Field f : fieldsForDiff) {
+            String name = f.getName();
+            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+            assertTrue(d.getDiffForField(name).hasDiff());
+            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+        }
+    }
+
+    @Test
+    public void testConfigDiff_specialSenders() {
+        // these two start the same
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // set c1 and c2 to have some different senders
+        c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+        c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+        c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+        c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(d.hasDiff());
+
+        // Diff in top-level fields
+        assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+        assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+        // Bonus testing of stringification of people senders and conversation senders
+        assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+        assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+    }
+
+    @Test
+    public void testConfigDiff_hasRuleDiffs() {
+        // two default configs
+        ZenModeConfig c1 = new ZenModeConfig();
+        ZenModeConfig c2 = new ZenModeConfig();
+
+        // two initially-identical rules
+        ZenModeConfig.ZenRule r1 = makeRule();
+        ZenModeConfig.ZenRule r2 = makeRule();
+
+        // one that will become a manual rule
+        ZenModeConfig.ZenRule m = makeRule();
+
+        // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+        c1.automaticRules.put(r1.id, r1);
+        ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(deleteRule.hasDiff());
+        assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+        assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+        // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+        r2.component = null;
+        r2.pkg = "different";
+        c2.manualRule = m;
+        c2.automaticRules.put(r2.id, r2);
+
+        // Expect diffs in both manual rule (added) and automatic rule (changed)
+        ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+        assertTrue(changed.hasDiff());
+        assertTrue(changed.getManualRuleDiff().hasDiff());
+
+        ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+        assertNotNull(automaticDiffs);
+        assertTrue(automaticDiffs.containsKey("ruleId"));
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+        assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+        assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+    }
+
+    // Helper methods for working with configs, policies, rules
+    // Just makes a zen rule with fields filled in
+    private ZenModeConfig.ZenRule makeRule() {
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.configurationActivity = new ComponentName("a", "a");
+        rule.component = new ComponentName("b", "b");
+        rule.conditionId = new Uri.Builder().scheme("hello").build();
+        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+        rule.enabled = true;
+        rule.creationTime = 123;
+        rule.id = "ruleId";
+        rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.modified = false;
+        rule.name = "name";
+        rule.snoozing = true;
+        rule.pkg = "a";
+        return rule;
+    }
+
+    // Get the fields on which we would want to check a diff. The requirements are: not final or/
+    // static (as these should/can never change), and not in a specific list that's exempted.
+    private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+            throws SecurityException {
+        Field[] fields = c.getDeclaredFields();
+        ArrayList<Field> out = new ArrayList<>();
+
+        for (Field field : fields) {
+            // Check for exempt reasons
+            int m = field.getModifiers();
+            if (Modifier.isFinal(m)
+                    || Modifier.isStatic(m)
+                    || exemptNames.contains(field.getName())) {
+                continue;
+            }
+            out.add(field);
+        }
+        return out;
+    }
+
+    // Generate a set of generic diffs for the specified two objects and the fields to generate
+    // diffs for, and store the results in the provided expectation maps to be able to check the
+    // output later.
+    private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+            ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+            throws Exception {
+        // different classes passed in means bad input
+        assertEquals(a.getClass(), b.getClass());
+
+        // Loop through fields for which we want to check diffs, set a diff and keep track of
+        // what we set.
+        for (Field f : fields) {
+            f.setAccessible(true);
+            // Just double-check also that the fields actually are for the class declared
+            assertEquals(f.getDeclaringClass(), a.getClass());
+            Class t = f.getType();
+            // handle the full set of primitive types first
+            if (boolean.class.equals(t)) {
+                f.setBoolean(a, true);
+                expectedA.put(f.getName(), true);
+                f.setBoolean(b, false);
+                expectedB.put(f.getName(), false);
+            } else if (int.class.equals(t)) {
+                // these are not actually valid going to be valid for arbitrary int enum fields, but
+                // we just put something in there regardless.
+                f.setInt(a, 2);
+                expectedA.put(f.getName(), 2);
+                f.setInt(b, 1);
+                expectedB.put(f.getName(), 1);
+            } else if (long.class.equals(t)) {
+                f.setLong(a, 200L);
+                expectedA.put(f.getName(), 200L);
+                f.setLong(b, 100L);
+                expectedB.put(f.getName(), 100L);
+            } else if (t.isPrimitive()) {
+                // This method doesn't yet handle other primitive types. If the relevant diff
+                // classes gain new fields of these types, please add another clause here.
+                fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+            } else if (String.class.equals(t)) {
+                f.set(a, "string1");
+                expectedA.put(f.getName(), "string1");
+                f.set(b, "string2");
+                expectedB.put(f.getName(), "string2");
+            } else {
+                // catch-all for other types: have the field be "added"
+                f.set(a, null);
+                expectedA.put(f.getName(), null);
+                try {
+                    f.set(b, t.getDeclaredConstructor().newInstance());
+                    expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+                } catch (Exception e) {
+                    // No default constructor, or blithely attempting to construct something doesn't
+                    // work for some reason. If the default value isn't null, then keep it.
+                    if (f.get(b) != null) {
+                        expectedB.put(f.getName(), f.get(b));
+                    } else {
+                        // If we can't even rely on that, fail. Have the test-writer special case
+                        // something, as this is not able to be genericized.
+                        fail("could not generically construct value for field: " + f.getName());
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f9798e..b2a5401 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@
 import android.media.AudioSystem;
 import android.media.VolumePolicy;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -98,6 +94,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
 import android.service.notification.ZenPolicy;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@
         mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
 
         assertEquals("Config mismatch: current vs expected: "
-                + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+                mZenModeHelperSpy.mConfig);
     }
 
     @Test
@@ -1046,7 +1044,8 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         assertEquals(
-                "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+                "Config mismatch: current vs expected: "
+                        + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
         assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
     }
 
@@ -1062,7 +1061,8 @@
         mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
 
         assertEquals("Config mismatch: current vs original: "
-                + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+                + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+                original, mZenModeHelperSpy.mConfig);
         assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
     }
 
@@ -1083,8 +1083,9 @@
 
         ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
         expected.user = 10;
-        assertEquals(
-                "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+        assertEquals("Config mismatch: current vs original: "
+                        + new ZenModeDiff.ConfigDiff(actual, expected),
+                expected, actual);
         assertEquals(expected.hashCode(), actual.hashCode());
         expected.user = 0;
         assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71..e704ebf 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@
         "platform-test-annotations",
         "services.core",
         "services.voiceinteraction",
+        "services.soundtrigger",
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth-prebuilt",
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d..de8d144 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@
 
 filegroup {
     name: "services.voiceinteraction-sources",
-    srcs: ["java/**/*.java"],
+    srcs: ["java/com/android/server/voiceinteraction/*.java"],
     path: "java",
     visibility: ["//frameworks/base/services"],
 }
 
+filegroup {
+    name: "services.soundtrigger_middleware-sources",
+    srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger_service-sources",
+    srcs: ["java/com/android/server/soundtrigger/*.java"],
+    path: "java",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "services.soundtrigger-sources",
+    srcs: [
+        ":services.soundtrigger_service-sources",
+        ":services.soundtrigger_middleware-sources",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.soundtrigger_middleware",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_middleware-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "android.hardware.soundtrigger-V2.3-java",
+    ],
+    visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+    name: "services.soundtrigger",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.soundtrigger_service-sources"],
+    libs: [
+        "services.core",
+    ],
+    static_libs: [
+        "services.soundtrigger_middleware",
+    ],
+}
+
 java_library_static {
     name: "services.voiceinteraction",
     defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c04..203a3e7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -39,12 +39,13 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +65,7 @@
 import android.media.soundtrigger.ISoundTriggerDetectionService;
 import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
 import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
 import android.os.Binder;
 import android.os.Bundle;
@@ -74,8 +76,8 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceSpecificException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -86,6 +88,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.utils.EventLogger;
 
@@ -98,8 +101,8 @@
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.UUID;
-import java.util.stream.Collectors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +299,23 @@
                 return listUnderlyingModuleProperties(originatorIdentity);
             }
         }
+
+        @Override
+        public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+            if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+                    android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+                        != PermissionChecker.PERMISSION_GRANTED) {
+                throw new SecurityException();
+            }
+            try {
+                ISoundTriggerMiddlewareService.Stub
+                        .asInterface(ServiceManager
+                                .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+                        .attachFakeHalInjection(injection);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9e..5846ff6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@
  *
  * @hide
  */
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
     static final String TAG = "SoundModelDBHelper";
     static final boolean DBG = false;
 
@@ -153,11 +153,7 @@
         }
     }
 
-    /**
-     * Updates the given keyphrase model, adds it, if it doesn't already exist.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
         synchronized(this) {
             SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@
         }
     }
 
-    /**
-     * Deletes the sound model and associated keyphrases.
-     */
+    @Override
     public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
         // Normalize the locale to guard against SQL injection.
         bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@
         }
     }
 
-    /**
-     * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
-     * Returns null if a match isn't found.
-     *
-     * TODO: We only support one keyphrase currently.
-     */
+    @Override
     public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
             String bcp47Locale) {
         // Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 0000000..f10c2f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -0,0 +1,90 @@
+/**
+ * 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.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+     *
+     * @param soundModel - The sound model to register in the database.
+     * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+     * Must have one and only one associated {@link Keyphrase}.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+    /**
+     * Deletes the previously registered keyphrase sound model from the database.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale);
+
+    //TODO(273286174): We only support one keyphrase currently.
+    /**
+     * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+     * contingent on the userHandle existing in the user list for the model.
+     * Returns null if a match isn't found.
+     *
+     * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+     * @param userHandle - The user handle making this request. Must be included in the user
+     *                     list of the registered sound model.
+     * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+     * @return - {@code true} if successful, {@code false} if unsuccessful
+     */
+    KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale);
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 0000000..9bbaf8e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * 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.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+    // Record representing the primary key used in the real model database.
+    private static final class EnrollmentKey {
+        private final int mKeyphraseId;
+        private final List<Integer> mUserIds;
+        private final String mLocale;
+
+        EnrollmentKey(int keyphraseId,
+                @NonNull List<Integer> userIds, @NonNull String locale) {
+            mKeyphraseId = keyphraseId;
+            mUserIds = Objects.requireNonNull(userIds);
+            mLocale = Objects.requireNonNull(locale);
+        }
+
+        int keyphraseId() {
+            return mKeyphraseId;
+        }
+
+        List<Integer> userIds() {
+            return mUserIds;
+        }
+
+        String locale() {
+            return mLocale;
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner sj = new StringJoiner(", ", "{", "}");
+            sj.add("keyphraseId: " + mKeyphraseId);
+            sj.add("userIds: " + mUserIds.toString());
+            sj.add("locale: " + mLocale.toString());
+            return "EnrollmentKey: " + sj.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int res = 1;
+            res = prime * res + mKeyphraseId;
+            res = prime * res + mUserIds.hashCode();
+            res = prime * res + mLocale.hashCode();
+            return res;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) return true;
+            if (other == null) return false;
+            if (!(other instanceof EnrollmentKey)) return false;
+            EnrollmentKey that = (EnrollmentKey) other;
+            if (mKeyphraseId != that.mKeyphraseId) return false;
+            if (!mUserIds.equals(that.mUserIds)) return false;
+            if (!mLocale.equals(that.mLocale)) return false;
+            return true;
+        }
+
+    }
+
+    private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+    @Override
+    public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+        final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+        mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+                        Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+                        keyphrase.getLocale().toLanguageTag()),
+                    soundModel);
+        return true;
+    }
+
+    @Override
+    public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+        return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+                && key.locale().equals(bcp47Locale)
+                && key.userIds().contains(userHandle));
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+    @Override
+    public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+            String bcp47Locale) {
+        return mModelMap.entrySet()
+                .stream()
+                .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+                        && entry.getKey().locale().equals(bcp47Locale)
+                        && entry.getKey().userIds().contains(userHandle)))
+                .findFirst()
+                .map((entry) -> entry.getValue())
+                .orElse(null);
+    }
+
+
+    /**
+     * Dumps contents of database for dumpsys
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Using test enrollment database, with enrolled models:");
+        pw.println(mModelMap);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca..1d7b966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@
 
     final Context mContext;
     final ContentResolver mResolver;
-    final DatabaseHelper mDbHelper;
+    // Can be overridden for testing purposes
+    private IEnrolledModelDb mDbHelper;
+    private final IEnrolledModelDb mRealDbHelper;
     final ActivityManagerInternal mAmInternal;
     final ActivityTaskManagerInternal mAtmInternal;
     final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@
         mResolver = context.getContentResolver();
         mUserManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(UserManagerInternal.class));
-        mDbHelper = new DatabaseHelper(context);
+        mDbHelper = mRealDbHelper = new DatabaseHelper(context);
         mServiceStub = new VoiceInteractionManagerServiceStub();
         mAmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@
             }
         }
 
+        @Override
+        @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+        public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+            super.setModelDatabaseForTestEnabled_enforcePermission();
+            enforceCallerAllowedToEnrollVoiceModel();
+            synchronized (this) {
+                if (enabled) {
+                    // Replace the dbhelper with a new test db
+                    final var db = new TestModelEnrollmentDatabase();
+                    try {
+                        // Listen to our caller death, and make sure we revert to the real
+                        // db if they left the model in a test state.
+                        token.linkToDeath(() -> {
+                            synchronized (this) {
+                                if (mDbHelper == db) {
+                                    mDbHelper = mRealDbHelper;
+                                    mImpl.notifySoundModelsChangedLocked();
+                                }
+                            }
+                        }, 0);
+                    } catch (RemoteException e) {
+                        // If the caller is already dead, nothing to do.
+                        return;
+                    }
+                    mDbHelper = db;
+                    mImpl.notifySoundModelsChangedLocked();
+                } else {
+                    // Nothing to do if the db is already set to the real impl.
+                    if (mDbHelper != mRealDbHelper) {
+                        mDbHelper = mRealDbHelper;
+                        mImpl.notifySoundModelsChangedLocked();
+                    }
+                }
+            }
+        }
+
         //----------------- SoundTrigger APIs --------------------------------//
         @Override
         public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@
                 final long caller = Binder.clearCallingIdentity();
                 try {
                     KeyphraseSoundModel soundModel =
-                            mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+                            mDbHelper.getKeyphraseSoundModel(keyphraseId,
+                                    callingUserId, bcp47Locale);
                     if (soundModel == null
                             || soundModel.getUuid() == null
                             || soundModel.getKeyphrases() == null) {
                         Slog.w(TAG, "No matching sound model found in startRecognition");
                         return SoundTriggerInternal.STATUS_ERROR;
-                    } else {
-                        // Regardless of the status of the start recognition, we need to make sure
-                        // that we unload this model if needed later.
-                        synchronized (VoiceInteractionManagerServiceStub.this) {
-                            mLoadedKeyphraseIds.put(keyphraseId, this);
-                            if (mSessionExternalCallback == null
-                                    || mSessionInternalCallback == null
-                                    || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                                mSessionInternalCallback = createSoundTriggerCallbackLocked(
-                                        callback);
-                                mSessionExternalCallback = callback;
-                            }
-                        }
-                        return mSession.startRecognition(keyphraseId, soundModel,
-                                mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                     }
+                    // Regardless of the status of the start recognition, we need to make sure
+                    // that we unload this model if needed later.
+                    synchronized (VoiceInteractionManagerServiceStub.this) {
+                        mLoadedKeyphraseIds.put(keyphraseId, this);
+                        if (mSessionExternalCallback == null
+                                || mSessionInternalCallback == null
+                                || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+                            mSessionExternalCallback = callback;
+                        }
+                    }
+                    return mSession.startRecognition(keyphraseId, soundModel,
+                            mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a55..0ad86c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@
                 Context.RECEIVER_EXPORTED);
     }
 
-    @GuardedBy("this")
     public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
         final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
         final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@
                 /* direct= */ true);
     }
 
-    @GuardedBy("this")
     public boolean showSessionLocked(@Nullable Bundle args, int flags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@
         }
     }
 
-    @GuardedBy("this")
     public boolean hideSessionLocked() {
         if (mActiveSession != null) {
             return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@
         return false;
     }
 
-    @GuardedBy("this")
     public boolean deliverNewSessionLocked(IBinder token,
             IVoiceInteractionSession session, IVoiceInteractor interactor) {
         if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@
         return true;
     }
 
-    @GuardedBy("this")
     public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType) {
         try {
@@ -373,7 +367,6 @@
         }
     }
 
-    @GuardedBy("this")
     public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
             int callingUid, IBinder token, Intent intent, String resolvedType,
             @NonNull Bundle bundle) {
@@ -397,7 +390,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
             @NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@
         }
     }
 
-    @GuardedBy("this")
     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
             @Nullable Bundle arguments, int taskId, IBinder assistToken,
             @Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void closeSystemDialogsLocked(IBinder token) {
         try {
             if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void finishLocked(IBinder token, boolean finishTask) {
         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
             Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@
         mActiveSession = null;
     }
 
-    @GuardedBy("this")
     public void setDisabledShowContextLocked(int callingUid, int flags) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -526,7 +513,6 @@
         mDisabledShowContext = flags;
     }
 
-    @GuardedBy("this")
     public int getDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -536,7 +522,6 @@
         return mDisabledShowContext;
     }
 
-    @GuardedBy("this")
     public int getUserDisabledShowContextLocked(int callingUid) {
         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
         if (callingUid != activeUid) {
@@ -550,7 +535,6 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
-    @GuardedBy("this")
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@
         mActiveSession.startListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@
         mActiveSession.stopListeningVisibleActivityChangedLocked();
     }
 
-    @GuardedBy("this")
     public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@
         mActiveSession.notifyActivityDestroyedLocked(activityToken);
     }
 
-    @GuardedBy("this")
     public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
         if (DEBUG) {
             Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@
         mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
     }
 
-    @GuardedBy("this")
     public void updateStateLocked(
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@
         }
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForHotwordDetectionLocked(
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@
                 voiceInteractionServiceUid);
     }
 
-    @GuardedBy("this")
     private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
         Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
 
@@ -724,7 +702,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void initAndVerifyDetectorLocked(
             @NonNull Identity voiceInteractorIdentity,
             @Nullable PersistableBundle options,
@@ -769,7 +746,6 @@
                 detectorType);
     }
 
-    @GuardedBy("this")
     public void destroyDetectorLocked(IBinder token) {
         Slog.v(TAG, "destroyDetectorLocked");
 
@@ -788,7 +764,6 @@
         }
     }
 
-    @GuardedBy("this")
     public void shutdownHotwordDetectionServiceLocked() {
         if (DEBUG) {
             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void setVisualQueryDetectionAttentionListenerLocked(
             @Nullable IVisualQueryDetectionAttentionListener listener) {
         if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    @GuardedBy("this")
     public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@
         mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    @GuardedBy("this")
     public void stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@
         mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
-    @GuardedBy("this")
     public void startListeningFromMicLocked(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@
         mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
     }
 
-    @GuardedBy("this")
     public void startListeningFromExternalSourceLocked(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -879,7 +849,6 @@
                 options, token, callback);
     }
 
-    @GuardedBy("this")
     public void stopListeningFromMicLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@
         mHotwordDetectionConnection.stopListeningFromMicLocked();
     }
 
-    @GuardedBy("this")
     public void triggerHardwareRecognitionEventForTestLocked(
             SoundTrigger.KeyphraseRecognitionEvent event,
             IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@
         mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
     }
 
-    @GuardedBy("this")
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
             IHotwordRecognitionStatusCallback callback) {
         if (DEBUG) {
@@ -933,12 +900,11 @@
         return null;
     }
 
-    @GuardedBy("this")
     boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
     }
-    @GuardedBy("this")
+
     boolean verifyProcessSharingLocked() {
         // only check this if both VQDS and HDS are declared in the app
         ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
-    @GuardedBy("this")
     void setDebugHotwordLoggingLocked(boolean logging) {
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@
         mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
     }
 
-    @GuardedBy("this")
     void resetHotwordDetectionConnectionLocked() {
         if (DEBUG) {
             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@
         mHotwordDetectionConnection = null;
     }
 
-    @GuardedBy("this")
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!mValid) {
             pw.print("  NOT VALID: ");
@@ -1023,7 +986,6 @@
         }
     }
 
-    @GuardedBy("this")
     void startLocked() {
         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
         intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@
         }
     }
 
-    @GuardedBy("this")
     void shutdownLocked() {
         // If there is an active session, cancel it to allow it to clean up its window and other
         // state.
@@ -1076,7 +1037,6 @@
         }
     }
 
-    @GuardedBy("this")
     void notifySoundModelsChangedLocked() {
         if (mService == null) {
             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5a..9dd2a61 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@
     }
 
     /**
-     * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
-     * method can be used to report both incoming and outgoing calls.  By reporting the call, the
-     * system is aware of the call and can provide updates on services (ex. Another device wants to
-     * disconnect the call) or events (ex. a new Bluetooth route became available).
-     *
+     * Add a call to the Android system service Telecom. This allows the system to start tracking an
+     * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+     * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+     * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
      * <p>
-     * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
-     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
-     * will asynchronously provide an update on whether the new call was added successfully via
-     * an {@link OutcomeReceiver}.  Additionally, callbacks will run on the executor thread that was
-     * passed in.
-     *
      * <p>
-     * Note: Only packages that register with
+     * <p>
+     * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+     * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+     * When your application is given foreground execution priority, your app is treated as a
+     * foreground service. Foreground execution priority will prevent the
+     * {@link android.app.ActivityManager} from killing your application when it is placed the
+     * background. Foreground execution priority is removed from your app when all of your app's
+     * calls terminate or your app no longer posts a valid notification.
+     * <p>
+     * <p>
+     * <p>
+     * <b>Note</b>: Only packages that register with
      * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
      * can utilize this API. {@link PhoneAccount}s that set the capabilities
      * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
      * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
      * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
      * are not supported and will cause an exception to be thrown.
-     *
      * <p>
-     * Usage example:
+     * <p>
+     * <p>
+     * <b>Usage example:</b>
      * <pre>
-     *
-     *  // An app should first define their own construct of a Call that overrides all the
-     *  // {@link CallControlCallback}s and {@link CallEventCallback}s
-     *  private class MyVoipCall {
-     *   public String callId = "";
-     *
-     *   public CallControlCallEventCallback handshakes = new
-     *                       CallControlCallEventCallback() {
-     *                         // override/ implement all {@link CallControlCallback}s
+     *  // Its up to your app on how you want to wrap the objects. One such implementation can be:
+     *  class MyVoipCall {
+     *    ...
+     *      public CallControlCallEventCallback handshakes = new  CallControlCallback() {
+     *                         ...
      *                        }
-     *   public CallEventCallback events = new
-     *                       CallEventCallback() {
-     *                         // override/ implement all {@link CallEventCallback}s
+     *
+     *      public CallEventCallback events = new CallEventCallback() {
+     *                         ...
      *                        }
-     *   public MyVoipCall(String id){
-     *       callId = id;
+     *
+     *      public MyVoipCall(String id){
+     *          ...
+     *      }
      *  }
      *
-     * PhoneAccountHandle handle = new PhoneAccountHandle(
-     *                          new ComponentName("com.example.voip.app",
-     *                                            "com.example.voip.app.NewCallActivity"), "123");
-     *
-     * CallAttributes callAttributes = new CallAttributes.Builder(handle,
-     *                                             CallAttributes.DIRECTION_OUTGOING,
-     *                                            "John Smith", Uri.fromParts("tel", "123", null))
-     *                                            .build();
-     *
      * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
      *
-     * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+     * telecomManager.addCall(callAttributes,
+     *                        Runnable::run,
+     *                        new OutcomeReceiver() {
      *                              public void onResult(CallControl callControl) {
-     *                                 // The call has been added successfully
+     *                                 // The call has been added successfully. For demonstration
+     *                                 // purposes, the call is disconnected immediately ...
+     *                                 callControl.disconnect(
+     *                                                 new DisconnectCause(DisconnectCause.LOCAL) )
      *                              }
-     *                           }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+     *                           },
+     *                           myFirstOutgoingCall.handshakes,
+     *                           myFirstOutgoingCall.events);
      * </pre>
      *
-     * @param callAttributes    attributes of the new call (incoming or outgoing, address, etc. )
-     * @param executor          thread to run background CallEventCallback updates on
-     * @param pendingControl    OutcomeReceiver that receives the result of addCall transaction
-     * @param handshakes        object that overrides {@link CallControlCallback}s
-     * @param events            object that overrides {@link CallEventCallback}s
+     * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+     * @param executor       execution context to run {@link CallControlCallback} updates on
+     * @param pendingControl Receives the result of addCall transaction. Upon success, a
+     *                       CallControl object is provided which can be used to do things like
+     *                       disconnect the call that was added.
+     * @param handshakes     callback that receives <b>actionable</b> updates that originate from
+     *                       Telecom.
+     * @param events         callback that receives <b>non</b>-actionable updates that originate
+     *                       from Telecom.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
     @SuppressLint("SamShouldBeLast")
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 25fbabc..166fbdd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@
         /**
          * Sets the displayed connection strength of the remote device to the internet.
          *
-         * @param connectionStrength Connection strength in range 0 to 3.
+         * @param connectionStrength Connection strength in range 0 to 4.
          * @return Returns the Builder object.
          */
         @NonNull
-        public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+        public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
             mConnectionStrength = connectionStrength;
             return this;
         }
@@ -205,8 +205,8 @@
         if (batteryPercentage < 0 || batteryPercentage > 100) {
             throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
         }
-        if (connectionStrength < 0 || connectionStrength > 3) {
-            throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+        if (connectionStrength < 0 || connectionStrength > 4) {
+            throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
         }
     }
 
@@ -265,9 +265,9 @@
     /**
      * Gets the displayed connection strength of the remote device to the internet.
      *
-     * @return Returns the connection strength in range 0 to 3.
+     * @return Returns the connection strength in range 0 to 4.
      */
-    @IntRange(from = 0, to = 3)
+    @IntRange(from = 0, to = 4)
     public int getConnectionStrength() {
         return mConnectionStrength;
     }