Merge "Provide isUserInLockdown through BiometricSettingsRepository" into udc-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index b92295a..51c6f6a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43413,6 +43413,8 @@
     field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
     field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
     field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+    field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
+    field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
@@ -50901,6 +50903,10 @@
     field public static final int KEYCODE_LAST_CHANNEL = 229; // 0xe5
     field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47
     field public static final int KEYCODE_M = 41; // 0x29
+    field public static final int KEYCODE_MACRO_1 = 313; // 0x139
+    field public static final int KEYCODE_MACRO_2 = 314; // 0x13a
+    field public static final int KEYCODE_MACRO_3 = 315; // 0x13b
+    field public static final int KEYCODE_MACRO_4 = 316; // 0x13c
     field public static final int KEYCODE_MANNER_MODE = 205; // 0xcd
     field public static final int KEYCODE_MEDIA_AUDIO_TRACK = 222; // 0xde
     field public static final int KEYCODE_MEDIA_CLOSE = 128; // 0x80
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e93467b..1a8ebb1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -915,6 +915,12 @@
     field public static final long FORCE_NON_RESIZE_APP = 181136395L; // 0xacbec0bL
     field public static final long FORCE_RESIZE_APP = 174042936L; // 0xa5faf38L
     field public static final long NEVER_SANDBOX_DISPLAY_APIS = 184838306L; // 0xb0468a2L
+    field public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION = 263959004L; // 0xfbbb1dcL
+    field public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // 0xfc0f74bL
+    field public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // 0xfc0ec12L
+    field public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L; // 0xfb1048bL
+    field public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION = 254631730L; // 0xf2d5f32L
+    field public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L; // 0xfdcbe7fL
     field public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // 0xa5faf64L
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN = 218959984L; // 0xd0d1070L
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_LARGE = 180326787L; // 0xabf9183L
@@ -924,6 +930,9 @@
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
     field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
+    field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L; // 0xfd27655L
+    field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; // 0xfd27b38L
+    field public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; // 0xf4156bcL
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
   }
 
@@ -3332,7 +3341,7 @@
     method public static String actionToString(int);
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
-    field public static final int LAST_KEYCODE = 312; // 0x138
+    field public static final int LAST_KEYCODE = 316; // 0x13c
   }
 
   public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index d23d3cd..6357798 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -37,8 +37,6 @@
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerExemptionManager.TempAllowListType;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -61,7 +59,8 @@
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private long mIdForResponseEvent;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
-    private @Nullable String mDeliveryGroupMatchingKey;
+    private @Nullable String mDeliveryGroupMatchingNamespaceFragment;
+    private @Nullable String mDeliveryGroupMatchingKeyFragment;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
     private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
     private @DeferralPolicy int mDeferralPolicy;
@@ -196,7 +195,13 @@
             "android:broadcast.deliveryGroupPolicy";
 
     /**
-     * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * Corresponds to namespace fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
+     */
+    private static final String KEY_DELIVERY_GROUP_NAMESPACE =
+            "android:broadcast.deliveryGroupMatchingNamespace";
+
+    /**
+     * Corresponds to key fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     private static final String KEY_DELIVERY_GROUP_KEY =
             "android:broadcast.deliveryGroupMatchingKey";
@@ -337,7 +342,8 @@
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
-        mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+        mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
+        mDeliveryGroupMatchingKeyFragment = opts.getString(KEY_DELIVERY_GROUP_KEY);
         mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
                 BundleMerger.class);
         mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
@@ -851,11 +857,8 @@
     @NonNull
     public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
             @NonNull String key) {
-        Preconditions.checkArgument(!namespace.contains(":"),
-                "namespace should not contain ':'");
-        Preconditions.checkArgument(!key.contains(":"),
-                "key should not contain ':'");
-        mDeliveryGroupMatchingKey = namespace + ":" + key;
+        mDeliveryGroupMatchingNamespaceFragment = Objects.requireNonNull(namespace);
+        mDeliveryGroupMatchingKeyFragment = Objects.requireNonNull(key);
         return this;
     }
 
@@ -868,7 +871,38 @@
      */
     @Nullable
     public String getDeliveryGroupMatchingKey() {
-        return mDeliveryGroupMatchingKey;
+        if (mDeliveryGroupMatchingNamespaceFragment == null
+                || mDeliveryGroupMatchingKeyFragment == null) {
+            return null;
+        }
+        return String.join(":", mDeliveryGroupMatchingNamespaceFragment,
+                mDeliveryGroupMatchingKeyFragment);
+    }
+
+    /**
+     * Return the namespace fragment that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group namespace fragment that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * @hide
+     */
+    @Nullable
+    public String getDeliveryGroupMatchingNamespaceFragment() {
+        return mDeliveryGroupMatchingNamespaceFragment;
+    }
+
+    /**
+     * Return the key fragment that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group key fragment that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * @hide
+     */
+    @Nullable
+    public String getDeliveryGroupMatchingKeyFragment() {
+        return mDeliveryGroupMatchingKeyFragment;
     }
 
     /**
@@ -876,7 +910,8 @@
      * {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     public void clearDeliveryGroupMatchingKey() {
-        mDeliveryGroupMatchingKey = null;
+        mDeliveryGroupMatchingNamespaceFragment = null;
+        mDeliveryGroupMatchingKeyFragment = null;
     }
 
     /**
@@ -1094,8 +1129,11 @@
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
-        if (mDeliveryGroupMatchingKey != null) {
-            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
+        if (mDeliveryGroupMatchingNamespaceFragment != null) {
+            b.putString(KEY_DELIVERY_GROUP_NAMESPACE, mDeliveryGroupMatchingNamespaceFragment);
+        }
+        if (mDeliveryGroupMatchingKeyFragment != null) {
+            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKeyFragment);
         }
         if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
             if (mDeliveryGroupExtrasMerger != null) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8aa9b73..b5d2f2c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1083,6 +1083,7 @@
     @ChangeId
     @Overridable
     @Disabled
+    @TestApi
     public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION =
             254631730L; // buganizer id
 
@@ -1142,6 +1143,7 @@
     @ChangeId
     @Overridable
     @Disabled
+    @TestApi
     public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION =
             263959004L; // buganizer id
 
@@ -1154,6 +1156,7 @@
     @ChangeId
     @Overridable
     @Disabled
+    @TestApi
     public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id
 
     /**
@@ -1166,6 +1169,7 @@
     @ChangeId
     @Overridable
     @Disabled
+    @TestApi
     public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
             264301586L; // buganizer id
 
@@ -1292,6 +1296,7 @@
     @ChangeId
     @Disabled
     @Overridable
+    @TestApi
     public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L;
 
     // Compat framework that per-app overrides rely on only supports booleans. That's why we have
@@ -1307,6 +1312,7 @@
     @ChangeId
     @Disabled
     @Overridable
+    @TestApi
     public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L;
 
     /**
@@ -1318,6 +1324,7 @@
     @ChangeId
     @Disabled
     @Overridable
+    @TestApi
     public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L;
 
     /**
@@ -1331,6 +1338,7 @@
     @ChangeId
     @Disabled
     @Overridable
+    @TestApi
     public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L;
 
     /**
@@ -1377,6 +1385,7 @@
     @ChangeId
     @Disabled
     @Overridable
+    @TestApi
     public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
 
     /**
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 2af0254..b6d9400 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -897,13 +897,38 @@
      * This key is handled by the framework and is never delivered to applications.
      */
     public static final int KEYCODE_RECENT_APPS = 312;
+    /**
+     * Key code constant: A button whose usage can be customized by the user through
+     *                    the system.
+     * User customizable key #1.
+     */
+    public static final int KEYCODE_MACRO_1 = 313;
+    /**
+     * Key code constant: A button whose usage can be customized by the user through
+     *                    the system.
+     * User customizable key #2.
+     */
+    public static final int KEYCODE_MACRO_2 = 314;
+    /**
+     * Key code constant: A button whose usage can be customized by the user through
+     *                    the system.
+     * User customizable key #3.
+     */
+    public static final int KEYCODE_MACRO_3 = 315;
+    /**
+     * Key code constant: A button whose usage can be customized by the user through
+     *                    the system.
+     * User customizable key #4.
+     */
+    public static final int KEYCODE_MACRO_4 = 316;
+
 
    /**
      * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
      * @hide
      */
     @TestApi
-    public static final int LAST_KEYCODE = KEYCODE_RECENT_APPS;
+    public static final int LAST_KEYCODE = KEYCODE_MACRO_4;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 82e1777..d1f7b63 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -389,6 +389,7 @@
     optional bool provides_max_bounds = 34;
     optional bool enable_recents_screenshot = 35;
     optional int32 last_drop_input_mode = 36;
+    optional int32 override_orientation = 37 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"];
 }
 
 /* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f0ef14..393e778 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1374,8 +1374,7 @@
         android:permissionFlags="hardRestricted"
         android:protectionLevel="dangerous" />
 
-    <!-- Allows an application to write (but not read) the user's
-         call log data.
+    <!-- Allows an application to write and read the user's call log data.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #WRITE_CONTACTS} permission and <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 247d484..e27cd97 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -424,6 +424,10 @@
 key 582   VOICE_ASSIST
 # Linux KEY_ASSISTANT
 key 583   ASSIST
+key 656   MACRO_1
+key 657   MACRO_2
+key 658   MACRO_3
+key 659   MACRO_4
 
 # Keys defined by HID usages
 key usage 0x0c0067 WINDOW
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index b93cc75..6312913 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -23,8 +23,8 @@
     <bool name="def_airplane_mode_on">false</bool>
     <bool name="def_theater_mode_on">false</bool>
     <!-- Comma-separated list of bluetooth, wifi, and cell. -->
-    <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string>
-    <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string>
+    <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,uwb,wifi,wimax</string>
+    <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi</string>
     <string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
     <bool name="def_auto_time">true</bool>
     <bool name="def_auto_time_zone">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 721b3c4..e736253 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3739,7 +3739,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 215;
+            private static final int SETTINGS_VERSION = 216;
 
             private final int mUserId;
 
@@ -5737,6 +5737,31 @@
                     currentVersion = 215;
                 }
 
+                if (currentVersion == 215) {
+                    // Version 215: default |def_airplane_mode_radios| and
+                    // |airplane_mode_toggleable_radios| changed to remove NFC & add UWB.
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final String oldApmRadiosValue = globalSettings.getSettingLocked(
+                            Settings.Global.AIRPLANE_MODE_RADIOS).getValue();
+                    if (TextUtils.equals("cell,bluetooth,wifi,nfc,wimax", oldApmRadiosValue)) {
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
+                                Settings.Global.AIRPLANE_MODE_RADIOS,
+                                getContext().getResources().getString(
+                                        R.string.def_airplane_mode_radios),
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    final String oldApmToggleableRadiosValue = globalSettings.getSettingLocked(
+                            Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS).getValue();
+                    if (TextUtils.equals("bluetooth,wifi,nfc", oldApmToggleableRadiosValue)) {
+                        globalSettings.insertSettingOverrideableByRestoreLocked(
+                                Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
+                                getContext().getResources().getString(
+                                        R.string.airplane_mode_toggleable_radios),
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    currentVersion = 216;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d92e65c..ff57052 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -947,18 +947,6 @@
                   android:visibleToInstantApps="true">
         </activity>
 
-        <activity android:name=".user.UserSwitcherActivity"
-                  android:label="@string/accessibility_multi_user_switch_switcher"
-                  android:theme="@style/Theme.UserSwitcherActivity"
-                  android:excludeFromRecents="true"
-                  android:showWhenLocked="true"
-                  android:showForAllUsers="true"
-                  android:finishOnTaskLaunch="true"
-                  android:lockTaskMode="always"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
-                  android:visibleToInstantApps="true">
-        </activity>
-
         <receiver android:name=".controls.management.ControlsRequestReceiver"
             android:exported="true">
             <intent-filter>
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
index b9509eb..01cba42 100644
--- a/packages/SystemUI/docs/user-switching.md
+++ b/packages/SystemUI/docs/user-switching.md
@@ -6,7 +6,7 @@
 
 ### Quick Settings
 
-In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherActivity][5]).
+In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherFullscreenDialog][5]).
 
 ### Bouncer
 
@@ -29,7 +29,7 @@
 
 ## Visual Components
 
-### [UserSwitcherActivity][5]
+### [UserSwitcherFullscreenDialog][5]
 
 A fullscreen user switching activity, supporting add guest/user actions if configured.
 
@@ -41,5 +41,5 @@
 [2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
 [3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
 [4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
-[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
 [6]: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2fb1592..a3655c31 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -844,12 +844,10 @@
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
     </style>
 
-    <style name="Theme.UserSwitcherActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+    <style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
         <item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
         <item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
         <item name="android:navigationBarColor">@color/user_switcher_fullscreen_bg</item>
-        <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
-        <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
     </style>
 
     <style name="Theme.CreateUser" parent="@android:style/Theme.DeviceDefault.NoActionBar">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index a010c9a..d221e22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -157,15 +156,6 @@
         // TODO: Remove this workaround by ensuring such a race condition never happens.
         mMainExecutor.executeDelayed(
                 this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
-        mView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
-            @Override
-            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
-                if (!mKeyguardViewController.isBouncerShowing()) {
-                    mView.hideKeyboard();
-                }
-                return insets;
-            }
-        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 30321f7..e1bca89 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -99,11 +99,13 @@
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.usb.UsbManager;
 import android.nfc.NfcAdapter;
 import android.os.CancellationSignal;
@@ -172,6 +174,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -1899,8 +1902,9 @@
     CancellationSignal mFingerprintCancelSignal;
     @VisibleForTesting
     CancellationSignal mFaceCancelSignal;
-    private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
-    private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
+    private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties =
+            Collections.emptyList();
+    private List<FaceSensorPropertiesInternal> mFaceSensorProperties = Collections.emptyList();
     private boolean mFingerprintLockedOut;
     private boolean mFingerprintLockedOutPermanent;
     private boolean mFaceLockedOutPermanent;
@@ -2366,11 +2370,29 @@
         setStrongAuthTracker(mStrongAuthTracker);
 
         if (mFpm != null) {
-            mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
+            mFpm.addAuthenticatorsRegisteredCallback(
+                    new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                        @Override
+                        public void onAllAuthenticatorsRegistered(
+                                List<FingerprintSensorPropertiesInternal> sensors)
+                                throws RemoteException {
+                            mFingerprintSensorProperties = sensors;
+                            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+                            mLogger.d("FingerprintManager onAllAuthenticatorsRegistered");
+                        }
+                    });
             mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
         if (mFaceManager != null) {
-            mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
+            mFaceManager.addAuthenticatorsRegisteredCallback(
+                    new IFaceAuthenticatorsRegisteredCallback.Stub() {
+                        @Override
+                        public void onAllAuthenticatorsRegistered(
+                                List<FaceSensorPropertiesInternal> sensors) throws RemoteException {
+                            mFaceSensorProperties = sensors;
+                            mLogger.d("FaceManager onAllAuthenticatorsRegistered");
+                        }
+                    });
             mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 79a51d6..7ffb594 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -701,6 +701,6 @@
 
     // TODO(b/272805037): Tracking Bug
     @JvmField
-    val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
-            namespace = "vpn", teamfood = true)
+    val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature",
+            namespace = "vpn")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 8387c1d..b394a07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -89,7 +89,7 @@
     fun showSettings(expandable: Expandable)
 
     /** Show the user switcher. */
-    fun showUserSwitcher(context: Context, expandable: Expandable)
+    fun showUserSwitcher(expandable: Expandable)
 }
 
 @SysUISingleton
@@ -177,7 +177,7 @@
         )
     }
 
-    override fun showUserSwitcher(context: Context, expandable: Expandable) {
-        userInteractor.showUserSwitcher(context, expandable)
+    override fun showUserSwitcher(expandable: Expandable) {
+        userInteractor.showUserSwitcher(expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index f170ac1..3a9098a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -230,7 +230,7 @@
             return
         }
 
-        footerActionsInteractor.showUserSwitcher(context, expandable)
+        footerActionsInteractor.showUserSwitcher(expandable)
     }
 
     private fun onSettingsButtonClicked(expandable: Expandable) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index f7c8bac..b2bf972 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.user;
 
-import android.app.Activity;
 import android.os.UserHandle;
 
 import com.android.settingslib.users.EditUserInfoController;
@@ -24,11 +23,8 @@
 import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
 import com.android.systemui.user.ui.dialog.UserDialogModule;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
 
 /**
  * Dagger module for User related classes.
@@ -49,12 +45,6 @@
         return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
     }
 
-    /** Provides UserSwitcherActivity */
-    @Binds
-    @IntoMap
-    @ClassKey(UserSwitcherActivity.class)
-    public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
-
     /**
      * Provides the {@link UserHandle} for the user associated with this System UI process.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
deleted file mode 100644
index 52b7fb6..0000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user
-
-import android.os.Bundle
-import android.view.WindowInsets.Type
-import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import dagger.Lazy
-import javax.inject.Inject
-
-/** Support a fullscreen user switcher */
-open class UserSwitcherActivity
-@Inject
-constructor(
-    private val falsingCollector: FalsingCollector,
-    private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
-) : ComponentActivity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(R.layout.user_switcher_fullscreen)
-        window.decorView.windowInsetsController?.let { controller ->
-            controller.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-            controller.hide(Type.systemBars())
-        }
-        val viewModel =
-            ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
-        UserSwitcherViewBinder.bind(
-            view = requireViewById(R.id.user_switcher_root),
-            viewModel = viewModel,
-            lifecycleOwner = this,
-            layoutInflater = layoutInflater,
-            falsingCollector = falsingCollector,
-            onFinish = this::finish,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
new file mode 100644
index 0000000..72786ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.user
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowInsets
+import android.view.WindowInsetsController
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+
+class UserSwitchFullscreenDialog(
+    context: Context,
+    private val falsingCollector: FalsingCollector,
+    private val userSwitcherViewModel: UserSwitcherViewModel,
+) : SystemUIDialog(context, R.style.Theme_UserSwitcherFullscreenDialog) {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setShowForAllUsers(true)
+        setCanceledOnTouchOutside(true)
+
+        window?.decorView?.windowInsetsController?.let { controller ->
+            controller.systemBarsBehavior =
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+            controller.hide(WindowInsets.Type.systemBars())
+        }
+
+        val view =
+            LayoutInflater.from(this.context).inflate(R.layout.user_switcher_fullscreen, null)
+        setContentView(view)
+
+        UserSwitcherViewBinder.bind(
+            view = requireViewById(R.id.user_switcher_root),
+            viewModel = userSwitcherViewModel,
+            layoutInflater = layoutInflater,
+            falsingCollector = falsingCollector,
+            onFinish = this::dismiss,
+        )
+    }
+
+    override fun getWidth(): Int {
+        val displayMetrics = context.resources.displayMetrics.apply {
+            context.display.getRealMetrics(this)
+        }
+        return displayMetrics.widthPixels
+    }
+
+    override fun getHeight() = MATCH_PARENT
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 94dd1b3..0ec1a21 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -49,7 +49,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.data.source.UserRecord
@@ -513,24 +512,12 @@
         }
     }
 
-    fun showUserSwitcher(context: Context, expandable: Expandable) {
-        if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+    fun showUserSwitcher(expandable: Expandable) {
+        if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+            showDialog(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+        } else {
             showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
-            return
         }
-
-        val intent =
-            Intent(context, UserSwitcherActivity::class.java).apply {
-                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-            }
-
-        activityStarter.startActivity(
-            intent,
-            true /* dismissShade */,
-            expandable.activityLaunchController(),
-            true /* showOverlockscreenwhenlocked */,
-            UserHandle.SYSTEM,
-        )
     }
 
     private fun showDialog(request: ShowDialogRequestModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 14cc3e7..de73cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -50,4 +50,8 @@
     data class ShowUserSwitcherDialog(
         override val expandable: Expandable?,
     ) : ShowDialogRequestModel()
+
+    data class ShowUserSwitcherFullscreenDialog(
+        override val expandable: Expandable?,
+    ) : ShowDialogRequestModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index e137107..7236e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -31,19 +31,18 @@
 import androidx.constraintlayout.helper.widget.Flow as FlowWidget
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.user.UserSwitcherPopupMenu
 import com.android.systemui.user.UserSwitcherRootView
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import com.android.systemui.util.children
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
@@ -56,7 +55,6 @@
     fun bind(
         view: ViewGroup,
         viewModel: UserSwitcherViewModel,
-        lifecycleOwner: LifecycleOwner,
         layoutInflater: LayoutInflater,
         falsingCollector: FalsingCollector,
         onFinish: () -> Unit,
@@ -79,88 +77,92 @@
         addButton.setOnClickListener { viewModel.onOpenMenuButtonClicked() }
         cancelButton.setOnClickListener { viewModel.onCancelButtonClicked() }
 
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
-                    viewModel.isFinishRequested
-                        .filter { it }
-                        .collect {
-                            onFinish()
-                            viewModel.onFinished()
-                        }
+        view.repeatWhenAttached {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        viewModel.isFinishRequested
+                            .filter { it }
+                            .collect {
+                                //finish requested, we want to dismiss popupmenu at the same time
+                                popupMenu?.dismiss()
+                                onFinish()
+                                viewModel.onFinished()
+                            }
+                    }
                 }
             }
-        }
 
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
 
-                launch {
-                    viewModel.isMenuVisible.collect { isVisible ->
-                        if (isVisible && popupMenu?.isShowing != true) {
-                            popupMenu?.dismiss()
-                            // Use post to make sure we show the popup menu *after* the activity is
-                            // ready to show one to avoid a WindowManager$BadTokenException.
-                            view.post {
-                                popupMenu =
-                                    createAndShowPopupMenu(
-                                        context = view.context,
-                                        anchorView = addButton,
-                                        adapter = popupMenuAdapter,
-                                        onDismissed = viewModel::onMenuClosed,
-                                    )
-                            }
-                        } else if (!isVisible && popupMenu?.isShowing == true) {
-                            popupMenu?.dismiss()
-                            popupMenu = null
-                        }
-                    }
-                }
-
-                launch {
-                    viewModel.menu.collect { menuViewModels ->
-                        popupMenuAdapter.setItems(menuViewModels)
-                    }
-                }
-
-                launch {
-                    viewModel.maximumUserColumns.collect { maximumColumns ->
-                        flowWidget.setMaxElementsWrap(maximumColumns)
-                    }
-                }
-
-                launch {
-                    viewModel.users.collect { users ->
-                        val viewPool =
-                            gridContainerView.children
-                                .filter { it.tag == USER_VIEW_TAG }
-                                .toMutableList()
-                        viewPool.forEach {
-                            gridContainerView.removeView(it)
-                            flowWidget.removeView(it)
-                        }
-                        users.forEach { userViewModel ->
-                            val userView =
-                                if (viewPool.isNotEmpty()) {
-                                    viewPool.removeAt(0)
-                                } else {
-                                    val inflatedView =
-                                        layoutInflater.inflate(
-                                            R.layout.user_switcher_fullscreen_item,
-                                            view,
-                                            false,
+                    launch {
+                        viewModel.isMenuVisible.collect { isVisible ->
+                            if (isVisible && popupMenu?.isShowing != true) {
+                                popupMenu?.dismiss()
+                                // Use post to make sure we show the popup menu *after* the activity is
+                                // ready to show one to avoid a WindowManager$BadTokenException.
+                                view.post {
+                                    popupMenu =
+                                        createAndShowPopupMenu(
+                                            context = view.context,
+                                            anchorView = addButton,
+                                            adapter = popupMenuAdapter,
+                                            onDismissed = viewModel::onMenuClosed,
                                         )
-                                    inflatedView.tag = USER_VIEW_TAG
-                                    inflatedView
                                 }
-                            userView.id = View.generateViewId()
-                            gridContainerView.addView(userView)
-                            flowWidget.addView(userView)
-                            UserViewBinder.bind(
-                                view = userView,
-                                viewModel = userViewModel,
-                            )
+                            } else if (!isVisible && popupMenu?.isShowing == true) {
+                                popupMenu?.dismiss()
+                                popupMenu = null
+                            }
+                        }
+                    }
+
+                    launch {
+                        viewModel.menu.collect { menuViewModels ->
+                            popupMenuAdapter.setItems(menuViewModels)
+                        }
+                    }
+
+                    launch {
+                        viewModel.maximumUserColumns.collect { maximumColumns ->
+                            flowWidget.setMaxElementsWrap(maximumColumns)
+                        }
+                    }
+
+                    launch {
+                        viewModel.users.collect { users ->
+                            val viewPool =
+                                gridContainerView.children
+                                    .filter { it.tag == USER_VIEW_TAG }
+                                    .toMutableList()
+                            viewPool.forEach {
+                                gridContainerView.removeView(it)
+                                flowWidget.removeView(it)
+                            }
+                            users.forEach { userViewModel ->
+                                val userView =
+                                    if (viewPool.isNotEmpty()) {
+                                        viewPool.removeAt(0)
+                                    } else {
+                                        val inflatedView =
+                                            layoutInflater.inflate(
+                                                R.layout.user_switcher_fullscreen_item,
+                                                view,
+                                                false,
+                                            )
+                                        inflatedView.tag = USER_VIEW_TAG
+                                        inflatedView
+                                    }
+                                userView.id = View.generateViewId()
+                                gridContainerView.addView(userView)
+                                flowWidget.addView(userView)
+                                UserViewBinder.bind(
+                                    view = userView,
+                                    viewModel = userViewModel,
+                                )
+                            }
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 79721b3..0930cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -26,13 +26,16 @@
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.user.UserSwitchFullscreenDialog
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import dagger.Lazy
 import javax.inject.Inject
 import javax.inject.Provider
@@ -54,6 +57,8 @@
     private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
     private val eventLogger: Lazy<UiEventLogger>,
     private val activityStarter: Lazy<ActivityStarter>,
+    private val falsingCollector: Lazy<FalsingCollector>,
+    private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>,
 ) : CoreStartable {
 
     private var currentDialog: Dialog? = null
@@ -124,6 +129,15 @@
                                     INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
                                 ),
                             )
+                        is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog ->
+                            Pair(
+                                UserSwitchFullscreenDialog(
+                                    context = context.get(),
+                                    falsingCollector = falsingCollector.get(),
+                                    userSwitcherViewModel = userSwitcherViewModel.get(),
+                                ),
+                                null, /* dialogCuj */
+                            )
                     }
                 currentDialog = dialog
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 3300e8e..78edad7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -55,5 +55,5 @@
         interactor.selectedUser.mapLatest { userModel -> userModel.image }
 
     /** Action to execute on click. Should launch the user switcher */
-    val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+    val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(it) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 37115ad..afd72e7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -17,12 +17,10 @@
 
 package com.android.systemui.user.ui.viewmodel
 
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -36,12 +34,13 @@
 import kotlinx.coroutines.flow.map
 
 /** Models UI state for the user switcher feature. */
+@SysUISingleton
 class UserSwitcherViewModel
-private constructor(
+@Inject
+constructor(
     private val userInteractor: UserInteractor,
     private val guestUserInteractor: GuestUserInteractor,
-    private val powerInteractor: PowerInteractor,
-) : ViewModel() {
+) {
 
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
@@ -112,34 +111,15 @@
         }
     }
 
-    private fun createFinishRequestedFlow(): Flow<Boolean> {
-        var mostRecentSelectedUserId: Int? = null
-        var mostRecentIsInteractive: Boolean? = null
-
-        return combine(
-            // When the user is switched, we should finish.
-            userInteractor.selectedUser
-                .map { it.id }
-                .map {
-                    val selectedUserChanged =
-                        mostRecentSelectedUserId != null && mostRecentSelectedUserId != it
-                    mostRecentSelectedUserId = it
-                    selectedUserChanged
-                },
-            // When the screen turns off, we should finish.
-            powerInteractor.isInteractive.map {
-                val screenTurnedOff = mostRecentIsInteractive == true && !it
-                mostRecentIsInteractive = it
-                screenTurnedOff
-            },
+    private fun createFinishRequestedFlow(): Flow<Boolean> =
+        combine(
             // When the cancel button is clicked, we should finish.
             hasCancelButtonBeenClicked,
             // If an executed action told us to finish, we should finish,
             isFinishRequiredDueToExecutedAction,
-        ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
-            selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
+        ) { cancelButtonClicked, executedActionFinish ->
+            cancelButtonClicked || executedActionFinish
         }
-    }
 
     private fun toViewModel(
         model: UserModel,
@@ -210,22 +190,4 @@
             { userInteractor.selectUser(model.id) }
         }
     }
-
-    class Factory
-    @Inject
-    constructor(
-        private val userInteractor: UserInteractor,
-        private val guestUserInteractor: GuestUserInteractor,
-        private val powerInteractor: PowerInteractor,
-    ) : ViewModelProvider.Factory {
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            @Suppress("UNCHECKED_CAST")
-            return UserSwitcherViewModel(
-                userInteractor = userInteractor,
-                guestUserInteractor = guestUserInteractor,
-                powerInteractor = powerInteractor,
-            )
-                as T
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 13b3b1a..082c8cc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -18,10 +18,8 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.View
 import android.view.inputmethod.InputMethodManager
 import android.widget.EditText
-import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -32,7 +30,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
@@ -40,7 +37,6 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -80,9 +76,7 @@
     Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
         .thenReturn(passwordEntry)
     `when`(keyguardPasswordView.resources).thenReturn(context.resources)
-    `when`(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
-        .thenReturn(mock(ImageView::class.java))
-     keyguardPasswordViewController =
+    keyguardPasswordViewController =
         KeyguardPasswordViewController(
             keyguardPasswordView,
             keyguardUpdateMonitor,
@@ -119,18 +113,6 @@
   }
 
   @Test
-  fun onApplyWindowInsetsListener_onApplyWindowInsets() {
-      `when`(keyguardViewController.isBouncerShowing).thenReturn(false)
-      val argumentCaptor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
-
-      keyguardPasswordViewController.onViewAttached()
-      verify(keyguardPasswordView).setOnApplyWindowInsetsListener(argumentCaptor.capture())
-      argumentCaptor.value.onApplyWindowInsets(keyguardPasswordView, null)
-
-      verify(keyguardPasswordView).hideKeyboard()
-  }
-
-  @Test
   fun testHideKeyboardWhenOnPause() {
     keyguardPasswordViewController.onPause()
     keyguardPasswordView.post {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4ed4e20..08813a7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -84,9 +84,11 @@
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
@@ -194,8 +196,6 @@
     @Mock
     private FaceManager mFaceManager;
     @Mock
-    private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
-    @Mock
     private BiometricManager mBiometricManager;
     @Mock
     private PackageManager mPackageManager;
@@ -254,6 +254,7 @@
     @Mock
     private Uri mURI;
 
+    private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private final int mCurrentUserId = 100;
     private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -274,21 +275,22 @@
     private StatusBarStateController.StateListener mStatusBarStateListener;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+    private IFingerprintAuthenticatorsRegisteredCallback
+            mFingerprintAuthenticatorsRegisteredCallback;
+    private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
     private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
 
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
+
+        mFaceSensorProperties =
+                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false));
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
         when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
         when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
         when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
 
-        // IBiometricsFace@1.0 does not support detection, only authentication.
-        when(mFaceSensorProperties.isEmpty()).thenReturn(false);
-        when(mFaceSensorProperties.get(anyInt())).thenReturn(
-                createFaceSensorProperties(/* supportsFaceDetection = */ false));
-
         mFingerprintSensorProperties = List.of(
                 new FingerprintSensorPropertiesInternal(1 /* sensorId */,
                         FingerprintSensorProperties.STRENGTH_STRONG,
@@ -345,6 +347,20 @@
 
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
+        ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
+                ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
+        mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
+        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+
+        ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
+                ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+                fingerprintCaptor.capture());
+        mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
+        mFingerprintAuthenticatorsRegisteredCallback
+                .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+
         verify(mBiometricManager)
                 .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
         mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
@@ -651,7 +667,7 @@
     }
 
     @Test
-    public void whenDetectFace_biometricDetectCallback() {
+    public void whenDetectFace_biometricDetectCallback() throws RemoteException {
         ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor =
                 ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class);
 
@@ -801,7 +817,8 @@
     }
 
     @Test
-    public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
+    public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning()
+            throws RemoteException {
         // GIVEN bypass is enabled, face detection is supported
         lockscreenBypassIsAllowed();
         supportsFaceDetection();
@@ -825,7 +842,7 @@
     }
 
     @Test
-    public void faceDetect_whenStrongAuthRequiredAndBypass() {
+    public void faceDetect_whenStrongAuthRequiredAndBypass() throws RemoteException {
         givenDetectFace();
 
         // FACE detect is triggered, not authenticate
@@ -2614,6 +2631,37 @@
         assertThat(captor.getValue().getWakeReason())
                 .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
     }
+    @Test
+    public void testFingerprintSensorProperties() throws RemoteException {
+        mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
+                new ArrayList<>());
+
+        assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
+                KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+
+        mFingerprintAuthenticatorsRegisteredCallback
+                .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+
+        verifyFingerprintAuthenticateCall();
+        assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
+                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+    }
+    @Test
+    public void testFaceSensorProperties() throws RemoteException {
+        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
+
+        assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+                KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+
+        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+        biometricsEnabledForCurrentUser();
+
+        verifyFaceAuthenticateNeverCalled();
+        verifyFaceDetectNeverCalled();
+        assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+                KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+    }
+
 
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
@@ -2656,10 +2704,10 @@
                 .thenReturn(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
     }
 
-    private void supportsFaceDetection() {
-        when(mFaceSensorProperties.get(anyInt()))
-                .thenReturn(createFaceSensorProperties(
-                        /* supportsFaceDetection = */ true));
+    private void supportsFaceDetection() throws RemoteException {
+        mFaceSensorProperties =
+                List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true));
+        mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
     }
 
     private void lockscreenBypassIsAllowed() {
@@ -2868,7 +2916,7 @@
         mTestableLooper.processAllMessages();
     }
 
-    private void givenDetectFace() {
+    private void givenDetectFace() throws RemoteException {
         // GIVEN bypass is enabled, face detection is supported and strong auth is required
         lockscreenBypassIsAllowed();
         supportsFaceDetection();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index 1ce2572..39ea46a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -126,7 +126,7 @@
         }
 
         fun assertLastProgress(progress: Float) {
-            assertThat(progressHistory.last()).isWithin(1.0E-4F).of(progress)
+            waitForCondition { progress == progressHistory.last() }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8d74c82..adba538 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,7 +19,6 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
-import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
@@ -49,7 +48,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.source.UserRecord
@@ -58,11 +56,9 @@
 import com.android.systemui.user.shared.model.UserModel
 import com.android.systemui.user.utils.MultiUserActionsEvent
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertNotNull
@@ -842,7 +838,7 @@
     fun `show user switcher - full screen disabled - shows dialog switcher`() =
         testScope.runTest {
             val expandable = mock<Expandable>()
-            underTest.showUserSwitcher(context, expandable)
+            underTest.showUserSwitcher(expandable)
 
             val dialogRequest = collectLastValue(underTest.dialogShowRequests)
 
@@ -855,30 +851,22 @@
         }
 
     @Test
-    fun `show user switcher - full screen enabled - launches activity`() {
-        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+    fun `show user switcher - full screen enabled - launches full screen dialog`() =
+        testScope.runTest {
+            featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
-        val expandable = mock<Expandable>()
-        underTest.showUserSwitcher(context, expandable)
+            val expandable = mock<Expandable>()
+            underTest.showUserSwitcher(expandable)
 
-        // Dialog is shown.
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(activityStarter)
-            .startActivity(
-                intentCaptor.capture(),
-                /* dismissShade= */ eq(true),
-                /* ActivityLaunchAnimator.Controller= */ nullable(),
-                /* showOverLockscreenWhenLocked= */ eq(true),
-                eq(UserHandle.SYSTEM),
-            )
-        assertThat(intentCaptor.value.component)
-            .isEqualTo(
-                ComponentName(
-                    context,
-                    UserSwitcherActivity::class.java,
-                )
-            )
-    }
+            val dialogRequest = collectLastValue(underTest.dialogShowRequests)
+
+            // Dialog is shown.
+            assertThat(dialogRequest())
+                    .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+
+            underTest.onDialogShown()
+            assertThat(dialogRequest()).isNull()
+        }
 
     @Test
     fun `users - secondary user - managed profile is not included`() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 7780a43..a342dad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,8 +34,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -88,7 +86,6 @@
 
     private lateinit var userRepository: FakeUserRepository
     private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var powerRepository: FakePowerRepository
 
     private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
@@ -116,7 +113,6 @@
         }
 
         keyguardRepository = FakeKeyguardRepository()
-        powerRepository = FakePowerRepository()
         val refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
@@ -145,7 +141,7 @@
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
         underTest =
-            UserSwitcherViewModel.Factory(
+            UserSwitcherViewModel(
                     userInteractor =
                         UserInteractor(
                             applicationContext = context,
@@ -174,13 +170,8 @@
                             guestUserInteractor = guestUserInteractor,
                             uiEventLogger = uiEventLogger,
                         ),
-                    powerInteractor =
-                        PowerInteractor(
-                            repository = powerRepository,
-                        ),
                     guestUserInteractor = guestUserInteractor,
                 )
-                .create(UserSwitcherViewModel::class.java)
     }
 
     @Test
@@ -327,46 +318,12 @@
         }
 
     @Test
-    fun `isFinishRequested - finishes when user is switched`() =
-        testScope.runTest {
-            val userInfos = setUsers(count = 2)
-            val isFinishRequested = mutableListOf<Boolean>()
-            val job =
-                launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
-            assertThat(isFinishRequested.last()).isFalse()
-
-            userRepository.setSelectedUserInfo(userInfos[1])
-
-            assertThat(isFinishRequested.last()).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
-    fun `isFinishRequested - finishes when the screen turns off`() =
-        testScope.runTest {
-            setUsers(count = 2)
-            powerRepository.setInteractive(true)
-            val isFinishRequested = mutableListOf<Boolean>()
-            val job =
-                launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
-            assertThat(isFinishRequested.last()).isFalse()
-
-            powerRepository.setInteractive(false)
-
-            assertThat(isFinishRequested.last()).isTrue()
-
-            job.cancel()
-        }
-
-    @Test
     fun `isFinishRequested - finishes when cancel button is clicked`() =
         testScope.runTest {
             setUsers(count = 2)
-            powerRepository.setInteractive(true)
             val isFinishRequested = mutableListOf<Boolean>()
             val job =
-                launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+                    launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
             assertThat(isFinishRequested.last()).isFalse()
 
             underTest.onCancelButtonClicked()
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a60f06a..51d349f 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5086,7 +5086,6 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            mAuthenticator = null;
             IAccountManagerResponse response = getResponseAndClose();
             if (response != null) {
                 try {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6bd3c79..e6ef3b4 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -999,23 +999,50 @@
 
     private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
             @NonNull BroadcastRecord oldRecord) {
-        final String newMatchingKey = getDeliveryGroupMatchingKey(newRecord);
-        final String oldMatchingKey = getDeliveryGroupMatchingKey(oldRecord);
         final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
         // If neither delivery group key nor matching filter is specified, then use
         // Intent.filterEquals() to identify the delivery group.
-        if (newMatchingKey == null && oldMatchingKey == null && newMatchingFilter == null) {
+        if (isMatchingKeyNull(newRecord) && isMatchingKeyNull(oldRecord)
+                && newMatchingFilter == null) {
             return newRecord.intent.filterEquals(oldRecord.intent);
         }
         if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
             return false;
         }
-        return Objects.equals(newMatchingKey, oldMatchingKey);
+        return areMatchingKeysEqual(newRecord, oldRecord);
+    }
+
+    private static boolean isMatchingKeyNull(@NonNull BroadcastRecord record) {
+        final String namespace = getDeliveryGroupMatchingNamespaceFragment(record);
+        final String key = getDeliveryGroupMatchingKeyFragment(record);
+        // If either namespace or key part is null, then treat the entire matching key as null.
+        return namespace == null || key == null;
+    }
+
+    private static boolean areMatchingKeysEqual(@NonNull BroadcastRecord newRecord,
+            @NonNull BroadcastRecord oldRecord) {
+        final String newNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(newRecord);
+        final String oldNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(oldRecord);
+        if (!Objects.equals(newNamespaceFragment, oldNamespaceFragment)) {
+            return false;
+        }
+
+        final String newKeyFragment = getDeliveryGroupMatchingKeyFragment(newRecord);
+        final String oldKeyFragment = getDeliveryGroupMatchingKeyFragment(oldRecord);
+        return Objects.equals(newKeyFragment, oldKeyFragment);
     }
 
     @Nullable
-    private static String getDeliveryGroupMatchingKey(@NonNull BroadcastRecord record) {
-        return record.options == null ? null : record.options.getDeliveryGroupMatchingKey();
+    private static String getDeliveryGroupMatchingNamespaceFragment(
+            @NonNull BroadcastRecord record) {
+        return record.options == null
+                ? null : record.options.getDeliveryGroupMatchingNamespaceFragment();
+    }
+
+    @Nullable
+    private static String getDeliveryGroupMatchingKeyFragment(@NonNull BroadcastRecord record) {
+        return record.options == null
+                ? null : record.options.getDeliveryGroupMatchingKeyFragment();
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b32e8f0..ee2e458 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3259,6 +3259,12 @@
                 Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
                         + " interceptKeyBeforeQueueing");
                 return key_consumed;
+            case KeyEvent.KEYCODE_MACRO_1:
+            case KeyEvent.KEYCODE_MACRO_2:
+            case KeyEvent.KEYCODE_MACRO_3:
+            case KeyEvent.KEYCODE_MACRO_4:
+                Slog.wtf(TAG, "KEYCODE_MACRO_x should be handled in interceptKeyBeforeQueueing");
+                return key_consumed;
         }
 
         if (isValidGlobalKey(keyCode)
@@ -4396,6 +4402,13 @@
                 result &= ~ACTION_PASS_TO_USER;
                 break;
             }
+            case KeyEvent.KEYCODE_MACRO_1:
+            case KeyEvent.KEYCODE_MACRO_2:
+            case KeyEvent.KEYCODE_MACRO_3:
+            case KeyEvent.KEYCODE_MACRO_4:
+                // TODO(b/266098478): Add logic to handle KEYCODE_MACROx feature
+                result &= ~ACTION_PASS_TO_USER;
+                break;
         }
 
         if (useHapticFeedback) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9069ac5..2ea59b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -173,6 +173,7 @@
 import static com.android.server.wm.ActivityRecordProto.NAME;
 import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS;
 import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS;
+import static com.android.server.wm.ActivityRecordProto.OVERRIDE_ORIENTATION;
 import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED;
 import static com.android.server.wm.ActivityRecordProto.PROC_ID;
 import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
@@ -10212,6 +10213,7 @@
         proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds());
         proto.write(ENABLE_RECENTS_SCREENSHOT, mEnableRecentsScreenshot);
         proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
+        proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
     }
 
     @Override
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 38dadc6..6bfcd39 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -29,10 +29,6 @@
 import android.service.credentials.CallingAppInfo;
 import android.util.Log;
 
-import com.android.server.credentials.metrics.ApiName;
-import com.android.server.credentials.metrics.ApiStatus;
-import com.android.server.credentials.metrics.ProviderStatusForMetrics;
-
 import java.util.ArrayList;
 
 /**
@@ -40,7 +36,7 @@
  * responses from providers, and updates the provider(S) state.
  */
 public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
-        IClearCredentialStateCallback>
+        IClearCredentialStateCallback, Void>
         implements ProviderSession.ProviderInternalCallback<Void> {
     private static final String TAG = "GetRequestSession";
 
@@ -50,7 +46,6 @@
             long startedTimestamp) {
         super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED,
                 callingAppInfo, cancellationSignal, startedTimestamp);
-        setupInitialPhaseMetric(ApiName.CLEAR_CREDENTIAL.getMetricCode(), MetricUtilities.ZERO);
     }
 
     /**
@@ -92,8 +87,10 @@
     public void onFinalResponseReceived(
             ComponentName componentName,
             Void response) {
-        setChosenMetric(componentName);
-        respondToClientWithResponseAndFinish();
+        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
+                mProviders.get(componentName.flattenToString()).mProviderSessionMetric
+                        .getCandidatePhasePerProviderMetric());
+        respondToClientWithResponseAndFinish(null);
     }
 
     protected void onProviderResponseComplete(ComponentName componentName) {
@@ -114,63 +111,28 @@
     }
 
     @Override
+    protected void invokeClientCallbackSuccess(Void response) throws RemoteException {
+        mClientCallback.onSuccess();
+    }
+
+    @Override
+    protected void invokeClientCallbackError(String errorType, String errorMsg)
+            throws RemoteException {
+        mClientCallback.onError(errorType, errorMsg);
+    }
+
+    @Override
     public void onFinalErrorReceived(ComponentName componentName, String errorType,
             String message) {
         //Not applicable for clearCredential as response is not picked by the user
     }
 
-    private void respondToClientWithResponseAndFinish() {
-        Log.i(TAG, "respondToClientWithResponseAndFinish");
-        collectFinalPhaseMetricStatus(false, ProviderStatusForMetrics.FINAL_SUCCESS);
-        if (isSessionCancelled()) {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-        try {
-            mClientCallback.onSuccess();
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.SUCCESS.getMetricCode());
-        } catch (RemoteException e) {
-            collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-            Log.i(TAG, "Issue while propagating the response to the client");
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
-        }
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
-        Log.i(TAG, "respondToClientWithErrorAndFinish");
-        collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-        if (isSessionCancelled()) {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-        try {
-            mClientCallback.onError(errorType, errorMsg);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-        logApiCall(mChosenProviderFinalPhaseMetric,
-                mCandidateBrowsingPhaseMetric,
-                /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
-        finishSession(/*propagateCancellation=*/false);
-    }
-
     private void processResponses() {
         for (ProviderSession session : mProviders.values()) {
             if (session.isProviderResponseSet()) {
                 // If even one provider responded successfully, send back the response
                 // TODO: Aggregate other exceptions
-                respondToClientWithResponseAndFinish();
+                respondToClientWithResponseAndFinish(null);
                 return;
             }
         }
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 687c861..dfd8cfa 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -35,8 +35,6 @@
 import android.service.credentials.PermissionUtils;
 import android.util.Log;
 
-import com.android.server.credentials.metrics.ApiName;
-import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
@@ -47,7 +45,7 @@
  * provider(s) state maintained in {@link ProviderCreateSession}.
  */
 public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
-        ICreateCredentialCallback>
+        ICreateCredentialCallback, CreateCredentialResponse>
         implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
     private static final String TAG = "CreateRequestSession";
 
@@ -59,7 +57,6 @@
             long startedTimestamp) {
         super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE,
                 callingAppInfo, cancellationSignal, startedTimestamp);
-        setupInitialPhaseMetric(ApiName.CREATE_CREDENTIAL.getMetricCode(), MetricUtilities.UNIT);
     }
 
     /**
@@ -85,7 +82,7 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
-        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+        mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
         try {
             mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newCreateRequestInfo(
@@ -95,7 +92,7 @@
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
                     providerDataList));
         } catch (RemoteException e) {
-            mChosenProviderFinalPhaseMetric.setUiReturned(false);
+            mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
             respondToClientWithErrorAndFinish(
                     CreateCredentialException.TYPE_UNKNOWN,
                     "Unable to invoke selector");
@@ -103,18 +100,31 @@
     }
 
     @Override
+    protected void invokeClientCallbackSuccess(CreateCredentialResponse response)
+            throws RemoteException {
+        mClientCallback.onResponse(response);
+    }
+
+    @Override
+    protected void invokeClientCallbackError(String errorType, String errorMsg)
+            throws RemoteException {
+        mClientCallback.onError(errorType, errorMsg);
+    }
+
+    @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable CreateCredentialResponse response) {
-        mChosenProviderFinalPhaseMetric.setUiReturned(true);
-        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
-        setChosenMetric(componentName);
+        mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
+        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
+                componentName.flattenToString()).mProviderSessionMetric
+                .getCandidatePhasePerProviderMetric());
         if (response != null) {
-            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+            mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+            mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
                     "Invalid response");
@@ -144,75 +154,6 @@
                 "No create options available.");
     }
 
-    private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
-        Log.i(TAG, "respondToClientWithResponseAndFinish");
-        // TODO(b/271135048) - Improve Metrics super/sub class setup and emit.
-        collectFinalPhaseMetricStatus(false, ProviderStatusForMetrics.FINAL_SUCCESS);
-        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
-            return;
-        }
-        if (isSessionCancelled()) {
-            // TODO(b/271135048) - Migrate to superclass utilities (post beta1 cleanup) - applies
-            // for all
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-        try {
-            mClientCallback.onResponse(response);
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.SUCCESS.getMetricCode());
-        } catch (RemoteException e) {
-            collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-            Log.i(TAG, "Issue while responding to client: " + e.getMessage());
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
-        }
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
-        Log.i(TAG, "respondToClientWithErrorAndFinish");
-        collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
-            return;
-        }
-        if (isSessionCancelled()) {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-        try {
-            mClientCallback.onError(errorType, errorMsg);
-        } catch (RemoteException e) {
-            Log.i(TAG, "Issue while responding to client: " + e.getMessage());
-        }
-        logFailureOrUserCancel(errorType);
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void logFailureOrUserCancel(String errorType) {
-        collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-        if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
-            mChosenProviderFinalPhaseMetric.setHasException(false);
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.USER_CANCELED.getMetricCode());
-        } else {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
-        }
-    }
-
     @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 06da76e5..e65b9f3 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -707,9 +707,10 @@
 
         private void finalizeAndEmitInitialPhaseMetric(RequestSession session) {
             try {
-                var initMetric = session.mInitialPhaseMetric;
+                var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
                 initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
-                MetricUtilities.logApiCalled(initMetric, ++session.mSequenceCounter);
+                MetricUtilities.logApiCalledInitialPhase(initMetric,
+                        session.mRequestSessionMetric.returnIncrementSequence());
             } catch (Exception e) {
                 Log.w(TAG, "Unexpected error during metric logging: " + e);
             }
@@ -788,7 +789,7 @@
                     if (serviceComponentName.equals(componentName)) {
                         if (!s.getServicePackageName().equals(callingPackage)) {
                             // The component name and the package name do not match.
-                            MetricUtilities.logApiCalled(
+                            MetricUtilities.logApiCalledSimpleV1(
                                     ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
                                     ApiStatus.FAILURE, callingUid);
                             Log.w(
@@ -797,7 +798,8 @@
                                             + " match package name.");
                             return false;
                         }
-                        MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
+                        MetricUtilities.logApiCalledSimpleV1(
+                                ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
                                 ApiStatus.SUCCESS, callingUid);
                         // TODO(b/271135048) - Update asap to use the new logging types
                         return true;
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8082cdb..93f543e 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -27,14 +27,11 @@
 import android.credentials.IGetCredentialCallback;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.RequestInfo;
-import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.util.Log;
 
-import com.android.server.credentials.metrics.ApiName;
-import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
@@ -45,7 +42,7 @@
  * responses from providers, and the UX app, and updates the provider(S) state.
  */
 public class GetRequestSession extends RequestSession<GetCredentialRequest,
-        IGetCredentialCallback>
+        IGetCredentialCallback, GetCredentialResponse>
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetRequestSession";
     public GetRequestSession(Context context, int userId, int callingUid,
@@ -57,7 +54,7 @@
         int numTypes = (request.getCredentialOptions().stream()
                 .map(CredentialOption::getType).collect(
                 Collectors.toSet())).size(); // Dedupe type strings
-        setupInitialPhaseMetric(ApiName.GET_CREDENTIAL.getMetricCode(), numTypes);
+        mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes);
     }
 
     /**
@@ -83,112 +80,58 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
-        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+        mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
         try {
-            Binder.withCleanCallingIdentity(() ->
-                    mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+            mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                             mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
-                    providerDataList)));
-        } catch (RuntimeException e) {
-            mChosenProviderFinalPhaseMetric.setUiReturned(false);
+                    providerDataList));
+        } catch (RemoteException e) {
+            mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
             respondToClientWithErrorAndFinish(
                     GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
         }
     }
 
     @Override
+    protected void invokeClientCallbackSuccess(GetCredentialResponse response)
+            throws RemoteException {
+        mClientCallback.onResponse(response);
+    }
+
+    @Override
+    protected void invokeClientCallbackError(String errorType, String errorMsg)
+            throws RemoteException {
+        mClientCallback.onError(errorType, errorMsg);
+    }
+
+    @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable GetCredentialResponse response) {
-        mChosenProviderFinalPhaseMetric.setUiReturned(true);
-        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
-        setChosenMetric(componentName);
+        mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
+        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
+                mProviders.get(componentName.flattenToString())
+                        .mProviderSessionMetric.getCandidatePhasePerProviderMetric());
         if (response != null) {
-            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+            mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+            mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
                     "Invalid response from provider");
         }
     }
 
-    //TODO: Try moving the three error & response methods below to RequestSession to be shared
-    // between get & create.
+    //TODO(b/274954697): Further shorten the three below to completely migrate to superclass
     @Override
     public void onFinalErrorReceived(ComponentName componentName, String errorType,
             String message) {
         respondToClientWithErrorAndFinish(errorType, message);
     }
 
-    private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
-        collectFinalPhaseMetricStatus(false, ProviderStatusForMetrics.FINAL_SUCCESS);
-        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
-            return;
-        }
-        if (isSessionCancelled()) {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-        try {
-            mClientCallback.onResponse(response);
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.SUCCESS.getMetricCode());
-        } catch (RemoteException e) {
-            collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-            Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
-        }
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
-        collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
-            return;
-        }
-        if (isSessionCancelled()) {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.CLIENT_CANCELED.getMetricCode());
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-
-        try {
-            mClientCallback.onError(errorType, errorMsg);
-        } catch (RemoteException e) {
-            Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
-        }
-        logFailureOrUserCancel(errorType);
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void logFailureOrUserCancel(String errorType) {
-        collectFinalPhaseMetricStatus(true, ProviderStatusForMetrics.FINAL_FAILURE);
-        if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
-            mChosenProviderFinalPhaseMetric.setHasException(false);
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.USER_CANCELED.getMetricCode());
-        } else {
-            logApiCall(mChosenProviderFinalPhaseMetric,
-                    mCandidateBrowsingPhaseMetric,
-                    /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
-        }
-    }
-
     @Override
     public void onUiCancellation(boolean isUserCancellation) {
         if (isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 65fb368..c48654a 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -37,8 +37,10 @@
  * from {@link com.android.internal.util.FrameworkStatsLog}.
  */
 public class MetricUtilities {
+    private static final boolean LOG_FLAG = true;
 
     private static final String TAG = "MetricUtilities";
+    public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED";
 
     public static final int DEFAULT_INT_32 = -1;
     public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
@@ -90,10 +92,13 @@
      * @param apiStatus            the final status of this particular api call
      * @param emitSequenceId       an emitted sequence id for the current session
      */
-    protected static void logApiCalled(ChosenProviderFinalPhaseMetric finalPhaseMetric,
+    public static void logApiCalledFinalPhase(ChosenProviderFinalPhaseMetric finalPhaseMetric,
             List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus,
             int emitSequenceId) {
         try {
+            if (!LOG_FLAG) {
+                return;
+            }
             int browsedSize = browsingPhaseMetrics.size();
             int[] browsedClickedEntries = new int[browsedSize];
             int[] browsedProviderUid = new int[browsedSize];
@@ -151,9 +156,12 @@
      * @param providers      a map with known providers and their held metric objects
      * @param emitSequenceId an emitted sequence id for the current session
      */
-    protected static void logApiCalled(Map<String, ProviderSession> providers,
+    public static void logApiCalledCandidatePhase(Map<String, ProviderSession> providers,
             int emitSequenceId) {
         try {
+            if (!LOG_FLAG) {
+                return;
+            }
             var providerSessions = providers.values();
             int providerSize = providerSessions.size();
             int sessionId = -1;
@@ -171,7 +179,8 @@
             int[] candidateRemoteEntryCountList = new int[providerSize];
             int index = 0;
             for (var session : providerSessions) {
-                CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
+                CandidatePhaseMetric metric = session.mProviderSessionMetric
+                        .getCandidatePhasePerProviderMetric();
                 if (sessionId == -1) {
                     sessionId = metric.getSessionId();
                 }
@@ -225,14 +234,18 @@
      * contain default values for all other optional parameters.
      *
      * TODO(b/271135048) - given space requirements, this may be a good candidate for another atom
+     * TODO immediately remove and carry over TODO to new log for this setup
      *
      * @param apiName    the api name to log
      * @param apiStatus  the status to log
      * @param callingUid the calling uid
      */
-    protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
+    public static void logApiCalledSimpleV1(ApiName apiName, ApiStatus apiStatus,
             int callingUid) {
         try {
+            if (!LOG_FLAG) {
+                return;
+            }
             FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
                     /* api_name */apiName.getMetricCode(),
                     /* caller_uid */ callingUid,
@@ -258,8 +271,12 @@
      * @param initialPhaseMetric contains all the data for this emit
      * @param sequenceNum        the sequence number for this api call session emit
      */
-    protected static void logApiCalled(InitialPhaseMetric initialPhaseMetric, int sequenceNum) {
+    public static void logApiCalledInitialPhase(InitialPhaseMetric initialPhaseMetric,
+            int sequenceNum) {
         try {
+            if (!LOG_FLAG) {
+                return;
+            }
             FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE,
                     /* api_name */ initialPhaseMetric.getApiName(),
                     /* caller_uid */ initialPhaseMetric.getCallerUid(),
@@ -275,5 +292,4 @@
             Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
     }
-
 }
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index f48fc2c..5c93f6b 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -38,7 +38,6 @@
 import android.service.credentials.PermissionUtils;
 import android.util.Log;
 
-import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
@@ -50,7 +49,7 @@
  * responses from providers, and the UX app, and updates the provider(S) state.
  */
 public class PrepareGetRequestSession extends RequestSession<GetCredentialRequest,
-        IGetCredentialCallback>
+        IGetCredentialCallback, GetCredentialResponse>
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetRequestSession";
 
@@ -67,7 +66,7 @@
         int numTypes = (request.getCredentialOptions().stream()
                 .map(CredentialOption::getType).collect(
                         Collectors.toSet())).size(); // Dedupe type strings
-        setupInitialPhaseMetric(ApiName.GET_CREDENTIAL.getMetricCode(), numTypes);
+        mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes);
         mPrepareGetCredentialCallback = prepareGetCredentialCallback;
     }
 
@@ -95,32 +94,45 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
-        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+        mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
         try {
             mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                             mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
-            mChosenProviderFinalPhaseMetric.setUiReturned(false);
+            mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
             respondToClientWithErrorAndFinish(
                     GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
         }
     }
 
     @Override
+    protected void invokeClientCallbackSuccess(GetCredentialResponse response)
+            throws RemoteException {
+        mClientCallback.onResponse(response);
+    }
+
+    @Override
+    protected void invokeClientCallbackError(String errorType, String errorMsg)
+            throws RemoteException {
+        mClientCallback.onError(errorType, errorMsg);
+    }
+
+    @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable GetCredentialResponse response) {
-        mChosenProviderFinalPhaseMetric.setUiReturned(true);
-        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
-        setChosenMetric(componentName);
+        mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
+        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
+                componentName.flattenToString()).mProviderSessionMetric
+                .getCandidatePhasePerProviderMetric());
         if (response != null) {
-            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+            mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+            mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
                     "Invalid response from provider");
@@ -135,66 +147,6 @@
         respondToClientWithErrorAndFinish(errorType, message);
     }
 
-    private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
-        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
-            return;
-        }
-        if (isSessionCancelled()) {
-//            TODO: properly log the new api
-//            logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-//                    ApiStatus.CLIENT_CANCELED);
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-        try {
-            mClientCallback.onResponse(response);
-//            TODO: properly log the new api
-//            logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-//                    ApiStatus.SUCCESS);
-        } catch (RemoteException e) {
-            Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
-//            TODO: properly log the new api
-//            logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-//                    ApiStatus.FAILURE);
-        }
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
-        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
-            Log.i(TAG, "Request has already been completed. This is strange.");
-            return;
-        }
-        if (isSessionCancelled()) {
-//            TODO: properly log the new api
-//            logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
-//                    ApiStatus.CLIENT_CANCELED);
-            finishSession(/*propagateCancellation=*/true);
-            return;
-        }
-
-        try {
-            mClientCallback.onError(errorType, errorMsg);
-        } catch (RemoteException e) {
-            Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
-        }
-        logFailureOrUserCancel(errorType);
-        finishSession(/*propagateCancellation=*/false);
-    }
-
-    private void logFailureOrUserCancel(String errorType) {
-        if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
-//            TODO: properly log the new api
-//            logApiCall(ApiName.GET_CREDENTIAL,
-//                    /* apiStatus */ ApiStatus.USER_CANCELED);
-        } else {
-//            TODO: properly log the new api
-//            logApiCall(ApiName.GET_CREDENTIAL,
-//                    /* apiStatus */ ApiStatus.FAILURE);
-        }
-    }
-
     @Override
     public void onUiCancellation(boolean isUserCancellation) {
         if (isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 69a642d..2e7aaa0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -91,7 +91,7 @@
         if (exception instanceof ClearCredentialStateException) {
             mProviderException = (ClearCredentialStateException) exception;
         }
-        captureCandidateFailureInMetrics();
+        mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 2ba9c22..e05eb60 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -40,8 +40,6 @@
 import android.util.Pair;
 import android.util.Slog;
 
-import com.android.server.credentials.metrics.EntryEnum;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -158,7 +156,7 @@
             // Store query phase exception for aggregation with final response
             mProviderException = (CreateCredentialException) exception;
         }
-        captureCandidateFailureInMetrics();
+        mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -179,37 +177,14 @@
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
-            gatherCandidateEntryMetrics(response);
+            mProviderSessionMetric.collectCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
         } else {
-            gatherCandidateEntryMetrics(response);
+            mProviderSessionMetric.collectCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
         }
     }
 
-    private void gatherCandidateEntryMetrics(BeginCreateCredentialResponse response) {
-        try {
-            var createEntries = response.getCreateEntries();
-            int numRemoteEntry = MetricUtilities.ZERO;
-            if (response.getRemoteCreateEntry() != null) {
-                numRemoteEntry = MetricUtilities.UNIT;
-                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
-            }
-            int numCreateEntries =
-                    createEntries == null ? MetricUtilities.ZERO : createEntries.size();
-            if (numCreateEntries > MetricUtilities.ZERO) {
-                createEntries.forEach(c ->
-                        mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
-            }
-            mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries + numRemoteEntry);
-            mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
-            mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCreateEntries);
-            mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(MetricUtilities.UNIT);
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
-        }
-    }
-
     @Override
     @Nullable
     protected CreateCredentialProviderData prepareUiData()
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 7d3c86b..b5f9e53 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -43,8 +43,6 @@
 import android.util.Pair;
 import android.util.Slog;
 
-import com.android.server.credentials.metrics.EntryEnum;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -52,7 +50,6 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Central provider session that listens for provider callbacks, and maintains provider state.
@@ -256,7 +253,7 @@
         if (exception instanceof GetCredentialException) {
             mProviderException = (GetCredentialException) exception;
         }
-        captureCandidateFailureInMetrics();
+        mProviderSessionMetric.collectCandidateExceptionStatus(/*hasException=*/true);
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
@@ -502,44 +499,14 @@
         addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
         // Log the data.
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
+            mProviderSessionMetric.collectCandidateEntryMetrics(response);
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
             return;
         }
-        gatherCandidateEntryMetrics(response);
+        mProviderSessionMetric.collectCandidateEntryMetrics(response);
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
     }
 
-    private void gatherCandidateEntryMetrics(BeginGetCredentialResponse response) {
-        try {
-            int numCredEntries = response.getCredentialEntries().size();
-            int numActionEntries = response.getActions().size();
-            int numAuthEntries = response.getAuthenticationActions().size();
-            int numRemoteEntry = MetricUtilities.ZERO;
-            if (response.getRemoteCredentialEntry() != null) {
-                numRemoteEntry = MetricUtilities.UNIT;
-                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
-            }
-            response.getCredentialEntries().forEach(c ->
-                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
-            response.getActions().forEach(c ->
-                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
-            response.getAuthenticationActions().forEach(c ->
-                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
-            mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
-                    + numActionEntries + numRemoteEntry);
-            mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
-            int numTypes = (response.getCredentialEntries().stream()
-                    .map(CredentialEntry::getType).collect(
-                            Collectors.toSet())).size(); // Dedupe type strings
-            mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(numTypes);
-            mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
-            mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
-            mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
-        }
-    }
-
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
      * we send back a TYPE_NO_CREDENTIAL error as to the developer.
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 64ac9b3..090c076 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -31,9 +31,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.InitialPhaseMetric;
-import com.android.server.credentials.metrics.ProviderStatusForMetrics;
+import com.android.server.credentials.metrics.ProviderSessionMetric;
 
 import java.util.UUID;
 
@@ -72,10 +70,8 @@
     protected R mProviderResponse;
     @NonNull
     protected Boolean mProviderResponseSet = false;
-    // Specific candidate provider metric for the provider this session handles
     @NonNull
-    protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
-            new CandidatePhaseMetric();
+    protected final ProviderSessionMetric mProviderSessionMetric = new ProviderSessionMetric();
     @NonNull
     private int mProviderSessionUid;
 
@@ -209,49 +205,18 @@
         return mRemoteCredentialService;
     }
 
-    protected void captureCandidateFailureInMetrics() {
-        mCandidatePhasePerProviderMetric.setHasException(true);
-    }
-
     /** Updates the status . */
     protected void updateStatusAndInvokeCallback(@NonNull Status status) {
         setStatus(status);
-        updateCandidateMetric(status);
+        mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status),
+                isCompletionStatus(status), mProviderSessionUid);
         mCallbacks.onProviderStatusChanged(status, mComponentName);
     }
 
-    private void updateCandidateMetric(Status status) {
-        try {
-            mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
-            mCandidatePhasePerProviderMetric
-                    .setQueryFinishTimeNanoseconds(System.nanoTime());
-            if (isTerminatingStatus(status)) {
-                mCandidatePhasePerProviderMetric.setQueryReturned(false);
-                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
-                        ProviderStatusForMetrics.QUERY_FAILURE
-                                .getMetricCode());
-            } else if (isCompletionStatus(status)) {
-                mCandidatePhasePerProviderMetric.setQueryReturned(true);
-                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
-                        ProviderStatusForMetrics.QUERY_SUCCESS
-                                .getMetricCode());
-            }
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
-        }
-    }
-
-    // Common method to transfer metrics from the initial phase to the candidate phase per provider
+    /** Common method that transfers metrics from the init phase to candidates */
     protected void startCandidateMetrics() {
-        try {
-            InitialPhaseMetric initMetric = ((RequestSession) mCallbacks).mInitialPhaseMetric;
-            mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
-            mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
-                    initMetric.getCredentialServiceStartedTimeNanoseconds());
-            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
-        }
+        mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric(
+                ((RequestSession) mCallbacks).mRequestSessionMetric.getInitialPhaseMetric());
     }
 
     /** Get the request to be sent to the provider. */
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index d4ad65e..cfb9ad4 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -16,8 +16,6 @@
 
 package com.android.server.credentials;
 
-import static com.android.server.credentials.MetricUtilities.logApiCalled;
-
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
@@ -30,27 +28,25 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.util.Log;
 
 import com.android.internal.R;
-import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
-import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
-import com.android.server.credentials.metrics.EntryEnum;
-import com.android.server.credentials.metrics.InitialPhaseMetric;
+import com.android.server.credentials.metrics.ApiName;
+import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
+import com.android.server.credentials.metrics.RequestSessionMetric;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
  * Base class of a request session, that listens to UI events. This class must be extended
  * every time a new response type is expected from the providers.
  */
-abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback {
+abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback {
     private static final String TAG = "RequestSession";
 
     // TODO: Revise access levels of attributes
@@ -77,16 +73,7 @@
     protected final CancellationSignal mCancellationSignal;
 
     protected final Map<String, ProviderSession> mProviders = new HashMap<>();
-    protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
-    protected final ChosenProviderFinalPhaseMetric
-            mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
-
-    // TODO(b/271135048) - Group metrics used in a scope together, such as here in RequestSession
-    // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
-    @NonNull
-    protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
-    // As emits occur in sequential order, increment this counter and utilize
-    protected int mSequenceCounter = 0;
+    protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric();
     protected final String mHybridService;
 
     @NonNull
@@ -122,17 +109,8 @@
                 mUserId, this);
         mHybridService = context.getResources().getString(
                 R.string.config_defaultCredentialManagerHybridService);
-        initialPhaseMetricSetup(timestampStarted);
-    }
-
-    private void initialPhaseMetricSetup(long timestampStarted) {
-        try {
-            mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
-            mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
-            mInitialPhaseMetric.setCallerUid(mCallingUid);
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
-        }
+        mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId,
+                mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
     }
 
     public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
@@ -140,11 +118,10 @@
 
     protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
 
-    // Sets up the initial metric collector for use across all request session impls
-    protected void setupInitialPhaseMetric(int metricCode, int requestClassType) {
-        this.mInitialPhaseMetric.setApiName(metricCode);
-        this.mInitialPhaseMetric.setCountRequestClassType(requestClassType);
-    }
+    protected abstract void invokeClientCallbackSuccess(V response) throws RemoteException;
+
+    protected abstract void invokeClientCallbackError(String errorType, String errorMsg) throws
+            RemoteException;
 
     public void addProviderSession(ComponentName componentName, ProviderSession providerSession) {
         mProviders.put(componentName.flattenToString(), providerSession);
@@ -170,26 +147,12 @@
             return;
         }
         Log.i(TAG, "Provider session found");
-        logBrowsingPhasePerSelect(selection, providerSession);
+        mRequestSessionMetric.collectMetricPerBrowsingSelect(selection,
+                providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric());
         providerSession.onUiEntrySelected(selection.getEntryKey(),
                 selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
     }
 
-    private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
-            ProviderSession providerSession) {
-        try {
-            CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
-            browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
-            browsingPhaseMetric.setEntryEnum(
-                    EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
-            browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
-                    .getCandidateUid());
-            this.mCandidateBrowsingPhaseMetric.add(browsingPhaseMetric);
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
-        }
-    }
-
     protected void finishSession(boolean propagateCancellation) {
         Log.i(TAG, "finishing session");
         if (propagateCancellation) {
@@ -208,14 +171,6 @@
         return false;
     }
 
-    protected void logApiCall(ChosenProviderFinalPhaseMetric finalPhaseMetric,
-            List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus) {
-        // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
-        // For the returned types by authentication entries - i.e. a CandidatePhase During Browse
-        // Possibly think of adding in more atoms for other APIs as well.
-        logApiCalled(finalPhaseMetric, browsingPhaseMetrics, apiStatus, ++mSequenceCounter);
-    }
-
     protected boolean isSessionCancelled() {
         return mCancellationSignal.isCanceled();
     }
@@ -239,7 +194,7 @@
         ArrayList<ProviderData> providerDataList = getProviderDataForUi();
         if (!providerDataList.isEmpty()) {
             Log.i(TAG, "provider list not empty about to initiate ui");
-            MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+            mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
             launchUiWithProviderData(providerDataList);
         }
     }
@@ -251,7 +206,7 @@
         ArrayList<ProviderData> providerDataList = new ArrayList<>();
 
         if (isSessionCancelled()) {
-            MetricUtilities.logApiCalled(mProviders, ++mSequenceCounter);
+            mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
             finishSession(/*propagateCancellation=*/true);
             return providerDataList;
         }
@@ -267,54 +222,65 @@
         return providerDataList;
     }
 
-    protected void collectFinalPhaseMetricStatus(boolean hasException,
-            ProviderStatusForMetrics finalSuccess) {
-        mChosenProviderFinalPhaseMetric.setHasException(hasException);
-        mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
-                finalSuccess.getMetricCode());
+    /**
+     * Allows subclasses to directly finalize the call and set closing metrics on response.
+     *
+     * @param response the response associated with the API call that just completed
+     */
+    protected void respondToClientWithResponseAndFinish(V response) {
+        mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false,
+                ProviderStatusForMetrics.FINAL_SUCCESS);
+        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+            Log.i(TAG, "Request has already been completed. This is strange.");
+            return;
+        }
+        if (isSessionCancelled()) {
+            mRequestSessionMetric.logApiCalledAtFinish(
+                    /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode());
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
+        try {
+            invokeClientCallbackSuccess(response);
+            mRequestSessionMetric.logApiCalledAtFinish(
+                    /*apiStatus=*/ ApiStatus.SUCCESS.getMetricCode());
+        } catch (RemoteException e) {
+            mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
+                    /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
+            Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
+            mRequestSessionMetric.logApiCalledAtFinish(
+                    /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode());
+        }
+        finishSession(/*propagateCancellation=*/false);
     }
 
     /**
-     * Called by RequestSession's upon chosen metric determination. It's expected that most bits
-     * are transferred here. However, certain new information, such as the selected provider's final
-     * exception bit, the framework to ui and back latency, or the ui response bit are set at other
-     * locations. Other information, such browsing metrics, api_status, and the sequence id count
-     * are combined together during the final emit moment with the actual and official
-     * {@link com.android.internal.util.FrameworkStatsLog} metric generation.
+     * Allows subclasses to directly finalize the call and set closing metrics on error completion.
      *
-     * @param componentName the componentName to associate with a provider
+     * @param errorType the type of error given back in the flow
+     * @param errorMsg the error message given back in the flow
      */
-    protected void setChosenMetric(ComponentName componentName) {
-        try {
-            CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
-                    .mCandidatePhasePerProviderMetric;
-
-            mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
-            mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
-
-            mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
-                    metric.getQueryLatencyMicroseconds());
-
-            mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
-                    metric.getServiceBeganTimeNanoseconds());
-            mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
-                    metric.getStartQueryTimeNanoseconds());
-            mChosenProviderFinalPhaseMetric.setQueryEndTimeNanoseconds(metric
-                    .getQueryFinishTimeNanoseconds());
-
-            mChosenProviderFinalPhaseMetric.setNumEntriesTotal(metric.getNumEntriesTotal());
-            mChosenProviderFinalPhaseMetric.setCredentialEntryCount(metric
-                    .getCredentialEntryCount());
-            mChosenProviderFinalPhaseMetric.setCredentialEntryTypeCount(
-                    metric.getCredentialEntryTypeCount());
-            mChosenProviderFinalPhaseMetric.setActionEntryCount(metric.getActionEntryCount());
-            mChosenProviderFinalPhaseMetric.setRemoteEntryCount(metric.getRemoteEntryCount());
-            mChosenProviderFinalPhaseMetric.setAuthenticationEntryCount(
-                    metric.getAuthenticationEntryCount());
-            mChosenProviderFinalPhaseMetric.setAvailableEntries(metric.getAvailableEntries());
-            mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
-        } catch (Exception e) {
-            Log.w(TAG, "Unexpected error during metric logging: " + e);
+    protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+        mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
+                /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
+        if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+            Log.i(TAG, "Request has already been completed. This is strange.");
+            return;
         }
+        if (isSessionCancelled()) {
+            mRequestSessionMetric.logApiCalledAtFinish(
+                    /*apiStatus=*/ ApiStatus.CLIENT_CANCELED.getMetricCode());
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
+
+        try {
+            invokeClientCallbackError(errorType, errorMsg);
+        } catch (RemoteException e) {
+            Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
+        }
+        boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING);
+        mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled);
+        finishSession(/*propagateCancellation=*/false);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index abd749c..f40e73e 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -16,12 +16,22 @@
 
 package com.android.server.credentials.metrics;
 
+import static android.credentials.ui.RequestInfo.TYPE_CREATE;
+import static android.credentials.ui.RequestInfo.TYPE_GET;
+import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED;
+
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CLEAR_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_CREATE_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_GET_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_UNKNOWN;
 
+import android.credentials.ui.RequestInfo;
+import android.util.Log;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
 public enum ApiName {
     UNKNOWN(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_UNKNOWN),
     GET_CREDENTIAL(CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_GET_CREDENTIAL),
@@ -31,12 +41,24 @@
         CREDENTIAL_MANAGER_INITIAL_PHASE__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
     );
 
+    private static final String TAG = "ApiName";
+
     private final int mInnerMetricCode;
 
+    private static final Map<String, Integer> sRequestInfoToMetric = Map.ofEntries(
+            new AbstractMap.SimpleEntry<>(TYPE_CREATE,
+                    CREATE_CREDENTIAL.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(TYPE_GET,
+                    GET_CREDENTIAL.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(TYPE_UNDEFINED,
+                    CLEAR_CREDENTIAL.mInnerMetricCode)
+    );
+
     ApiName(int innerMetricCode) {
         this.mInnerMetricCode = innerMetricCode;
     }
 
+
     /**
      * Gives the West-world version of the metric name.
      *
@@ -45,4 +67,20 @@
     public int getMetricCode() {
         return this.mInnerMetricCode;
     }
+
+    /**
+     * Given a string key type known to the framework, this returns the known metric code associated
+     * with that string. This is mainly used by {@link RequestSessionMetric} collection contexts.
+     * This relies on {@link RequestInfo} string keys.
+     *
+     * @param stringKey a string key type for a particular request info
+     * @return the metric code associated with this request info's api name counterpart
+     */
+    public static int getMetricCodeFromRequestInfo(String stringKey) {
+        if (!sRequestInfoToMetric.containsKey(stringKey)) {
+            Log.w(TAG, "Attempted to use an unsupported string key request info");
+            return UNKNOWN.mInnerMetricCode;
+        }
+        return sRequestInfoToMetric.get(stringKey);
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 4053294..10d4f9c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -16,12 +16,14 @@
 
 package com.android.server.credentials.metrics;
 
+import android.util.IntArray;
 import android.util.Log;
 
 import com.android.server.credentials.MetricUtilities;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * The central candidate provider metric object that mimics our defined metric setup.
@@ -70,7 +72,7 @@
     // The count of authentication entries from this provider, defaults to -1
     private int mAuthenticationEntryCount = -1;
     // Gathered to pass on to chosen provider when required
-    private final List<Integer> mAvailableEntries = new ArrayList<>();
+    private final IntArray mAvailableEntries = new IntArray();
 
     public CandidatePhaseMetric() {
     }
@@ -263,6 +265,6 @@
      * this metric
      */
     public List<Integer> getAvailableEntries() {
-        return new ArrayList<>(this.mAvailableEntries); // no alias copy
+        return Arrays.stream(mAvailableEntries.toArray()).boxed().collect(Collectors.toList());
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
new file mode 100644
index 0000000..76fd478
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -0,0 +1,184 @@
+/*
+ * 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.credentials.metrics;
+
+import android.annotation.NonNull;
+import android.service.credentials.BeginCreateCredentialResponse;
+import android.service.credentials.BeginGetCredentialResponse;
+import android.service.credentials.CredentialEntry;
+import android.util.Log;
+
+import com.android.server.credentials.MetricUtilities;
+
+import java.util.stream.Collectors;
+
+/**
+ * Provides contextual metric collection for objects generated from
+ * {@link com.android.server.credentials.ProviderSession} flows to isolate metric
+ * collection from the core codebase. For any future additions to the ProviderSession subclass
+ * list, metric collection should be added to this file.
+ */
+public class ProviderSessionMetric {
+
+    private static final String TAG = "ProviderSessionMetric";
+
+    // Specific candidate provider metric for the provider this session handles
+    @NonNull
+    protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
+            new CandidatePhaseMetric();
+
+    public ProviderSessionMetric() {}
+
+    /**
+     * Retrieve the candidate provider phase metric and the data it contains.
+     */
+    public CandidatePhaseMetric getCandidatePhasePerProviderMetric() {
+        return mCandidatePhasePerProviderMetric;
+    }
+
+    /**
+     * This collects for ProviderSessions, with respect to the candidate providers, whether
+     * an exception occurred in the candidate call.
+     *
+     * @param hasException indicates if the candidate provider associated with an exception
+     */
+    public void collectCandidateExceptionStatus(boolean hasException) {
+        mCandidatePhasePerProviderMetric.setHasException(hasException);
+    }
+
+    /**
+     * Used to collect metrics at the update stage when a candidate provider gives back an update.
+     *
+     * @param isFailureStatus indicates the candidate provider sent back a terminated response
+     * @param isCompletionStatus indicates the candidate provider sent back a completion response
+     * @param providerSessionUid the uid of the provider
+     */
+    public void collectCandidateMetricUpdate(boolean isFailureStatus,
+            boolean isCompletionStatus, int providerSessionUid) {
+        try {
+            mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid);
+            mCandidatePhasePerProviderMetric
+                    .setQueryFinishTimeNanoseconds(System.nanoTime());
+            if (isFailureStatus) {
+                mCandidatePhasePerProviderMetric.setQueryReturned(false);
+                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                        ProviderStatusForMetrics.QUERY_FAILURE
+                                .getMetricCode());
+            } else if (isCompletionStatus) {
+                mCandidatePhasePerProviderMetric.setQueryReturned(true);
+                mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                        ProviderStatusForMetrics.QUERY_SUCCESS
+                                .getMetricCode());
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Starts the collection of a single provider metric in the candidate phase of the API flow.
+     * It's expected that this should be called at the start of the query phase so that session id
+     * and timestamps can be shared. They can be accessed granular-ly through the underlying
+     * objects, but for {@link com.android.server.credentials.ProviderSession} context metrics,
+     * it's recommended to use these context-specified methods.
+     *
+     * @param initMetric the pre candidate phase metric collection object of type
+     * {@link InitialPhaseMetric} used to transfer initial information
+     */
+    public void collectCandidateMetricSetupViaInitialMetric(InitialPhaseMetric initMetric) {
+        try {
+            mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
+            mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
+                    initMetric.getCredentialServiceStartedTimeNanoseconds());
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Once candidate providers give back entries, this helps collect their info for metric
+     * purposes.
+     *
+     * @param response contains entries and data from the candidate provider responses
+     * @param <R> the response type associated with the API flow in progress
+     */
+    public <R> void collectCandidateEntryMetrics(R response) {
+        try {
+            if (response instanceof BeginGetCredentialResponse) {
+                beginGetCredentialResponseCollectionCandidateEntryMetrics(
+                        (BeginGetCredentialResponse) response);
+            } else if (response instanceof BeginCreateCredentialResponse) {
+                beginCreateCredentialResponseCollectionCandidateEntryMetrics(
+                        (BeginCreateCredentialResponse) response);
+            } else {
+                Log.i(TAG, "Your response type is unsupported for metric logging");
+            }
+
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    private void beginCreateCredentialResponseCollectionCandidateEntryMetrics(
+            BeginCreateCredentialResponse response) {
+        var createEntries = response.getCreateEntries();
+        int numRemoteEntry = MetricUtilities.ZERO;
+        if (response.getRemoteCreateEntry() != null) {
+            numRemoteEntry = MetricUtilities.UNIT;
+            mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
+        }
+        int numCreateEntries =
+                createEntries == null ? MetricUtilities.ZERO : createEntries.size();
+        if (numCreateEntries > MetricUtilities.ZERO) {
+            createEntries.forEach(c ->
+                    mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+        }
+        mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries + numRemoteEntry);
+        mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
+        mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCreateEntries);
+        mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(MetricUtilities.UNIT);
+    }
+
+    private void beginGetCredentialResponseCollectionCandidateEntryMetrics(
+            BeginGetCredentialResponse response) {
+        int numCredEntries = response.getCredentialEntries().size();
+        int numActionEntries = response.getActions().size();
+        int numAuthEntries = response.getAuthenticationActions().size();
+        int numRemoteEntry = MetricUtilities.ZERO;
+        if (response.getRemoteCredentialEntry() != null) {
+            numRemoteEntry = MetricUtilities.UNIT;
+            mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
+        }
+        response.getCredentialEntries().forEach(c ->
+                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
+        response.getActions().forEach(c ->
+                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
+        response.getAuthenticationActions().forEach(c ->
+                mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
+        mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
+                + numActionEntries + numRemoteEntry);
+        mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
+        int numTypes = (response.getCredentialEntries().stream()
+                .map(CredentialEntry::getType).collect(
+                        Collectors.toSet())).size(); // Dedupe type strings
+        mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(numTypes);
+        mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
+        mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
+        mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
new file mode 100644
index 0000000..325b7e1
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.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.credentials.metrics;
+
+import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase;
+import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase;
+
+import android.annotation.NonNull;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.server.credentials.ProviderSession;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides contextual metric collection for objects generated from classes such as
+ * {@link com.android.server.credentials.GetRequestSession},
+ * {@link com.android.server.credentials.CreateRequestSession},
+ * and {@link com.android.server.credentials.ClearRequestSession} flows to isolate metric
+ * collection from the core codebase. For any future additions to the RequestSession subclass
+ * list, metric collection should be added to this file.
+ */
+public class RequestSessionMetric {
+    private static final String TAG = "RequestSessionMetric";
+
+    // As emits occur in sequential order, increment this counter and utilize
+    protected int mSequenceCounter = 0;
+
+    protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+    protected final ChosenProviderFinalPhaseMetric
+            mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
+    // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
+    @NonNull
+    protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
+
+    public RequestSessionMetric() {
+    }
+
+    /**
+     * Increments the metric emit sequence counter and returns the current state value of the
+     * sequence.
+     *
+     * @return the current state value of the metric emit sequence.
+     */
+    public int returnIncrementSequence() {
+        return ++mSequenceCounter;
+    }
+
+
+    /**
+     * @return the initial metrics associated with the request session
+     */
+    public InitialPhaseMetric getInitialPhaseMetric() {
+        return mInitialPhaseMetric;
+    }
+
+    /**
+     * Upon starting the service, this fills the initial phase metric properly.
+     *
+     * @param timestampStarted the timestamp the service begins at
+     * @param mRequestId       the IBinder used to retrieve a unique id
+     * @param mCallingUid      the calling process's uid
+     * @param metricCode       typically pulled from {@link ApiName}
+     */
+    public void collectInitialPhaseMetricInfo(long timestampStarted, IBinder mRequestId,
+            int mCallingUid, int metricCode) {
+        try {
+            mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
+            mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
+            mInitialPhaseMetric.setCallerUid(mCallingUid);
+            mInitialPhaseMetric.setApiName(metricCode);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Collects whether the UI returned for metric purposes.
+     *
+     * @param uiReturned indicates whether the ui returns or not
+     */
+    public void collectUiReturnedFinalPhase(boolean uiReturned) {
+        try {
+            mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Sets the start time for the UI being called for metric purposes.
+     *
+     * @param uiCallStartTime the nanosecond time when the UI call began
+     */
+    public void collectUiCallStartTime(long uiCallStartTime) {
+        try {
+            mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(uiCallStartTime);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * When the UI responds to the framework at the very final phase, this collects the timestamp
+     * and status of the return for metric purposes.
+     *
+     * @param uiReturned     indicates whether the ui returns or not
+     * @param uiEndTimestamp the nanosecond time when the UI call ended
+     */
+    public void collectUiResponseData(boolean uiReturned, long uiEndTimestamp) {
+        try {
+            mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
+            mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(uiEndTimestamp);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Collects the final chosen provider status, with the status value coming from
+     * {@link ApiStatus}.
+     *
+     * @param status the final status of the chosen provider
+     */
+    public void collectChosenProviderStatus(int status) {
+        try {
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(status);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Collects request class type count in the RequestSession flow.
+     *
+     * @param requestClassTypeCount the number of class types in the request
+     */
+    public void collectGetFlowInitialMetricInfo(int requestClassTypeCount) {
+        try {
+            mInitialPhaseMetric.setCountRequestClassType(requestClassTypeCount);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * During browsing, where multiple entries can be selected, this collects the browsing phase
+     * metric information.
+     * TODO(b/271135048) - modify asap to account for a new metric emit per browse response to
+     * framework.
+     *
+     * @param selection                   contains the selected entry key type
+     * @param selectedProviderPhaseMetric contains the utility information of the selected provider
+     */
+    public void collectMetricPerBrowsingSelect(UserSelectionDialogResult selection,
+            CandidatePhaseMetric selectedProviderPhaseMetric) {
+        try {
+            CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
+            browsingPhaseMetric.setSessionId(mInitialPhaseMetric.getSessionId());
+            browsingPhaseMetric.setEntryEnum(
+                    EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
+            browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid());
+            mCandidateBrowsingPhaseMetric.add(browsingPhaseMetric);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Updates the final phase metric with the designated bit
+     *
+     * @param exceptionBitFinalPhase represents if the final phase provider had an exception
+     */
+    private void setHasExceptionFinalPhase(boolean exceptionBitFinalPhase) {
+        try {
+            mChosenProviderFinalPhaseMetric.setHasException(exceptionBitFinalPhase);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Allows encapsulating the overall final phase metric status from the chosen and final
+     * provider.
+     *
+     * @param hasException represents if the final phase provider had an exception
+     * @param finalStatus  represents the final status of the chosen provider
+     */
+    public void collectFinalPhaseProviderMetricStatus(boolean hasException,
+            ProviderStatusForMetrics finalStatus) {
+        try {
+            mChosenProviderFinalPhaseMetric.setHasException(hasException);
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+                    finalStatus.getMetricCode());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Called by RequestSessions upon chosen metric determination. It's expected that most bits
+     * are transferred here. However, certain new information, such as the selected provider's final
+     * exception bit, the framework to ui and back latency, or the ui response bit are set at other
+     * locations. Other information, such browsing metrics, api_status, and the sequence id count
+     * are combined during the final emit moment with the actual and official
+     * {@link com.android.internal.util.FrameworkStatsLog} metric generation.
+     *
+     * @param candidatePhaseMetric the componentName to associate with a provider
+     */
+    public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) {
+        try {
+            mChosenProviderFinalPhaseMetric.setSessionId(candidatePhaseMetric.getSessionId());
+            mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid());
+
+            mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
+                    candidatePhaseMetric.getQueryLatencyMicroseconds());
+
+            mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+                    candidatePhaseMetric.getServiceBeganTimeNanoseconds());
+            mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+                    candidatePhaseMetric.getStartQueryTimeNanoseconds());
+            mChosenProviderFinalPhaseMetric.setQueryEndTimeNanoseconds(candidatePhaseMetric
+                    .getQueryFinishTimeNanoseconds());
+
+            mChosenProviderFinalPhaseMetric.setNumEntriesTotal(candidatePhaseMetric
+                    .getNumEntriesTotal());
+            mChosenProviderFinalPhaseMetric.setCredentialEntryCount(candidatePhaseMetric
+                    .getCredentialEntryCount());
+            mChosenProviderFinalPhaseMetric.setCredentialEntryTypeCount(
+                    candidatePhaseMetric.getCredentialEntryTypeCount());
+            mChosenProviderFinalPhaseMetric.setActionEntryCount(candidatePhaseMetric
+                    .getActionEntryCount());
+            mChosenProviderFinalPhaseMetric.setRemoteEntryCount(candidatePhaseMetric
+                    .getRemoteEntryCount());
+            mChosenProviderFinalPhaseMetric.setAuthenticationEntryCount(
+                    candidatePhaseMetric.getAuthenticationEntryCount());
+            mChosenProviderFinalPhaseMetric.setAvailableEntries(candidatePhaseMetric
+                    .getAvailableEntries());
+            mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * In the final phase, this helps log use cases that were either pure failures or user
+     * canceled. It's expected that {@link #collectFinalPhaseProviderMetricStatus(boolean,
+     * ProviderStatusForMetrics) collectFinalPhaseProviderMetricStatus} is called prior to this.
+     * Otherwise, the logging will miss required bits
+     *
+     * @param isUserCanceledError a boolean indicating if the error was due to user cancelling
+     */
+    public void logFailureOrUserCancel(boolean isUserCanceledError) {
+        try {
+            if (isUserCanceledError) {
+                setHasExceptionFinalPhase(/* has_exception */ false);
+                logApiCalledAtFinish(
+                        /* apiStatus */ ApiStatus.USER_CANCELED.getMetricCode());
+            } else {
+                logApiCalledAtFinish(
+                        /* apiStatus */ ApiStatus.FAILURE.getMetricCode());
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Handles candidate phase metric emit in the RequestSession context, after the candidate phase
+     * completes.
+     *
+     * @param providers a map with known providers and their held metric objects
+     */
+    public void logCandidatePhaseMetrics(Map<String, ProviderSession> providers) {
+        try {
+            logApiCalledCandidatePhase(providers, ++mSequenceCounter);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * Handles the final logging for RequestSession context for the final phase.
+     *
+     * @param apiStatus the final status of the api being called
+     */
+    public void logApiCalledAtFinish(int apiStatus) {
+        try {
+            // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
+            // For the returned types by authentication entries - i.e. a CandidatePhase During
+            // Browse
+            // Possibly think of adding in more atoms for other APIs as well.
+            logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric,
+                    apiStatus,
+                    ++mSequenceCounter);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
index 9f9e6a3..d813962 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
@@ -57,7 +57,8 @@
 
         @Parameterized.Parameters(name = "reboot={0}")
         @JvmStatic
-        fun parameters() = arrayOf(false, true)
+        // TODO(b/275403538): re-enable non-reboot scenarios with better tracking of APK removal
+        fun parameters() = arrayOf(/*false, */true)
 
         data class Volume(
             val diskId: String,
@@ -200,15 +201,6 @@
             // TODO: There must be a better way to prevent it from auto-mounting.
             removeVirtualDisk()
             device.reboot()
-        } else {
-            // Because PackageManager unmount scan is asynchronous, need to retry until the package
-            // has been unloaded. This only has to be done in the non-reboot case. Reboot will
-            // clear the data structure by its nature.
-            retryUntilSuccess {
-                // The compiler section will print the state of the physical APK
-                HostUtils.packageSection(device, pkgName, sectionName = "Compiler stats")
-                        .any { it.contains("Unable to find package: $pkgName") }
-            }
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index 1cc7ccc..5cc3371 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -24,30 +24,45 @@
 android_test_helper_app {
     name: "PackageManagerTestAppStub",
     manifest: "AndroidManifestVersion1.xml",
-    srcs: []
+    srcs: [],
 }
 
 android_test_helper_app {
     name: "PackageManagerTestAppVersion1",
-    manifest: "AndroidManifestVersion1.xml"
+    manifest: "AndroidManifestVersion1.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
 }
 
 android_test_helper_app {
     name: "PackageManagerTestAppVersion2",
-    manifest: "AndroidManifestVersion2.xml"
+    manifest: "AndroidManifestVersion2.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
 }
 
 android_test_helper_app {
     name: "PackageManagerTestAppVersion3",
-    manifest: "AndroidManifestVersion3.xml"
+    manifest: "AndroidManifestVersion3.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
 }
 
 android_test_helper_app {
     name: "PackageManagerTestAppVersion4",
-    manifest: "AndroidManifestVersion4.xml"
+    manifest: "AndroidManifestVersion4.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
 }
 
 android_test_helper_app {
     name: "PackageManagerTestAppOriginalOverride",
-    manifest: "AndroidManifestOriginalOverride.xml"
+    manifest: "AndroidManifestOriginalOverride.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index d559b67..8c84014 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -70,6 +70,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -2911,6 +2912,7 @@
     }
 
     @LargeTest
+    @CddTest(requirements = {"3.1/C-0-8"})
     public void testMinInstallableTargetSdkFail() throws Exception {
         // Test installing a package that doesn't meet the minimum installable sdk requirement
         setMinInstallableTargetSdkFeatureFlags();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 390119c..36d191b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1008,6 +1008,42 @@
                 dropboxEntryBroadcast2.first, expectedMergedBroadcast.first));
     }
 
+    @Test
+    public void testDeliveryGroupPolicy_sameAction_differentMatchingCriteria() {
+        final Intent closeSystemDialogs1 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        final BroadcastOptions optionsCloseSystemDialog1 = BroadcastOptions.makeBasic()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+        final Intent closeSystemDialogs2 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+                .putExtra("reason", "testing");
+        final BroadcastOptions optionsCloseSystemDialog2 = BroadcastOptions.makeBasic()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing");
+
+        // Halt all processing so that we get a consistent view
+        mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+                closeSystemDialogs1, optionsCloseSystemDialog1));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+                closeSystemDialogs2, optionsCloseSystemDialog2));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+                closeSystemDialogs1, optionsCloseSystemDialog1));
+        // Verify that only the older broadcast with no extras was removed.
+        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        verifyPendingRecords(queue, List.of(closeSystemDialogs2, closeSystemDialogs1));
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+                closeSystemDialogs2, optionsCloseSystemDialog2));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+                closeSystemDialogs1, optionsCloseSystemDialog1));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+                closeSystemDialogs2, optionsCloseSystemDialog2));
+        // Verify that only the older broadcast with no extras was removed.
+        verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2));
+    }
+
     private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs,
             int droppedCount) {
         final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ba4a54e..d2431f1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4586,6 +4586,57 @@
             "data_stall_recovery_should_skip_bool_array";
 
     /**
+     * String array containing the list of names for service numbers provided by carriers. This key
+     * should be used with {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY}. The names provided in
+     * this array will be mapped 1:1 with the numbers provided in the {@link
+     * #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} array.
+     *
+     * <p>The data would be considered valid if and only if:
+     *
+     * <ul>
+     *   <li>The number of items in both the arrays are equal
+     *   <li>The data added to the {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} array is valid.
+     *       See {@link #KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY} for more information.
+     * </ul>
+     *
+     * <p>Example:
+     *
+     * <pre><code>
+     * <string-array name="carrier_service_name_array" num="2">
+     *   <item value="Police"/>
+     *   <item value="Ambulance"/>
+     * </string-array>
+     * </code></pre>
+     */
+    public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
+
+    /**
+     * String array containing the list of service numbers provided by carriers. This key should be
+     * used with {@link #KEY_CARRIER_SERVICE_NAME_STRING_ARRAY}. The numbers provided in this array
+     * will be mapped 1:1 with the names provided in the {@link
+     * #KEY_CARRIER_SERVICE_NAME_STRING_ARRAY} array.
+     *
+     * <p>The data would be considered valid if and only if:
+     *
+     * <ul>
+     *   <li>The number of items in both the arrays are equal
+     *   <li>The item added in this key follows a specific format. Either it should be all numbers,
+     *       or "+" followed by all numbers.
+     * </ul>
+     *
+     * <p>Example:
+     *
+     * <pre><code>
+     * <string-array name="carrier_service_number_array" num="2">
+     *   <item value="123"/>
+     *   <item value="+343"/>
+     * </string-array>
+     * </code></pre>
+     */
+    public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY =
+        "carrier_service_number_array";
+
+    /**
      * Configs used by ImsServiceEntitlement.
      */
     public static final class ImsServiceEntitlement {
@@ -10285,6 +10336,8 @@
                 new long[] {180000, 180000, 180000, 180000});
         sDefaults.putBooleanArray(KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
                 new boolean[] {false, false, true, false, false});
+        sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
+        sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
     }
 
     /**