Merge "Start ODP system service."
diff --git a/core/api/current.txt b/core/api/current.txt
index 9851428..de540bf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4757,6 +4757,7 @@
     method public int getLaunchDisplayId();
     method public boolean getLockTaskMode();
     method public int getPendingIntentBackgroundActivityStartMode();
+    method public int getPendingIntentCreatorBackgroundActivityStartMode();
     method public int getSplashScreenStyle();
     method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public boolean isShareIdentityEnabled();
@@ -4777,6 +4778,7 @@
     method public android.app.ActivityOptions setLockTaskEnabled(boolean);
     method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
     method @NonNull public android.app.ActivityOptions setPendingIntentBackgroundActivityStartMode(int);
+    method @NonNull public android.app.ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(int);
     method @NonNull public android.app.ActivityOptions setShareIdentityEnabled(boolean);
     method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
     method public android.os.Bundle toBundle();
@@ -24030,6 +24032,7 @@
     method @Nullable public android.net.Uri getIconUri();
     method @NonNull public String getId();
     method @NonNull public CharSequence getName();
+    method public int getType();
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
@@ -24046,6 +24049,22 @@
     field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
     field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+    field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
+    field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
+    field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_DOCK = 13; // 0xd
+    field public static final int TYPE_GROUP = 2000; // 0x7d0
+    field public static final int TYPE_HDMI = 9; // 0x9
+    field public static final int TYPE_HEARING_AID = 23; // 0x17
+    field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
+    field public static final int TYPE_REMOTE_SPEAKER = 1002; // 0x3ea
+    field public static final int TYPE_REMOTE_TV = 1001; // 0x3e9
+    field public static final int TYPE_UNKNOWN = 0; // 0x0
+    field public static final int TYPE_USB_ACCESSORY = 12; // 0xc
+    field public static final int TYPE_USB_DEVICE = 11; // 0xb
+    field public static final int TYPE_USB_HEADSET = 22; // 0x16
+    field public static final int TYPE_WIRED_HEADPHONES = 4; // 0x4
+    field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
   }
 
   public static final class MediaRoute2Info.Builder {
@@ -24061,6 +24080,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
@@ -24634,7 +24654,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
     field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
     field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2
-    field public static final int FLAG_SUGGESTED_ROUTE = 4; // 0x4
+    field public static final int FLAG_SUGGESTED = 4; // 0x4
     field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
     field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
     field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
@@ -26328,8 +26348,23 @@
 
 package android.media.tv {
 
+  public final class AdBuffer implements android.os.Parcelable {
+    ctor public AdBuffer(int, @NonNull String, @NonNull android.os.SharedMemory, int, int, long, int);
+    method public int describeContents();
+    method public int getFlags();
+    method public int getId();
+    method public int getLength();
+    method @NonNull public String getMimeType();
+    method public int getOffset();
+    method public long getPresentationTimeUs();
+    method @NonNull public android.os.SharedMemory getSharedMemory();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdBuffer> CREATOR;
+  }
+
   public final class AdRequest implements android.os.Parcelable {
     ctor public AdRequest(int, int, @Nullable android.os.ParcelFileDescriptor, long, long, long, @Nullable String, @NonNull android.os.Bundle);
+    ctor public AdRequest(int, int, @Nullable android.net.Uri, long, long, long, @NonNull android.os.Bundle);
     method public int describeContents();
     method public long getEchoIntervalMillis();
     method @Nullable public android.os.ParcelFileDescriptor getFileDescriptor();
@@ -26339,6 +26374,7 @@
     method public int getRequestType();
     method public long getStartTimeMillis();
     method public long getStopTimeMillis();
+    method @Nullable public android.net.Uri getUri();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdRequest> CREATOR;
     field public static final int REQUEST_TYPE_START = 1; // 0x1
@@ -26353,6 +26389,7 @@
     method public int getResponseType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdResponse> CREATOR;
+    field public static final int RESPONSE_TYPE_BUFFERING = 5; // 0x5
     field public static final int RESPONSE_TYPE_ERROR = 4; // 0x4
     field public static final int RESPONSE_TYPE_FINISHED = 2; // 0x2
     field public static final int RESPONSE_TYPE_PLAYING = 1; // 0x1
@@ -27106,6 +27143,7 @@
   public abstract static class TvInputService.Session implements android.view.KeyEvent.Callback {
     ctor public TvInputService.Session(android.content.Context);
     method public void layoutSurface(int, int, int, int);
+    method public void notifyAdBufferConsumed(@NonNull android.media.tv.AdBuffer);
     method public void notifyAdResponse(@NonNull android.media.tv.AdResponse);
     method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
     method public void notifyAudioPresentationChanged(@NonNull java.util.List<android.media.AudioPresentation>);
@@ -27121,6 +27159,7 @@
     method public void notifyTuned(@NonNull android.net.Uri);
     method public void notifyVideoAvailable();
     method public void notifyVideoUnavailable(int);
+    method public void onAdBuffer(@NonNull android.media.tv.AdBuffer);
     method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
     method public android.view.View onCreateOverlayView();
     method public boolean onGenericMotionEvent(android.view.MotionEvent);
@@ -27407,9 +27446,11 @@
     ctor public TvInteractiveAppService.Session(@NonNull android.content.Context);
     method public boolean isMediaViewEnabled();
     method @CallSuper public void layoutSurface(int, int, int, int);
+    method @CallSuper public void notifyAdBuffer(@NonNull android.media.tv.AdBuffer);
     method @CallSuper public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String);
     method @CallSuper public void notifySessionStateChanged(int, int);
     method @CallSuper public final void notifyTeletextAppStateChanged(int);
+    method public void onAdBufferConsumed(@NonNull android.media.tv.AdBuffer);
     method public void onAdResponse(@NonNull android.media.tv.AdResponse);
     method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
     method public void onContentAllowed();
@@ -45372,6 +45413,7 @@
     field public static final String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
     field @Deprecated public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
     field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH";
+    field public static final String EXTRA_LAST_KNOWN_NETWORK_COUNTRY = "android.telephony.extra.LAST_KNOWN_NETWORK_COUNTRY";
     field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
     field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY";
     field public static final String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d0c000e..4a0b2eb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9783,6 +9783,133 @@
 
 }
 
+package android.net.wifi.sharedconnectivity.app {
+
+  public final class DeviceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0, to=100) @NonNull public int getBatteryPercentage();
+    method @IntRange(from=0, to=3) @NonNull public int getConnectionStrength();
+    method @NonNull public String getDeviceName();
+    method public int getDeviceType();
+    method @NonNull public String getModelName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.DeviceInfo> CREATOR;
+    field public static final int DEVICE_TYPE_LAPTOP = 3; // 0x3
+    field public static final int DEVICE_TYPE_PHONE = 1; // 0x1
+    field public static final int DEVICE_TYPE_TABLET = 2; // 0x2
+    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class DeviceInfo.Builder {
+    ctor public DeviceInfo.Builder();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setDeviceName(@NonNull String);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setDeviceType(int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setModelName(@NonNull String);
+  }
+
+  public final class KnownNetwork implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo();
+    method @NonNull public int getNetworkSource();
+    method @NonNull public int[] getSecurityTypes();
+    method @NonNull public String getSsid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.KnownNetwork> CREATOR;
+    field public static final int NETWORK_SOURCE_CLOUD_SELF = 1; // 0x1
+    field public static final int NETWORK_SOURCE_NEARBY_SELF = 0; // 0x0
+  }
+
+  public static final class KnownNetwork.Builder {
+    ctor public KnownNetwork.Builder();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSecurityTypes(@NonNull int[]);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String);
+  }
+
+  public interface SharedConnectivityClientCallback {
+    method public void onKnownNetworksUpdated(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork>);
+    method public void onSharedConnectivitySettingsChanged(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState);
+    method public void onTetherNetworksUpdated(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.TetherNetwork>);
+  }
+
+  public class SharedConnectivityManager {
+    ctor public SharedConnectivityManager(@NonNull android.content.Context);
+    method public boolean connectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
+    method public boolean connectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork);
+    method public boolean disconnectTetherNetwork();
+    method public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
+    method public boolean registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
+    method public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
+  }
+
+  public final class SharedConnectivitySettingsState implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public boolean isInstantTetherEnabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState> CREATOR;
+  }
+
+  public static final class SharedConnectivitySettingsState.Builder {
+    ctor public SharedConnectivitySettingsState.Builder();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherEnabled(boolean);
+  }
+
+  public final class TetherNetwork implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public long getDeviceId();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo();
+    method @Nullable public String getHotspotBssid();
+    method @Nullable public int[] getHotspotSecurityTypes();
+    method @Nullable public String getHotspotSsid();
+    method @NonNull public String getNetworkName();
+    method @NonNull public int getNetworkType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.TetherNetwork> CREATOR;
+    field public static final int NETWORK_TYPE_CELLULAR = 1; // 0x1
+    field public static final int NETWORK_TYPE_ETHERNET = 3; // 0x3
+    field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int NETWORK_TYPE_WIFI = 2; // 0x2
+  }
+
+  public static final class TetherNetwork.Builder {
+    ctor public TetherNetwork.Builder();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork build();
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceId(long);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotBssid(@NonNull String);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSecurityTypes(@NonNull int[]);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSsid(@NonNull String);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkName(@NonNull String);
+    method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkType(int);
+  }
+
+}
+
+package android.net.wifi.sharedconnectivity.service {
+
+  public abstract class SharedConnectivityService extends android.app.Service {
+    ctor public SharedConnectivityService();
+    ctor public SharedConnectivityService(@NonNull android.os.Handler);
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onConnectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
+    method public abstract void onConnectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork);
+    method public abstract void onDisconnectTetherNetwork();
+    method public abstract void onForgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
+    method public final void setKnownNetworks(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork>);
+    method public final void setSettingsState(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState);
+    method public final void setTetherNetworks(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.TetherNetwork>);
+  }
+
+}
+
 package android.nfc {
 
   public final class NfcAdapter {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2f5e820..b3e3f35 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1863,6 +1863,14 @@
 
 }
 
+package android.net.wifi.sharedconnectivity.app {
+
+  public class SharedConnectivityManager {
+    method public void setService(@Nullable android.os.IInterface);
+  }
+
+}
+
 package android.os {
 
   public final class BatteryStatsManager {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index b57fb20..3b88257 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -343,6 +343,13 @@
             "android:activity.applyActivityFlagsForBubbles";
 
     /**
+     * Indicates to apply {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the launching shortcut.
+     * @hide
+     */
+    private static final String KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT =
+            "android:activity.applyMultipleTaskFlagForShortcut";
+
+    /**
      * For Activity transitions, the calling Activity's TransitionListener used to
      * notify the called Activity when the shared element and the exit transitions
      * complete.
@@ -397,8 +404,8 @@
     /** See {@link #setDismissKeyguard()}. */
     private static final String KEY_DISMISS_KEYGUARD = "android.activity.dismissKeyguard";
 
-    private static final String KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE =
-            "android.activity.ignorePendingIntentCreatorForegroundState";
+    private static final String KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE =
+            "android.activity.pendingIntentCreatorBackgroundActivityStartMode";
 
     /**
      * @see #setLaunchCookie
@@ -476,6 +483,7 @@
     private boolean mShareIdentity = false;
     private boolean mDisallowEnterPictureInPictureWhileLaunching;
     private boolean mApplyActivityFlagsForBubbles;
+    private boolean mApplyMultipleTaskFlagForShortcut;
     private boolean mTaskAlwaysOnTop;
     private boolean mTaskOverlay;
     private boolean mTaskOverlayCanResume;
@@ -499,7 +507,9 @@
     private boolean mTransientLaunch;
     private PictureInPictureParams mLaunchIntoPipParams;
     private boolean mDismissKeyguard;
-    private boolean mIgnorePendingIntentCreatorForegroundState;
+    @BackgroundActivityStartMode
+    private int mPendingIntentCreatorBackgroundActivityStartMode =
+            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
     private boolean mDisableStartingWindow;
 
     /**
@@ -1276,6 +1286,8 @@
                 KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
         mApplyActivityFlagsForBubbles = opts.getBoolean(
                 KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, false);
+        mApplyMultipleTaskFlagForShortcut = opts.getBoolean(
+                KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, false);
         if (opts.containsKey(KEY_ANIM_SPECS)) {
             Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
             mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
@@ -1307,8 +1319,9 @@
         mIsEligibleForLegacyPermissionPrompt =
                 opts.getBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE);
         mDismissKeyguard = opts.getBoolean(KEY_DISMISS_KEYGUARD);
-        mIgnorePendingIntentCreatorForegroundState = opts.getBoolean(
-                KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE);
+        mPendingIntentCreatorBackgroundActivityStartMode = opts.getInt(
+                KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
+                MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
         mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
     }
 
@@ -1903,6 +1916,16 @@
         return mApplyActivityFlagsForBubbles;
     }
 
+    /** @hide */
+    public void setApplyMultipleTaskFlagForShortcut(boolean apply) {
+        mApplyMultipleTaskFlagForShortcut = apply;
+    }
+
+    /** @hide */
+    public boolean isApplyMultipleTaskFlagForShortcut() {
+        return mApplyMultipleTaskFlagForShortcut;
+    }
+
     /**
      * Sets a launch cookie that can be used to track the activity and task that are launch as a
      * result of this option. If the launched activity is a trampoline that starts another activity
@@ -2009,19 +2032,38 @@
      * Sets background activity launch logic won't use pending intent creator foreground state.
      *
      * @hide
+     * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead
      */
-    public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean state) {
-        mIgnorePendingIntentCreatorForegroundState = state;
+    @Deprecated
+    public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) {
+        mPendingIntentCreatorBackgroundActivityStartMode = ignore
+                ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
         return this;
     }
 
     /**
-     * @return whether background activity launch logic should use pending intent creator
-     * foreground state.
-     * @hide
+     * Allow a {@link PendingIntent} to use the privilege of its creator to start background
+     * activities.
+     *
+     * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
+     * @throws IllegalArgumentException is the value is not a valid
+     * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
      */
-    public boolean getIgnorePendingIntentCreatorForegroundState() {
-        return mIgnorePendingIntentCreatorForegroundState;
+    @NonNull
+    public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
+            @BackgroundActivityStartMode int mode) {
+        mPendingIntentCreatorBackgroundActivityStartMode = mode;
+        return this;
+    }
+
+    /**
+     * Returns the mode to start background activities granted by the creator of the
+     * {@link PendingIntent}.
+     *
+     * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+     */
+    public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
+        return mPendingIntentCreatorBackgroundActivityStartMode;
     }
 
     /**
@@ -2240,6 +2282,10 @@
         if (mApplyActivityFlagsForBubbles) {
             b.putBoolean(KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, mApplyActivityFlagsForBubbles);
         }
+        if (mApplyMultipleTaskFlagForShortcut) {
+            b.putBoolean(KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT,
+                    mApplyMultipleTaskFlagForShortcut);
+        }
         if (mAnimSpecs != null) {
             b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
         }
@@ -2295,9 +2341,10 @@
         if (mDismissKeyguard) {
             b.putBoolean(KEY_DISMISS_KEYGUARD, mDismissKeyguard);
         }
-        if (mIgnorePendingIntentCreatorForegroundState) {
-            b.putBoolean(KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE,
-                    mIgnorePendingIntentCreatorForegroundState);
+        if (mPendingIntentCreatorBackgroundActivityStartMode
+                != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+            b.putInt(KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
+                    mPendingIntentCreatorBackgroundActivityStartMode);
         }
         if (mDisableStartingWindow) {
             b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 73f34eb..7d40a22 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1432,14 +1432,14 @@
                     .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
 
     /**
-     * Hide foreground service stop button in quick settings.
+     * Allows an application to start an activity while running in the background.
      *
      * Only to be used by the system.
      *
      * @hide
      */
-    public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
+    public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
 
     /**
      * Allows an application to capture bugreport directly without consent dialog when using the
@@ -2027,14 +2027,14 @@
             "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction";
 
     /**
-     * Hide foreground service stop button in quick settings.
+     * Allows an application to start an activity while running in the background.
      *
      * Only to be used by the system.
      *
      * @hide
      */
-    public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
-            "android:system_exempt_from_fgs_stop_button";
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION =
+            "android:system_exempt_from_activity_bg_start_restriction";
 
     /**
      * Allows an application to capture bugreport directly without consent dialog when using the
@@ -2562,9 +2562,9 @@
                 OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
                 "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION")
                 .build(),
-        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
-                OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
-                "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+                OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+                "SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION").build(),
         new AppOpInfo.Builder(
                 OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
                 OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 20d19c1..5ef29e4 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -388,7 +388,6 @@
                 new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM),
                 new RegularPermission(Manifest.permission.USE_EXACT_ALARM),
                 new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
-                new AppOpPermission(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
             }, false)
     );
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d8ec7cc..b494a20 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -39,6 +39,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.StringDef;
+import android.annotation.SupportsCoexistence;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -8596,6 +8597,7 @@
      * primary user, or a profile owner of an organization-owned managed profile or a holder of the
      * permission {@link android.Manifest.permission#SET_TIME_ZONE}.
      */
+    @SupportsCoexistence
     @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
     public void setAutoTimeZoneEnabled(@NonNull ComponentName admin, boolean enabled) {
         throwIfParentInstance("setAutoTimeZone");
@@ -9665,6 +9667,7 @@
      * @param activity The Activity that is added as default intent handler.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
+    @SupportsCoexistence
     public void addPersistentPreferredActivity(@NonNull ComponentName admin, IntentFilter filter,
             @NonNull ComponentName activity) {
         throwIfParentInstance("addPersistentPreferredActivity");
@@ -10624,6 +10627,7 @@
      *                           profile owner of an organization-owned managed profile and the
      *                           list of permitted input method package names is not null or empty.
      */
+    @SupportsCoexistence
     public boolean setPermittedInputMethods(
             @NonNull ComponentName admin, List<String> packageNames) {
         if (mService != null) {
@@ -11696,6 +11700,7 @@
      * @see DeviceAdminReceiver#onLockTaskModeExiting(Context, Intent)
      * @see UserManager#DISALLOW_CREATE_WINDOWS
      */
+    @SupportsCoexistence
     public void setLockTaskPackages(@NonNull ComponentName admin, @NonNull String[] packages)
             throws SecurityException {
         throwIfParentInstance("setLockTaskPackages");
@@ -11764,6 +11769,7 @@
      * affiliated user or profile, or the profile owner when no device owner is set.
      * @see #isAffiliatedUser
      **/
+    @SupportsCoexistence
     public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
         throwIfParentInstance("setLockTaskFeatures");
         if (mService != null) {
@@ -12267,6 +12273,7 @@
      * @see #setDelegatedScopes
      * @see #DELEGATION_BLOCK_UNINSTALL
      */
+    @SupportsCoexistence
     public void setUninstallBlocked(@Nullable ComponentName admin, String packageName,
             boolean uninstallBlocked) {
         throwIfParentInstance("setUninstallBlocked");
@@ -12755,6 +12762,7 @@
      * @see #setDelegatedScopes
      * @see #DELEGATION_PERMISSION_GRANT
      */
+    @SupportsCoexistence
     public boolean setPermissionGrantState(@NonNull ComponentName admin,
             @NonNull String packageName, @NonNull String permission,
             @PermissionGrantState int grantState) {
@@ -15301,6 +15309,7 @@
      * @param packages The package names for the apps.
      * @throws SecurityException if {@code admin} is not a device owner or a profile owner.
      */
+    @SupportsCoexistence
     public void setUserControlDisabledPackages(@NonNull ComponentName admin,
             @NonNull List<String> packages) {
         throwIfParentInstance("setUserControlDisabledPackages");
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 6e49e95..b7ec7b5 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -545,6 +545,7 @@
         int resolveViewAutofillFlags(Context context, int fillRequestFlags) {
             return (fillRequestFlags & FillRequest.FLAG_MANUAL_REQUEST) != 0
                         || context.isAutofillCompatibilityEnabled()
+                        || (fillRequestFlags & FillRequest.FLAG_PCC_DETECTION) != 0
                     ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
         }
 
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 335975b..4515663 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1107,7 +1107,7 @@
         } else {
             sb.append("?mcc");
         }
-        if (mnc != 0) {
+        if (mnc != MNC_ZERO) {
             sb.append(mnc);
             sb.append("mnc");
         } else {
diff --git a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
index 8587348..7844b40 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
@@ -39,5 +39,15 @@
      *        {@link android.view.Display#getDisplayId()}.
      */
     void onRequestDisabled(int displayId);
+
+    /**
+     * To avoid delay in switching refresh rate when activating LHBM, allow screens to request
+     * higher refersh rate if auth is possible on particular screen
+     *
+     * @param displayId The displayId for which the refresh rate should be unset. See
+     *        {@link android.view.Display#getDisplayId()}.
+     * @param isPossible If authentication is possible on particualr screen
+     */
+    void onAuthenticationPossible(int displayId, boolean isPossible);
 }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0efd264..1df45d1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1069,11 +1069,6 @@
      * Specifies that windows besides app windows should not be
      * created. This will block the creation of the following types of windows.
      * <li>{@link LayoutParams#TYPE_TOAST}</li>
-     * <li>{@link LayoutParams#TYPE_PHONE}</li>
-     * <li>{@link LayoutParams#TYPE_PRIORITY_PHONE}</li>
-     * <li>{@link LayoutParams#TYPE_SYSTEM_ALERT}</li>
-     * <li>{@link LayoutParams#TYPE_SYSTEM_ERROR}</li>
-     * <li>{@link LayoutParams#TYPE_SYSTEM_OVERLAY}</li>
      * <li>{@link LayoutParams#TYPE_APPLICATION_OVERLAY}</li>
      *
      * <p>This can only be set by device owners and profile owners on the primary user.
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index c80c57c..e5b76f6 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -1,7 +1,9 @@
 # Bug component: 95221
 
+# Please assign new bugs to android-storage-triage@, not to individual people
+
 # Android Storage Team
-abkaur@google.com
+alukin@google.com
 corinac@google.com
 dipankarb@google.com
 krishang@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1c032ee..b29efab 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11468,6 +11468,13 @@
                 "extra_automatic_power_save_mode";
 
         /**
+         * Whether lockscreen weather is enabled.
+         *
+         * @hide
+         */
+        public static final String LOCK_SCREEN_WEATHER_ENABLED = "lockscreen_weather_enabled";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 0f7c9b6..eb5e893 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -117,6 +117,12 @@
      */
     public static final @RequestFlags int FLAG_RESET_FILL_DIALOG_STATE = 0x100;
 
+    /**
+     * Indicate the fill request is made for PCC detection
+     * @hide
+     */
+    public static final @RequestFlags int FLAG_PCC_DETECTION = 0x200;
+
     /** @hide */
     public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
 
@@ -200,7 +206,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/FillRequest.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -215,7 +221,8 @@
         FLAG_VIEW_NOT_FOCUSED,
         FLAG_SUPPORTS_FILL_DIALOG,
         FLAG_IME_SHOWING,
-        FLAG_RESET_FILL_DIALOG_STATE
+        FLAG_RESET_FILL_DIALOG_STATE,
+        FLAG_PCC_DETECTION
     })
     @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
@@ -245,6 +252,8 @@
                     return "FLAG_IME_SHOWING";
             case FLAG_RESET_FILL_DIALOG_STATE:
                     return "FLAG_RESET_FILL_DIALOG_STATE";
+            case FLAG_PCC_DETECTION:
+                    return "FLAG_PCC_DETECTION";
             default: return Integer.toHexString(value);
         }
     }
@@ -322,7 +331,8 @@
                         | FLAG_VIEW_NOT_FOCUSED
                         | FLAG_SUPPORTS_FILL_DIALOG
                         | FLAG_IME_SHOWING
-                        | FLAG_RESET_FILL_DIALOG_STATE);
+                        | FLAG_RESET_FILL_DIALOG_STATE
+                        | FLAG_PCC_DETECTION);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
         this.mDelayedFillIntentSender = delayedFillIntentSender;
 
@@ -463,7 +473,7 @@
         byte flg = in.readByte();
         int id = in.readInt();
         List<FillContext> fillContexts = new ArrayList<>();
-        in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
         Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
         int flags = in.readInt();
         InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
@@ -484,7 +494,8 @@
                         | FLAG_VIEW_NOT_FOCUSED
                         | FLAG_SUPPORTS_FILL_DIALOG
                         | FLAG_IME_SHOWING
-                        | FLAG_RESET_FILL_DIALOG_STATE);
+                        | FLAG_RESET_FILL_DIALOG_STATE
+                        | FLAG_PCC_DETECTION);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
         this.mDelayedFillIntentSender = delayedFillIntentSender;
 
@@ -506,10 +517,10 @@
     };
 
     @DataClass.Generated(
-            time = 1663290803064L,
+            time = 1675460688829L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 70a7739..ba7d823 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -781,19 +781,6 @@
         }
     }
 
-    public static boolean containsType(@InternalInsetsType int[] types,
-            @InternalInsetsType int type) {
-        if (types == null) {
-            return false;
-        }
-        for (int t : types) {
-            if (t == type) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public void dump(String prefix, PrintWriter pw) {
         final String newPrefix = prefix + "  ";
         pw.println(prefix + "InsetsState");
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 7d1dc76..c8c910d 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -28,6 +28,7 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.InflateException;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -137,16 +138,9 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createAnimationFromXml(context, parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
             if (parser != null) parser.close();
         }
@@ -159,8 +153,9 @@
     }
 
     @UnsupportedAppUsage
-    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
-            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
+    private static Animation createAnimationFromXml(
+            Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs)
+            throws XmlPullParserException, IOException, InflateException {
 
         Animation anim = null;
 
@@ -168,8 +163,8 @@
         int type;
         int depth = parser.getDepth();
 
-        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-               && type != XmlPullParser.END_DOCUMENT) {
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
 
             if (type != XmlPullParser.START_TAG) {
                 continue;
@@ -193,7 +188,7 @@
             } else if (name.equals("extend")) {
                 anim = new ExtendAnimation(c, attrs);
             } else {
-                throw new RuntimeException("Unknown animation name: " + parser.getName());
+                throw new InflateException("Unknown animation name: " + parser.getName());
             }
 
             if (parent != null) {
@@ -220,29 +215,24 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createLayoutAnimationFromXml(context, parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
             if (parser != null) parser.close();
         }
     }
 
-    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
-            XmlPullParser parser) throws XmlPullParserException, IOException {
+    private static LayoutAnimationController createLayoutAnimationFromXml(
+            Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException, InflateException {
 
         return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
     }
 
-    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
-            XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+    private static LayoutAnimationController createLayoutAnimationFromXml(
+            Context c, XmlPullParser parser, AttributeSet attrs)
+            throws XmlPullParserException, IOException, InflateException {
 
         LayoutAnimationController controller = null;
 
@@ -263,7 +253,7 @@
             } else if ("gridLayoutAnimation".equals(name)) {
                 controller = new GridLayoutAnimationController(c, attrs);
             } else {
-                throw new RuntimeException("Unknown layout animation name: " + name);
+                throw new InflateException("Unknown layout animation name: " + name);
             }
         }
 
@@ -342,16 +332,9 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
             if (parser != null) parser.close();
         }
@@ -372,25 +355,20 @@
         try {
             parser = res.getAnimation(id);
             return createInterpolatorFromXml(res, theme, parser);
-        } catch (XmlPullParserException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
-        } catch (IOException ex) {
-            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
-                    Integer.toHexString(id));
-            rnf.initCause(ex);
-            throw rnf;
+        } catch (XmlPullParserException | IOException | InflateException ex) {
+            throw new NotFoundException(
+                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
         } finally {
-            if (parser != null)
+            if (parser != null) {
                 parser.close();
+            }
         }
 
     }
 
-    private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
-            throws XmlPullParserException, IOException {
+    private static Interpolator createInterpolatorFromXml(
+            Resources res, Theme theme, XmlPullParser parser)
+            throws XmlPullParserException, IOException, InflateException {
 
         BaseInterpolator interpolator = null;
 
@@ -430,7 +408,7 @@
             } else if (name.equals("pathInterpolator")) {
                 interpolator = new PathInterpolator(res, theme, attrs);
             } else {
-                throw new RuntimeException("Unknown interpolator name: " + parser.getName());
+                throw new InflateException("Unknown interpolator name: " + parser.getName());
             }
         }
         return interpolator;
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 681bc7a..1eecb41 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -202,12 +202,12 @@
                     ri.nonLocalizedLabel = li.getNonLocalizedLabel();
                     ri.icon = li.getIconResource();
                     ri.iconResourceId = ri.icon;
-                    ri.userHandle = mInitialIntentsUserSpace;
                 }
                 if (userManager.isManagedProfile()) {
                     ri.noResourceId = true;
                     ri.icon = 0;
                 }
+                ri.userHandle = mInitialIntentsUserSpace;
                 mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri)));
                 if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
             }
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 0ea60a7..18c8eb4 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -447,13 +447,13 @@
                         ri.nonLocalizedLabel = li.getNonLocalizedLabel();
                         ri.icon = li.getIconResource();
                         ri.iconResourceId = ri.icon;
-                        ri.userHandle = mInitialIntentsUserSpace;
                     }
                     if (userManager.isManagedProfile()) {
                         ri.noResourceId = true;
                         ri.icon = 0;
                     }
 
+                    ri.userHandle = mInitialIntentsUserSpace;
                     addResolveInfo(new DisplayResolveInfo(ii, ri,
                             ri.loadLabel(mPm), null, ii, makePresentationGetter(ri)));
                 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1c85ca2..b529a10 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -214,11 +214,11 @@
      * bar and navigation bar which are temporarily visible to the user.
      *
      * @param displayId the ID of the display to notify.
-     * @param types the internal insets types of the bars are about to show transiently.
+     * @param types the insets types of the bars are about to show transiently.
      * @param isGestureOnSystemBar whether the gesture to show the transient bar was a gesture on
      *        one of the bars itself.
      */
-    void showTransient(int displayId, in int[] types, boolean isGestureOnSystemBar);
+    void showTransient(int displayId, int types, boolean isGestureOnSystemBar);
 
     /**
      * Notifies System UI to abort the transient state of system bars, which prevents the bars being
@@ -226,9 +226,9 @@
      * bars again.
      *
      * @param displayId the ID of the display to notify.
-     * @param types the internal insets types of the bars are about to abort the transient state.
+     * @param types the insets types of the bars are about to abort the transient state.
      */
-    void abortTransient(int displayId, in int[] types);
+    void abortTransient(int displayId, int types);
 
     /**
      * Show a warning that the device is about to go to sleep due to user inactivity.
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 54221ce..4f827cd 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.statusbar;
 
-import android.annotation.NonNull;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -41,15 +40,14 @@
     public final int mBehavior;
     public final int mRequestedVisibleTypes;
     public final String mPackageName;
-    public final int[] mTransientBarTypes;
+    public final int mTransientBarTypes;
     public final LetterboxDetails[] mLetterboxDetails;
 
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
             int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
             int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
             boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
-            String packageName, @NonNull int[] transientBarTypes,
-            LetterboxDetails[] letterboxDetails) {
+            String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) {
         mIcons = new ArrayMap<>(icons);
         mDisabledFlags1 = disabledFlags1;
         mAppearance = appearance;
@@ -87,7 +85,7 @@
         dest.writeInt(mBehavior);
         dest.writeInt(mRequestedVisibleTypes);
         dest.writeString(mPackageName);
-        dest.writeIntArray(mTransientBarTypes);
+        dest.writeInt(mTransientBarTypes);
         dest.writeParcelableArray(mLetterboxDetails, flags);
     }
 
@@ -113,7 +111,7 @@
                     final int behavior = source.readInt();
                     final int requestedVisibleTypes = source.readInt();
                     final String packageName = source.readString();
-                    final int[] transientBarTypes = source.createIntArray();
+                    final int transientBarTypes = source.readInt();
                     final LetterboxDetails[] letterboxDetails =
                             source.readParcelableArray(null, LetterboxDetails.class);
                     return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index e56c381..5c3759f 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,10 +1,7 @@
 package com.android.internal.widget;
 
-import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
-
 import android.annotation.NonNull;
 import android.os.AsyncTask;
-import android.provider.DeviceConfig;
 
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
 
@@ -120,8 +117,7 @@
             @Override
             protected void onPostExecute(Boolean result) {
                 callback.onChecked(result, mThrottleTimeout);
-                if (DeviceConfig.getBoolean(NAMESPACE_AUTO_PIN_CONFIRMATION,
-                        "enable_auto_pin_confirmation", false)) {
+                if (LockPatternUtils.isAutoPinConfirmFeatureAvailable()) {
                     utils.setPinLength(userId, credentialCopy.size());
                 }
                 credentialCopy.zeroize();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 2f51479..4d820ac 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -681,7 +681,7 @@
      * @return true, if deviceConfig flag is set to true or the flag is not propagated and
      * defaultValue is true.
      */
-    public boolean isAutoPinConfirmFeatureAvailable() {
+    public static boolean isAutoPinConfirmFeatureAvailable() {
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION,
                 FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 92a9e56..82de7b8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2193,6 +2193,9 @@
     <!-- The name of the package that will hold the system financed device controller role. -->
     <string name="config_systemFinancedDeviceController" translatable="false">com.android.devicelockcontroller</string>
 
+    <!-- The component name of the wear service class that will be started by the system server. -->
+    <string name="config_wearServiceComponent" translatable="false"></string>
+
     <!-- The name of the package that will handle updating the device management role. -->
     <string name="config_devicePolicyManagementUpdater" translatable="false"></string>
 
@@ -6207,6 +6210,18 @@
          different from the home screen wallpaper. -->
     <bool name="config_independentLockscreenLiveWallpaper">false</bool>
 
+    <!-- Device state that corresponds to concurrent display mode where the default display
+         is the internal display. Public API for the feature is provided through Jetpack
+         WindowManager.
+         TODO(b/236022708) Move concurrent display state to device state config file
+    -->
+    <integer name="config_deviceStateConcurrentRearDisplay">-1</integer>
+
+    <!-- Physical display address that corresponds to the rear display in rear display mode
+         and concurrent display mode. Used to get information about the display before
+         entering the corresponding modes -->
+    <string name="config_rearDisplayPhysicalAddress" translatable="false"></string>
+
     <!-- List of certificate to be used for font fs-verity integrity verification -->
     <string-array translatable="false" name="config_fontManagerServiceCerts">
     </string-array>
@@ -6276,4 +6291,7 @@
         "stopped" during initial boot of a device, or after an OTA update. Stopped state of
         an app is not changed during subsequent reboots.  -->
     <bool name="config_stopSystemPackagesByDefault">false</bool>
+
+    <!-- Whether to show weather on the lock screen by default. -->
+    <bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f460219a..aeb46cc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4907,6 +4907,8 @@
   <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
   <java-symbol type="string" name="device_state_notification_turn_off_button"/>
   <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
+  <java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" />
+  <java-symbol type="string" name="config_rearDisplayPhysicalAddress" />
 
   <!-- For app language picker -->
   <java-symbol type="string" name="system_locale_title" />
@@ -4926,4 +4928,8 @@
   <java-symbol type="array" name="config_displayShapeArray" />
 
   <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/>
+  <java-symbol type="string" name="config_wearServiceComponent" />
+
+  <!-- Whether to show weather on the lockscreen by default. -->
+  <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/admin/PackagePolicyTest.java b/core/tests/coretests/src/android/app/admin/PackagePolicyTest.java
new file mode 100644
index 0000000..d8298fd
--- /dev/null
+++ b/core/tests/coretests/src/android/app/admin/PackagePolicyTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST;
+import static android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM;
+import static android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PackagePolicyTest {
+
+    private static final String TEST_PACKAGE_NAME = "com.example";
+
+    private static final String TEST_PACKAGE_NAME_2 = "com.example.2";
+
+    private static final String TEST_SYSTEM_PACKAGE_NAME = "com.system";
+
+
+    @Test
+    public void testParceling() {
+        final int policyType = PACKAGE_POLICY_BLOCKLIST;
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+
+        final Parcel parcel = Parcel.obtain();
+        PackagePolicy packagePolicy = new PackagePolicy(policyType, packageNames);
+        try {
+            packagePolicy.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            packagePolicy = PackagePolicy.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+
+        assertEquals(policyType, packagePolicy.getPolicyType());
+        assertNotNull(packagePolicy.getPackageNames());
+        assertEquals(1, packagePolicy.getPackageNames().size());
+        assertTrue(packagePolicy.getPackageNames().contains(TEST_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testEmptyBlocklistCreation() {
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_BLOCKLIST);
+        assertEquals(PACKAGE_POLICY_BLOCKLIST, policy.getPolicyType());
+        assertNotNull(policy.getPackageNames());
+        assertTrue(policy.getPackageNames().isEmpty());
+    }
+
+    @Test
+    public void testEmptyAllowlistCreation() {
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST);
+        assertEquals(PACKAGE_POLICY_ALLOWLIST, policy.getPolicyType());
+        assertNotNull(policy.getPackageNames());
+        assertTrue(policy.getPackageNames().isEmpty());
+    }
+
+    @Test
+    public void testEmptyAllowlistAndSystemCreation() {
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM);
+        assertEquals(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, policy.getPolicyType());
+        assertNotNull(policy.getPackageNames());
+        assertTrue(policy.getPackageNames().isEmpty());
+    }
+
+    @Test
+    public void testSuppliedBlocklistCreation() {
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_BLOCKLIST, packageNames);
+        assertEquals(PACKAGE_POLICY_BLOCKLIST, policy.getPolicyType());
+        assertNotNull(policy.getPackageNames());
+        assertEquals(1, policy.getPackageNames().size());
+        assertTrue(policy.getPackageNames().contains(TEST_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testSuppliedAllowlistCreation() {
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST, packageNames);
+        assertEquals(PACKAGE_POLICY_ALLOWLIST, policy.getPolicyType());
+        assertNotNull(policy.getPackageNames());
+        assertEquals(1, policy.getPackageNames().size());
+        assertTrue(policy.getPackageNames().contains(TEST_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testSuppliedAllowlistAndSystemCreation() {
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, packageNames);
+        assertEquals(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, policy.getPolicyType());
+        assertNotNull(policy.getPackageNames());
+        assertEquals(1, policy.getPackageNames().size());
+        assertTrue(policy.getPackageNames().contains(TEST_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testBlocklist_isPackageAllowed() {
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+        final Set<String> systemPackages = new ArraySet<>();
+        systemPackages.add(TEST_SYSTEM_PACKAGE_NAME);
+
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_BLOCKLIST, packageNames);
+
+        assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME, Collections.emptySet()));
+        assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME, systemPackages));
+        assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, Collections.emptySet()));
+        assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, systemPackages));
+        assertTrue(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, Collections.emptySet()));
+        assertTrue(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, systemPackages));
+    }
+
+    @Test
+    public void testAllowlist_isPackageAllowed() {
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+        final Set<String> systemPackages = new ArraySet<>();
+        systemPackages.add(TEST_SYSTEM_PACKAGE_NAME);
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST, packageNames);
+
+        assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, Collections.emptySet()));
+        assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, systemPackages));
+        assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, Collections.emptySet()));
+        assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, systemPackages));
+        assertFalse(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, Collections.emptySet()));
+        assertFalse(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, systemPackages));
+    }
+
+    @Test
+    public void testAllowlistAndSystem_isPackageAllowed() {
+        final Set<String> packageNames = new ArraySet<>();
+        packageNames.add(TEST_PACKAGE_NAME);
+        final Set<String> systemPackages = new ArraySet<>();
+        systemPackages.add(TEST_SYSTEM_PACKAGE_NAME);
+        PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, packageNames);
+
+        assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, Collections.emptySet()));
+        assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, systemPackages));
+        assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, Collections.emptySet()));
+        assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, systemPackages));
+        assertFalse(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, Collections.emptySet()));
+        assertTrue(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, systemPackages));
+    }
+
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 048c48b..f79ba28 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -67,7 +67,7 @@
                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
                 WindowInsets.Type.defaultVisible(),
                 "test" /* packageName */,
-                new int[0] /* transientBarTypes */,
+                0 /* transientBarTypes */,
                 new LetterboxDetails[] {letterboxDetails});
 
         final RegisterStatusBarResult copy = clone(original);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f2d6250..53f4747 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -61,6 +61,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/WindowToken.java"
     },
+    "-2088209279": {
+      "message": "Notified TransitionController that the display is ready.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-2072089308": {
       "message": "Attempted to add window with token that is a sub-window: %s.  Aborting.",
       "level": "WARN",
@@ -2767,6 +2773,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "378890013": {
+      "message": "Apply and finish immediately because player is disabled for transition #%d .",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "385237117": {
       "message": "moveFocusableActivityToTop: already on top and focused, activity=%s",
       "level": "DEBUG",
@@ -3649,6 +3661,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1282992082": {
+      "message": "Disabling player for transition #%d because display isn't enabled yet",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "1284122013": {
       "message": "TaskFragment appeared name=%s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
new file mode 100644
index 0000000..1ff1694
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.view.Display;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Consumer;
+
+/**
+ * {@link Presentation} object that is used to present extra content
+ * on the rear facing display when in a rear display presentation feature.
+ */
+class RearDisplayPresentation extends Presentation implements ExtensionWindowAreaPresentation {
+
+    @NonNull
+    private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer;
+
+    RearDisplayPresentation(@NonNull Context outerContext, @NonNull Display display,
+            @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) {
+        super(outerContext, display);
+        mStateConsumer = stateConsumer;
+    }
+
+    /**
+     * {@code mStateConsumer} is notified that their content is now visible when the
+     * {@link Presentation} object is started. There is no comparable callback for
+     * {@link WindowAreaComponent#SESSION_STATE_INVISIBLE} in {@link #onStop()} due to the
+     * timing of when a {@link android.hardware.devicestate.DeviceStateRequest} is cancelled
+     * ending rear display presentation mode happening before the {@link Presentation} is stopped.
+     */
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mStateConsumer.accept(WindowAreaComponent.SESSION_STATE_VISIBLE);
+    }
+
+    @NonNull
+    @Override
+    public Context getPresentationContext() {
+        return getContext();
+    }
+
+    @Override
+    public void setPresentationView(View view) {
+        setContentView(view);
+        if (!isShowing()) {
+            show();
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java
new file mode 100644
index 0000000..141a6ad
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_ACTIVE;
+import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_INACTIVE;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Consumer;
+
+import java.util.Objects;
+
+/**
+ * Controller class that keeps track of the status of the device state request
+ * to enable the rear display presentation feature. This controller notifies the session callback
+ * when the state request is active, and notifies the callback when the request is canceled.
+ *
+ * Clients are notified via {@link Consumer} provided with
+ * {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} values to signify
+ * when the request becomes active and cancelled.
+ */
+class RearDisplayPresentationController implements DeviceStateRequest.Callback {
+
+    private static final String TAG = "RearDisplayPresentationController";
+
+    // Original context that requested to enable rear display presentation mode
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer;
+    @Nullable
+    private ExtensionWindowAreaPresentation mExtensionWindowAreaPresentation;
+    @NonNull
+    private final DisplayManager mDisplayManager;
+
+    /**
+     * Creates the RearDisplayPresentationController
+     * @param context Originating {@link android.content.Context} that is initiating the rear
+     *                display presentation session.
+     * @param stateConsumer {@link Consumer} that will be notified that the session is active when
+     *        the device state request is active and the session has been created. If the device
+     *        state request is cancelled, the callback will be notified that the session has been
+     *        ended. This could occur through a call to cancel the feature or if the device is
+     *        manipulated in a way that cancels any device state override.
+     */
+    RearDisplayPresentationController(@NonNull Context context,
+            @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(stateConsumer);
+
+        mContext = context;
+        mStateConsumer = stateConsumer;
+        mDisplayManager = context.getSystemService(DisplayManager.class);
+    }
+
+    @Override
+    public void onRequestActivated(@NonNull DeviceStateRequest request) {
+        Display[] rearDisplays = mDisplayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR);
+        if (rearDisplays.length == 0) {
+            mStateConsumer.accept(SESSION_STATE_INACTIVE);
+            Log.e(TAG, "Rear display list should not be empty");
+            return;
+        }
+
+        mExtensionWindowAreaPresentation =
+                new RearDisplayPresentation(mContext, rearDisplays[0], mStateConsumer);
+        mStateConsumer.accept(SESSION_STATE_ACTIVE);
+    }
+
+    @Override
+    public void onRequestCanceled(@NonNull DeviceStateRequest request) {
+        mStateConsumer.accept(SESSION_STATE_INACTIVE);
+    }
+
+    @Nullable
+    public ExtensionWindowAreaPresentation getWindowAreaPresentation() {
+        return mExtensionWindowAreaPresentation;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java
new file mode 100644
index 0000000..0b1423a
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import android.util.DisplayMetrics;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Class that provides information around the current status of a window area feature. Contains
+ * the current {@link WindowAreaComponent.WindowAreaStatus} value corresponding to the
+ * rear display presentation feature, as well as the {@link DisplayMetrics} for the rear facing
+ * display.
+ */
+class RearDisplayPresentationStatus implements ExtensionWindowAreaStatus {
+
+    @WindowAreaComponent.WindowAreaStatus
+    private final int mWindowAreaStatus;
+
+    @NonNull
+    private final DisplayMetrics mDisplayMetrics;
+
+    RearDisplayPresentationStatus(@WindowAreaComponent.WindowAreaStatus int status,
+            @NonNull DisplayMetrics displayMetrics) {
+        mWindowAreaStatus = status;
+        mDisplayMetrics = displayMetrics;
+    }
+
+    /**
+     * Returns the {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus}
+     * value that relates to the current status of a feature.
+     */
+    @Override
+    @WindowAreaComponent.WindowAreaStatus
+    public int getWindowAreaStatus() {
+        return mWindowAreaStatus;
+    }
+
+    /**
+     * Returns the {@link DisplayMetrics} that corresponds to the window area that a feature
+     * interacts with. This is converted to size class information provided to developers.
+     */
+    @Override
+    @NonNull
+    public DisplayMetrics getWindowAreaDisplayMetrics() {
+        return mDisplayMetrics;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 20602a1..274dcae 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -22,7 +22,12 @@
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
+import android.hardware.display.DisplayManager;
 import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayAddress;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -30,6 +35,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.concurrent.Executor;
 
@@ -47,61 +53,102 @@
 
     private final Object mLock = new Object();
 
+    @NonNull
     private final DeviceStateManager mDeviceStateManager;
+    @NonNull
+    private final DisplayManager mDisplayManager;
+    @NonNull
     private final Executor mExecutor;
 
     @GuardedBy("mLock")
     private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
+    @GuardedBy("mLock")
+    private final ArraySet<Consumer<ExtensionWindowAreaStatus>>
+            mRearDisplayPresentationStatusListeners = new ArraySet<>();
     private final int mRearDisplayState;
+    private final int mConcurrentDisplayState;
+    @NonNull
+    private final int[] mFoldedDeviceStates;
+    @NonNull
+    private long mRearDisplayAddress = 0;
     @WindowAreaSessionState
     private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
 
     @GuardedBy("mLock")
     private int mCurrentDeviceState = INVALID_DEVICE_STATE;
     @GuardedBy("mLock")
-    private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
+    private int[] mCurrentSupportedDeviceStates;
+
     @GuardedBy("mLock")
-    private DeviceStateRequest mDeviceStateRequest;
+    private DeviceStateRequest mRearDisplayStateRequest;
+    @GuardedBy("mLock")
+    private RearDisplayPresentationController mRearDisplayPresentationController;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private DisplayMetrics mRearDisplayMetrics;
+
+    @WindowAreaSessionState
+    @GuardedBy("mLock")
+    private int mLastReportedRearDisplayPresentationStatus;
 
     public WindowAreaComponentImpl(@NonNull Context context) {
         mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+        mDisplayManager = context.getSystemService(DisplayManager.class);
         mExecutor = context.getMainExecutor();
 
+        mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
+        mFoldedDeviceStates = context.getResources().getIntArray(
+                R.array.config_foldedDeviceStates);
+
         // TODO(b/236022708) Move rear display state to device state config file
         mRearDisplayState = context.getResources().getInteger(
                 R.integer.config_deviceStateRearDisplay);
 
+        mConcurrentDisplayState = context.getResources().getInteger(
+                R.integer.config_deviceStateConcurrentRearDisplay);
+
         mDeviceStateManager.registerCallback(mExecutor, this);
+        if (mConcurrentDisplayState != INVALID_DEVICE_STATE) {
+            mRearDisplayAddress = Long.parseLong(context.getResources().getString(
+                    R.string.config_rearDisplayPhysicalAddress));
+        }
     }
 
     /**
      * Adds a listener interested in receiving updates on the RearDisplayStatus
      * of the device. Because this is being called from the OEM provided
-     * extensions, we will post the result of the listener on the executor
+     * extensions, the result of the listener will be posted on the executor
      * provided by the developer at the initial call site.
      *
-     * Depending on the initial state of the device, we will return either
+     * Rear display mode moves the calling application to the display on the device that is
+     * facing the same direction as the rear cameras. This would be the cover display on a fold-in
+     * style device when the device is opened.
+     *
+     * Depending on the initial state of the device, the {@link Consumer} will receive either
      * {@link WindowAreaComponent#STATUS_AVAILABLE} or
      * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
-     * state respectively. When the rear display feature is triggered, we update the status to be
-     * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
+     * state respectively. When the rear display feature is triggered, the status is updated to be
+     * {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
+     * TODO(b/240727590): Prefix with AREA_
      *
-     * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
-     * enabled.
+     * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently
+     *  enabled.
      *
      * @param consumer {@link Consumer} interested in receiving updates to the status of
      * rear display mode.
      */
+    @Override
     public void addRearDisplayStatusListener(
             @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
         synchronized (mLock) {
             mRearDisplayStatusListeners.add(consumer);
 
-            // If current device state is still invalid, we haven't gotten our initial value yet
+            // If current device state is still invalid, the initial value has not been provided.
             if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
                 return;
             }
-            consumer.accept(getCurrentStatus());
+            consumer.accept(getCurrentRearDisplayModeStatus());
         }
     }
 
@@ -109,6 +156,7 @@
      * Removes a listener no longer interested in receiving updates.
      * @param consumer no longer interested in receiving updates to RearDisplayStatus
      */
+    @Override
     public void removeRearDisplayStatusListener(
             @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
         synchronized (mLock) {
@@ -119,13 +167,17 @@
     /**
      * Creates and starts a rear display session and provides updates to the
      * callback provided. Because this is being called from the OEM provided
-     * extensions, we will post the result of the listener on the executor
+     * extensions, the result of the listener will be posted on the executor
      * provided by the developer at the initial call site.
      *
-     * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
+     * Rear display mode moves the calling application to the display on the device that is
+     * facing the same direction as the rear cameras. This would be the cover display on a fold-in
+     * style device when the device is opened.
+     *
+     * When rear display mode is enabled, a request is made to {@link DeviceStateManager}
      * to override the device state to the state that corresponds to RearDisplay
-     * mode. When the {@link DeviceStateRequest} is activated, we let the
-     * consumer know that the session is active by sending
+     * mode. When the {@link DeviceStateRequest} is activated, the provided {@link Consumer} is
+     * notified that the session is active by receiving
      * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
      *
      * @param activity to provide updates to the client on
@@ -133,19 +185,20 @@
      * @param rearDisplaySessionCallback to provide updates to the client on
      * the status of the Session
      */
+    @Override
     public void startRearDisplaySession(@NonNull Activity activity,
             @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
         synchronized (mLock) {
-            if (mDeviceStateRequest != null) {
+            if (mRearDisplayStateRequest != null) {
                 // Rear display session is already active
                 throw new IllegalStateException(
                         "Unable to start new rear display session as one is already active");
             }
-            mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
+            mRearDisplayStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
             mDeviceStateManager.requestState(
-                    mDeviceStateRequest,
+                    mRearDisplayStateRequest,
                     mExecutor,
-                    new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
+                    new RearDisplayStateRequestCallbackAdapter(rearDisplaySessionCallback)
             );
         }
     }
@@ -153,13 +206,14 @@
     /**
      * Ends the current rear display session and provides updates to the
      * callback provided. Because this is being called from the OEM provided
-     * extensions, we will post the result of the listener on the executor
-     * provided by the developer.
+     * extensions, the result of the listener will be posted on the executor
+     * provided by the developer at the initial call site.
      */
+    @Override
     public void endRearDisplaySession() {
         synchronized (mLock) {
-            if (mDeviceStateRequest != null || isRearDisplayActive()) {
-                mDeviceStateRequest = null;
+            if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
+                mRearDisplayStateRequest = null;
                 mDeviceStateManager.cancelStateRequest();
             } else {
                 throw new IllegalStateException(
@@ -168,13 +222,176 @@
         }
     }
 
+    /**
+     * Adds a listener interested in receiving updates on the RearDisplayPresentationStatus
+     * of the device. Because this is being called from the OEM provided
+     * extensions, the result of the listener will be posted on the executor
+     * provided by the developer at the initial call site.
+     *
+     * Rear display presentation mode is a feature where an {@link Activity} can present
+     * additional content on a device with a second display that is facing the same direction
+     * as the rear camera (i.e. the cover display on a fold-in style device). The calling
+     * {@link Activity} does not move, whereas in rear display mode it does.
+     *
+     * This listener receives a {@link Pair} with the first item being the
+     * {@link WindowAreaComponent.WindowAreaStatus} that corresponds to the current status of the
+     * feature, and the second being the {@link DisplayMetrics} of the display that would be
+     * presented to when the feature is active.
+     *
+     * Depending on the initial state of the device, the {@link Consumer} will receive either
+     * {@link WindowAreaComponent#STATUS_AVAILABLE} or
+     * {@link WindowAreaComponent#STATUS_UNAVAILABLE} for the status value of the {@link Pair} if
+     * the feature is supported or not in that state respectively. Rear display presentation mode is
+     * currently not supported when the device is folded. When the rear display presentation feature
+     * is triggered, the status is updated to be {@link WindowAreaComponent#STATUS_UNAVAILABLE}.
+     * TODO(b/240727590): Prefix with AREA_
+     *
+     * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently
+     *  enabled.
+     *
+     * @param consumer {@link Consumer} interested in receiving updates to the status of
+     * rear display presentation mode.
+     */
     @Override
-    public void onBaseStateChanged(int state) {
+    public void addRearDisplayPresentationStatusListener(
+            @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
         synchronized (mLock) {
-            mCurrentDeviceBaseState = state;
-            if (state == mCurrentDeviceState) {
-                updateStatusConsumers(getCurrentStatus());
+            mRearDisplayPresentationStatusListeners.add(consumer);
+
+            // If current device state is still invalid, the initial value has not been provided
+            if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+                return;
             }
+            @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
+            consumer.accept(
+                    new RearDisplayPresentationStatus(currentStatus, getRearDisplayMetrics()));
+        }
+    }
+
+    /**
+     * Removes a listener no longer interested in receiving updates.
+     * @param consumer no longer interested in receiving updates to RearDisplayPresentationStatus
+     */
+    @Override
+    public void removeRearDisplayPresentationStatusListener(
+            @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
+        synchronized (mLock) {
+            mRearDisplayPresentationStatusListeners.remove(consumer);
+        }
+    }
+
+    /**
+     * Creates and starts a rear display presentation session and sends state updates to the
+     * consumer provided. This consumer will receive a constant represented by
+     * {@link WindowAreaSessionState} to represent the state of the current rear display
+     * session. It will be translated to a more friendly interface in the library.
+     *
+     * Because this is being called from the OEM provided extensions, the library
+     * will post the result of the listener on the executor provided by the developer.
+     *
+     * Rear display presentation mode refers to a feature where an {@link Activity} can present
+     * additional content on a device with a second display that is facing the same direction
+     * as the rear camera (i.e. the cover display on a fold-in style device). The calling
+     * {@link Activity} stays on the user-facing display.
+     *
+     * @param activity that the OEM implementation will use as a base
+     * context and to identify the source display area of the request.
+     * The reference to the activity instance must not be stored in the OEM
+     * implementation to prevent memory leaks.
+     * @param consumer to provide updates to the client on the status of the session
+     * @throws UnsupportedOperationException if this method is called when rear display presentation
+     * mode is not available. This could be to an incompatible device state or when
+     * another process is currently in this mode.
+     */
+    @Override
+    public void startRearDisplayPresentationSession(@NonNull Activity activity,
+            @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {
+        synchronized (mLock) {
+            if (mRearDisplayPresentationController != null) {
+                // Rear display presentation session is already active
+                throw new IllegalStateException(
+                        "Unable to start new rear display presentation session as one is already "
+                                + "active");
+            }
+            if (getCurrentRearDisplayPresentationModeStatus()
+                    != WindowAreaComponent.STATUS_AVAILABLE) {
+                throw new IllegalStateException(
+                        "Unable to start new rear display presentation session as the feature is "
+                                + "is not currently available");
+            }
+
+            mRearDisplayPresentationController = new RearDisplayPresentationController(activity,
+                    stateStatus -> {
+                        synchronized (mLock) {
+                            if (stateStatus == SESSION_STATE_INACTIVE) {
+                                // If the last reported session status was VISIBLE
+                                // then the INVISIBLE state should be dispatched before INACTIVE
+                                // due to not having a good mechanism to know when
+                                // the content is no longer visible before it's fully removed
+                                if (getLastReportedRearDisplayPresentationStatus()
+                                        == SESSION_STATE_VISIBLE) {
+                                    consumer.accept(SESSION_STATE_INVISIBLE);
+                                }
+                                mRearDisplayPresentationController = null;
+                            }
+                            mLastReportedRearDisplayPresentationStatus = stateStatus;
+                            consumer.accept(stateStatus);
+                        }
+                    });
+
+            DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder(
+                    mConcurrentDisplayState).build();
+            mDeviceStateManager.requestState(
+                    concurrentDisplayStateRequest,
+                    mExecutor,
+                    mRearDisplayPresentationController
+            );
+        }
+    }
+
+    /**
+     * Ends the current rear display presentation session and provides updates to the
+     * callback provided. When this is ended, the presented content from the calling
+     * {@link Activity} will also be removed from the rear facing display.
+     * Because this is being called from the OEM provided extensions, the result of the listener
+     * will be posted on the executor provided by the developer at the initial call site.
+     *
+     * Cancelling the {@link DeviceStateRequest} and exiting the rear display presentation state,
+     * will remove the presentation window from the cover display as the cover display is no longer
+     * enabled.
+     */
+    @Override
+    public void endRearDisplayPresentationSession() {
+        synchronized (mLock) {
+            if (mRearDisplayPresentationController != null) {
+                mDeviceStateManager.cancelStateRequest();
+            } else {
+                throw new IllegalStateException(
+                        "Unable to cancel a rear display presentation session as there is no "
+                                + "active session");
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
+        synchronized (mLock) {
+            ExtensionWindowAreaPresentation presentation = null;
+            if (mRearDisplayPresentationController != null) {
+                presentation = mRearDisplayPresentationController.getWindowAreaPresentation();
+            }
+            return presentation;
+        }
+    }
+
+    @Override
+    public void onSupportedStatesChanged(int[] supportedStates) {
+        synchronized (mLock) {
+            mCurrentSupportedDeviceStates = supportedStates;
+            updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
+            updateRearDisplayPresentationStatusListeners(
+                    getCurrentRearDisplayPresentationModeStatus());
         }
     }
 
@@ -182,34 +399,17 @@
     public void onStateChanged(int state) {
         synchronized (mLock) {
             mCurrentDeviceState = state;
-            updateStatusConsumers(getCurrentStatus());
+            updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
+            updateRearDisplayPresentationStatusListeners(
+                    getCurrentRearDisplayPresentationModeStatus());
         }
     }
 
-    @Override
-    public void addRearDisplayPresentationStatusListener(
-            @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
-
-    @Override
-    public void removeRearDisplayPresentationStatusListener(
-            @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
-
-    @Override
-    public void startRearDisplayPresentationSession(@NonNull Activity activity,
-            @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {}
-
-    @Override
-    public void endRearDisplayPresentationSession() {}
-
-    @Override
-    @Nullable
-    public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
-        return null;
-    }
 
     @GuardedBy("mLock")
-    private int getCurrentStatus() {
+    private int getCurrentRearDisplayModeStatus() {
         if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
+                || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)
                 || isRearDisplayActive()) {
             return WindowAreaComponent.STATUS_UNAVAILABLE;
         }
@@ -218,19 +418,20 @@
 
     /**
      * Helper method to determine if a rear display session is currently active by checking
-     * if the current device configuration matches that of rear display. This would be true
-     * if there is a device override currently active (base state != current state) and the current
-     * state is that which corresponds to {@code mRearDisplayState}
-     * @return {@code true} if the device is in rear display mode and {@code false} if not
+     * if the current device state is that which corresponds to {@code mRearDisplayState}.
+     *
+     * @return {@code true} if the device is in rear display state {@code false} if not
      */
     @GuardedBy("mLock")
     private boolean isRearDisplayActive() {
-        return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
-                == mRearDisplayState);
+        return mCurrentDeviceState == mRearDisplayState;
     }
 
     @GuardedBy("mLock")
-    private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
+    private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) {
+        if (mRearDisplayState == INVALID_DEVICE_STATE) {
+            return;
+        }
         synchronized (mLock) {
             for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
                 mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
@@ -238,26 +439,95 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private int getCurrentRearDisplayPresentationModeStatus() {
+        if (mCurrentDeviceState == mConcurrentDisplayState
+                || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
+                || isDeviceFolded()) {
+            return WindowAreaComponent.STATUS_UNAVAILABLE;
+        }
+        return WindowAreaComponent.STATUS_AVAILABLE;
+    }
+
+    @GuardedBy("mLock")
+    private boolean isDeviceFolded() {
+        return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState);
+    }
+
+    @GuardedBy("mLock")
+    private void updateRearDisplayPresentationStatusListeners(
+            @WindowAreaStatus int windowAreaStatus) {
+        if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+            return;
+        }
+        RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus(
+                windowAreaStatus, getRearDisplayMetrics());
+        synchronized (mLock) {
+            for (int i = 0; i < mRearDisplayPresentationStatusListeners.size(); i++) {
+                mRearDisplayPresentationStatusListeners.valueAt(i).accept(consumerValue);
+            }
+        }
+    }
+
+    /**
+     * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing
+     * display was not found in the display list, but we have already computed the
+     * {@link DisplayMetrics} for that display, we return the cached value.
+     *
+     * TODO(b/267563768): Update with guidance from Display team for missing displays.
+     *
+     * @throws IllegalArgumentException if the display is not found and there is no cached
+     * {@link DisplayMetrics} for this display.
+     */
+    @GuardedBy("mLock")
+    private DisplayMetrics getRearDisplayMetrics() {
+        Display[] displays = mDisplayManager.getDisplays(
+                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+        for (int i = 0; i < displays.length; i++) {
+            DisplayAddress.Physical address =
+                    (DisplayAddress.Physical) displays[i].getAddress();
+            if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+                if (mRearDisplayMetrics == null) {
+                    mRearDisplayMetrics = new DisplayMetrics();
+                }
+                displays[i].getRealMetrics(mRearDisplayMetrics);
+                return mRearDisplayMetrics;
+            }
+        }
+        if (mRearDisplayMetrics != null) {
+            return mRearDisplayMetrics;
+        } else {
+            throw new IllegalArgumentException(
+                    "No display found with the provided display address");
+        }
+    }
+
+    @GuardedBy("mLock")
+    @WindowAreaSessionState
+    private int getLastReportedRearDisplayPresentationStatus() {
+        return mLastReportedRearDisplayPresentationStatus;
+    }
+
     /**
      * Callback for the {@link DeviceStateRequest} to be notified of when the request has been
      * activated or cancelled. This callback provides information to the client library
      * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
      */
-    private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
+    private class RearDisplayStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
 
         private final Consumer<Integer> mRearDisplaySessionCallback;
 
-        DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
+        RearDisplayStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
             mRearDisplaySessionCallback = callback;
         }
 
         @Override
         public void onRequestActivated(@NonNull DeviceStateRequest request) {
             synchronized (mLock) {
-                if (request.equals(mDeviceStateRequest)) {
+                if (request.equals(mRearDisplayStateRequest)) {
                     mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
                     mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
-                    updateStatusConsumers(getCurrentStatus());
+                    updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
                 }
             }
         }
@@ -265,12 +535,12 @@
         @Override
         public void onRequestCanceled(DeviceStateRequest request) {
             synchronized (mLock) {
-                if (request.equals(mDeviceStateRequest)) {
-                    mDeviceStateRequest = null;
+                if (request.equals(mRearDisplayStateRequest)) {
+                    mRearDisplayStateRequest = null;
                 }
                 mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
                 mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
-                updateStatusConsumers(getCurrentStatus());
+                updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
             }
         }
     }
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 378ad81..7cd5dd6 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 5d451a5..7a6aec7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -173,7 +173,7 @@
      */
     public void onLocationChanged() {
         getBoundsOnScreen(mTmpRect);
-        mTaskViewTaskController.onLocationChanged(mTmpRect);
+        mTaskViewTaskController.setWindowBounds(mTmpRect);
     }
 
     /**
@@ -198,7 +198,7 @@
     public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format,
             int width, int height) {
         getBoundsOnScreen(mTmpRect);
-        mTaskViewTaskController.surfaceChanged(mTmpRect);
+        mTaskViewTaskController.setWindowBounds(mTmpRect);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
index 69ce35f..1f223a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java
@@ -183,26 +183,6 @@
     }
 
     /**
-     * Call when view position or size has changed. Do not call when animating.
-     */
-    public void onLocationChanged(Rect newBounds) {
-        if (mTaskToken == null) {
-            return;
-        }
-        // Sync Transactions can't operate simultaneously with shell transition collection.
-        // The transition animation (upon showing) will sync the location itself.
-        if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        updateWindowBounds(wct);
-        mSyncQueue.queue(wct);
-    }
-
-    private void updateWindowBounds(WindowContainerTransaction wct) {
-        wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
-    }
-
-    /**
      * Release this container if it is initialized.
      */
     public void release() {
@@ -394,15 +374,24 @@
     }
 
     /**
-     * Should be called when the client surface is changed.
+     * Sets the window bounds to {@code boundsOnScreen}.
+     * Call when view position or size has changed. Can also be called before the animation when
+     * the final bounds are known.
+     * Do not call during the animation.
      *
      * @param boundsOnScreen the on screen bounds of the surface view.
      */
-    public void surfaceChanged(Rect boundsOnScreen) {
+    public void setWindowBounds(Rect boundsOnScreen) {
         if (mTaskToken == null) {
             return;
         }
-        onLocationChanged(boundsOnScreen);
+        // Sync Transactions can't operate simultaneously with shell transition collection.
+        // The transition animation (upon showing) will sync the location itself.
+        if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(mTaskToken, boundsOnScreen);
+        mSyncQueue.queue(wct);
     }
 
     /** Should be called when the client surface is destroyed. */
@@ -493,7 +482,7 @@
                     .setPosition(mTaskLeash, 0, 0)
                     .apply();
 
-            updateWindowBounds(wct);
+            wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
         } else {
             // The surface has already been destroyed before the task has appeared,
             // so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e36dfc3..36c0cb6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1058,6 +1058,11 @@
         }
     }
 
+    /** Sets the app bubble's taskId which is cached for SysUI. */
+    public void setAppBubbleTaskId(int taskId) {
+        mImpl.mCachedState.setAppBubbleTaskId(taskId);
+    }
+
     /**
      * Fills the overflow bubbles by loading them from disk.
      */
@@ -1636,6 +1641,7 @@
             private HashSet<String> mSuppressedBubbleKeys = new HashSet<>();
             private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>();
             private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>();
+            private int mAppBubbleTaskId = INVALID_TASK_ID;
 
             private ArrayList<Bubble> mTmpBubbles = new ArrayList<>();
 
@@ -1667,12 +1673,22 @@
 
                 mSuppressedBubbleKeys.clear();
                 mShortcutIdToBubble.clear();
+                mAppBubbleTaskId = INVALID_TASK_ID;
                 for (Bubble b : mTmpBubbles) {
                     mShortcutIdToBubble.put(b.getShortcutId(), b);
                     updateBubbleSuppressedState(b);
+
+                    if (KEY_APP_BUBBLE.equals(b.getKey())) {
+                        mAppBubbleTaskId = b.getTaskId();
+                    }
                 }
             }
 
+            /** Sets the app bubble's taskId which is cached for SysUI. */
+            synchronized void setAppBubbleTaskId(int taskId) {
+                mAppBubbleTaskId = taskId;
+            }
+
             /**
              * Updates a specific bubble suppressed state.  This is used mainly because notification
              * suppression changes don't go through the same BubbleData update mechanism.
@@ -1722,6 +1738,8 @@
                 for (String key : mSuppressedGroupToNotifKeys.keySet()) {
                     pw.println("   suppressing: " + key);
                 }
+
+                pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId);
             }
         }
 
@@ -1773,8 +1791,7 @@
 
         @Override
         public boolean isAppBubbleTaskId(int taskId) {
-            Bubble appBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
-            return appBubble != null && appBubble.getTaskId() == taskId;
+            return mCachedState.mAppBubbleTaskId == taskId;
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 1feff18..57c7731 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -278,6 +278,11 @@
             // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
             mTaskId = taskId;
 
+            if (Bubble.KEY_APP_BUBBLE.equals(getBubbleKey())) {
+                // Let the controller know sooner what the taskId is.
+                mController.setAppBubbleTaskId(mTaskId);
+            }
+
             // With the task org, the taskAppeared callback will only happen once the task has
             // already drawn
             setContentVisibility(true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 45b234a..f616e6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -699,19 +699,6 @@
         return bounds.width() > bounds.height();
     }
 
-    /** Reverse the split position. */
-    @SplitPosition
-    public static int reversePosition(@SplitPosition int position) {
-        switch (position) {
-            case SPLIT_POSITION_TOP_OR_LEFT:
-                return SPLIT_POSITION_BOTTOM_OR_RIGHT;
-            case SPLIT_POSITION_BOTTOM_OR_RIGHT:
-                return SPLIT_POSITION_TOP_OR_LEFT;
-            default:
-                return SPLIT_POSITION_UNDEFINED;
-        }
-    }
-
     /**
      * Return if this layout is landscape.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
new file mode 100644
index 0000000..042721c9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -0,0 +1,85 @@
+/*
+ * 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.wm.shell.common.split;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/** Helper utility class for split screen components to use. */
+public class SplitScreenUtils {
+    /** Reverse the split position. */
+    @SplitScreenConstants.SplitPosition
+    public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) {
+        switch (position) {
+            case SPLIT_POSITION_TOP_OR_LEFT:
+                return SPLIT_POSITION_BOTTOM_OR_RIGHT;
+            case SPLIT_POSITION_BOTTOM_OR_RIGHT:
+                return SPLIT_POSITION_TOP_OR_LEFT;
+            case SPLIT_POSITION_UNDEFINED:
+            default:
+                return SPLIT_POSITION_UNDEFINED;
+        }
+    }
+
+    /** Returns true if the task is valid for split screen. */
+    public static boolean isValidToSplit(ActivityManager.RunningTaskInfo taskInfo) {
+        return taskInfo != null && taskInfo.supportsMultiWindow
+                && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
+                && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
+    }
+
+    /** Retrieve package name from an intent */
+    @Nullable
+    public static String getPackageName(Intent intent) {
+        if (intent == null || intent.getComponent() == null) {
+            return null;
+        }
+        return intent.getComponent().getPackageName();
+    }
+
+    /** Retrieve package name from a PendingIntent */
+    @Nullable
+    public static String getPackageName(PendingIntent pendingIntent) {
+        if (pendingIntent == null || pendingIntent.getIntent() == null) {
+            return null;
+        }
+        return getPackageName(pendingIntent.getIntent());
+    }
+
+    /** Retrieve package name from a taskId */
+    @Nullable
+    public static String getPackageName(int taskId, ShellTaskOrganizer taskOrganizer) {
+        final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId);
+        return taskInfo != null ? getPackageName(taskInfo.baseIntent) : null;
+    }
+
+    /** Returns true if they are the same package. */
+    public static boolean samePackage(String packageName1, String packageName2) {
+        return packageName1 != null && packageName1.equals(packageName2);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 6728c00..d9ac76e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -28,6 +28,7 @@
 import android.annotation.NonNull;
 import android.app.TaskInfo;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -361,22 +362,26 @@
         }
 
         void setColorContentOverlay(Context context) {
-            final SurfaceControl.Transaction tx =
-                    mSurfaceControlTransactionFactory.getTransaction();
-            if (mContentOverlay != null) {
-                mContentOverlay.detach(tx);
-            }
-            mContentOverlay = new PipContentOverlay.PipColorOverlay(context);
-            mContentOverlay.attach(tx, mLeash);
+            reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context));
         }
 
         void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
+            reattachContentOverlay(
+                    new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
+        }
+
+        void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) {
+            reattachContentOverlay(
+                    new PipContentOverlay.PipAppIconOverlay(context, bounds, activityInfo));
+        }
+
+        private void reattachContentOverlay(PipContentOverlay overlay) {
             final SurfaceControl.Transaction tx =
                     mSurfaceControlTransactionFactory.getTransaction();
             if (mContentOverlay != null) {
                 mContentOverlay.detach(tx);
             }
-            mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint);
+            mContentOverlay = overlay;
             mContentOverlay.attach(tx, mLeash);
         }
 
@@ -570,8 +575,9 @@
                     final Rect base = getBaseValue();
                     final Rect start = getStartValue();
                     final Rect end = getEndValue();
+                    Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
                     if (mContentOverlay != null) {
-                        mContentOverlay.onAnimationUpdate(tx, fraction);
+                        mContentOverlay.onAnimationUpdate(tx, bounds, fraction);
                     }
                     if (rotatedEndRect != null) {
                         // Animate the bounds in a different orientation. It only happens when
@@ -579,7 +585,6 @@
                         applyRotation(tx, leash, fraction, start, end);
                         return;
                     }
-                    Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
                     float angle = (1.0f - fraction) * startingAngle;
                     setCurrentValue(bounds);
                     if (inScaleTransition() || sourceHintRect == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 283b1ec..480bf93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -16,11 +16,21 @@
 
 package com.android.wm.shell.pip;
 
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.TaskSnapshot;
@@ -51,9 +61,11 @@
      * Animates the internal {@link #mLeash} by a given fraction.
      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
      *                 call apply on this transaction, it should be applied on the caller side.
+     * @param currentBounds {@link Rect} of the current animation bounds.
      * @param fraction progress of the animation ranged from 0f to 1f.
      */
-    public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction);
+    public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+            Rect currentBounds, float fraction);
 
     /**
      * Callback when reaches the end of animation on the internal {@link #mLeash}.
@@ -66,13 +78,15 @@
 
     /** A {@link PipContentOverlay} uses solid color. */
     public static final class PipColorOverlay extends PipContentOverlay {
+        private static final String TAG = PipColorOverlay.class.getSimpleName();
+
         private final Context mContext;
 
         public PipColorOverlay(Context context) {
             mContext = context;
             mLeash = new SurfaceControl.Builder(new SurfaceSession())
-                    .setCallsite("PipAnimation")
-                    .setName(PipColorOverlay.class.getSimpleName())
+                    .setCallsite(TAG)
+                    .setName(TAG)
                     .setColorLayer()
                     .build();
         }
@@ -88,7 +102,8 @@
         }
 
         @Override
-        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
+        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+                Rect currentBounds, float fraction) {
             atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
         }
 
@@ -114,6 +129,8 @@
 
     /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
     public static final class PipSnapshotOverlay extends PipContentOverlay {
+        private static final String TAG = PipSnapshotOverlay.class.getSimpleName();
+
         private final TaskSnapshot mSnapshot;
         private final Rect mSourceRectHint;
 
@@ -121,8 +138,8 @@
             mSnapshot = snapshot;
             mSourceRectHint = new Rect(sourceRectHint);
             mLeash = new SurfaceControl.Builder(new SurfaceSession())
-                    .setCallsite("PipAnimation")
-                    .setName(PipSnapshotOverlay.class.getSimpleName())
+                    .setCallsite(TAG)
+                    .setName(TAG)
                     .build();
         }
 
@@ -143,7 +160,8 @@
         }
 
         @Override
-        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
+        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+                Rect currentBounds, float fraction) {
             // Do nothing. Keep the snapshot till animation ends.
         }
 
@@ -152,4 +170,113 @@
             atomicTx.remove(mLeash);
         }
     }
+
+    /** A {@link PipContentOverlay} shows app icon on solid color background. */
+    public static final class PipAppIconOverlay extends PipContentOverlay {
+        private static final String TAG = PipAppIconOverlay.class.getSimpleName();
+        private static final int APP_ICON_SIZE_DP = 48;
+
+        private final Context mContext;
+        private final int mAppIconSizePx;
+        private final Rect mAppBounds;
+        private final Matrix mTmpTransform = new Matrix();
+        private final float[] mTmpFloat9 = new float[9];
+
+        private Bitmap mBitmap;
+
+        public PipAppIconOverlay(Context context, Rect appBounds, ActivityInfo activityInfo) {
+            mContext = context;
+            mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP,
+                    context.getResources().getDisplayMetrics());
+            mAppBounds = new Rect(appBounds);
+            mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
+                    Bitmap.Config.ARGB_8888);
+            prepareAppIconOverlay(activityInfo);
+            mLeash = new SurfaceControl.Builder(new SurfaceSession())
+                    .setCallsite(TAG)
+                    .setName(TAG)
+                    .build();
+        }
+
+        @Override
+        public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+            tx.show(mLeash);
+            tx.setLayer(mLeash, Integer.MAX_VALUE);
+            tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+            tx.reparent(mLeash, parentLeash);
+            tx.apply();
+        }
+
+        @Override
+        public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+                Rect currentBounds, float fraction) {
+            mTmpTransform.reset();
+            // Scale back the bitmap with the pivot point at center.
+            mTmpTransform.postScale(
+                    (float) mAppBounds.width() / currentBounds.width(),
+                    (float) mAppBounds.height() / currentBounds.height(),
+                    mAppBounds.centerX(),
+                    mAppBounds.centerY());
+            atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
+                    .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
+        }
+
+        @Override
+        public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
+            atomicTx.remove(mLeash);
+        }
+
+        @Override
+        public void detach(SurfaceControl.Transaction tx) {
+            super.detach(tx);
+            if (mBitmap != null && !mBitmap.isRecycled()) {
+                mBitmap.recycle();
+            }
+        }
+
+        private void prepareAppIconOverlay(ActivityInfo activityInfo) {
+            final Canvas canvas = new Canvas();
+            canvas.setBitmap(mBitmap);
+            final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+                    android.R.attr.colorBackground });
+            try {
+                int colorAccent = ta.getColor(0, 0);
+                canvas.drawRGB(
+                        Color.red(colorAccent),
+                        Color.green(colorAccent),
+                        Color.blue(colorAccent));
+            } finally {
+                ta.recycle();
+            }
+            final Drawable appIcon = loadActivityInfoIcon(activityInfo,
+                    mContext.getResources().getConfiguration().densityDpi);
+            final Rect appIconBounds = new Rect(
+                    mAppBounds.centerX() - mAppIconSizePx / 2,
+                    mAppBounds.centerY() - mAppIconSizePx / 2,
+                    mAppBounds.centerX() + mAppIconSizePx / 2,
+                    mAppBounds.centerY() + mAppIconSizePx / 2);
+            appIcon.setBounds(appIconBounds);
+            appIcon.draw(canvas);
+            mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+        }
+
+        // Copied from com.android.launcher3.icons.IconProvider#loadActivityInfoIcon
+        private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
+            final int iconRes = ai.getIconResource();
+            Drawable icon = null;
+            // Get the preferred density icon from the app's resources
+            if (density != 0 && iconRes != 0) {
+                try {
+                    final Resources resources = mContext.getPackageManager()
+                            .getResourcesForApplication(ai.applicationInfo);
+                    icon = resources.getDrawableForDensity(iconRes, density);
+                } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { }
+            }
+            // Get the default density icon
+            if (icon == null) {
+                icon = ai.loadIcon(mContext.getPackageManager());
+            }
+            return icon;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8ba2583..aad27b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,6 +62,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.Display;
@@ -1568,7 +1569,13 @@
             // Similar to auto-enter-pip transition, we use content overlay when there is no
             // source rect hint to enter PiP use bounds animation.
             if (sourceHintRect == null) {
-                animator.setColorContentOverlay(mContext);
+                if (SystemProperties.getBoolean(
+                        "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+                    animator.setAppIconContentOverlay(
+                            mContext, currentBounds, mTaskInfo.topActivityInfo);
+                } else {
+                    animator.setColorContentOverlay(mContext);
+                }
             } else {
                 final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
                         mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 83158ff..d9d1009 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -50,6 +50,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.os.SystemProperties;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -792,7 +793,13 @@
             if (sourceHintRect == null) {
                 // We use content overlay when there is no source rect hint to enter PiP use bounds
                 // animation.
-                animator.setColorContentOverlay(mContext);
+                if (SystemProperties.getBoolean(
+                        "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+                    animator.setAppIconContentOverlay(
+                            mContext, currentBounds, taskInfo.topActivityInfo);
+                } else {
+                    animator.setColorContentOverlay(mContext);
+                }
             }
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 21eeaa2..7cb5cf2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -28,6 +28,9 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
+import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
@@ -39,11 +42,8 @@
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -82,8 +82,8 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -318,10 +318,6 @@
         return mStageCoordinator;
     }
 
-    public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
-        return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
-    }
-
     @Nullable
     public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
         if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
@@ -480,39 +476,54 @@
     @Override
     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             @Nullable Bundle options, UserHandle user) {
-        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
-                    RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers,
-                    RemoteAnimationTarget[] nonApps,
-                    final IRemoteAnimationFinishedCallback finishedCallback) {
-                try {
-                    finishedCallback.onAnimationFinished();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
-                }
-                final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-                mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
-                mSyncQueue.queue(evictWct);
+        if (options == null) options = new Bundle();
+        final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+
+        if (samePackage(packageName, getPackageName(reverseSplitPosition(position)))) {
+            if (supportMultiInstancesSplit(packageName)) {
+                activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else if (isSplitScreenVisible()) {
+                mStageCoordinator.switchSplitPosition("startShortcut");
+                return;
+            } else {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+                return;
             }
-            @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
-            }
-        };
-        options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
-                null /* wct */);
-        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
-                0 /* duration */, 0 /* statusBarTransitionDelay */);
-        ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
-        activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
-        try {
-            LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
-            launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
-                    activityOptions.toBundle(), user);
-        } catch (ActivityNotFoundException e) {
-            Slog.e(TAG, "Failed to launch shortcut", e);
         }
+
+        mStageCoordinator.startShortcut(packageName, shortcutId, position,
+                activityOptions.toBundle(), user);
+    }
+
+    void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+            @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        if (options1 == null) options1 = new Bundle();
+        final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+
+        final String packageName1 = shortcutInfo.getPackage();
+        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+                activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                taskId = INVALID_TASK_ID;
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+
+        mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
+                activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter,
+                instanceId);
     }
 
     /**
@@ -530,8 +541,10 @@
             @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
             InstanceId instanceId) {
         Intent fillInIntent = null;
-        if (launchSameAppAdjacently(pendingIntent, taskId)) {
-            if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
                 fillInIntent = new Intent();
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -551,8 +564,10 @@
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
             float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         Intent fillInIntent = null;
-        if (launchSameAppAdjacently(pendingIntent, taskId)) {
-            if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
                 fillInIntent = new Intent();
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -573,8 +588,10 @@
             float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
-        if (launchSameAppAdjacently(pendingIntent1, pendingIntent2)) {
-            if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+        final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        if (samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 fillInIntent2 = new Intent();
@@ -602,13 +619,15 @@
         if (fillInIntent == null) fillInIntent = new Intent();
         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
 
-        if (launchSameAppAdjacently(position, intent)) {
-            final ComponentName launching = intent.getIntent().getComponent();
-            if (supportMultiInstancesSplit(launching)) {
+        final String packageName1 = SplitScreenUtils.getPackageName(intent);
+        final String packageName2 = getPackageName(reverseSplitPosition(position));
+        if (SplitScreenUtils.samePackage(packageName1, packageName2)) {
+            if (supportMultiInstancesSplit(packageName1)) {
                 // To prevent accumulating large number of instances in the background, reuse task
                 // in the background with priority.
                 final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
-                        .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+                        .map(recentTasks -> recentTasks.findTaskInBackground(
+                                intent.getIntent().getComponent()))
                         .orElse(null);
                 if (taskInfo != null) {
                     startTask(taskInfo.taskId, position, options);
@@ -636,63 +655,32 @@
         mStageCoordinator.startIntent(intent, fillInIntent, position, options);
     }
 
+    /** Retrieve package name of a specific split position if split screen is activated, otherwise
+     *  returns the package name of the top running task. */
     @Nullable
-    private String getPackageName(Intent intent) {
-        if (intent == null || intent.getComponent() == null) {
-            return null;
-        }
-        return intent.getComponent().getPackageName();
-    }
-
-    private boolean launchSameAppAdjacently(@SplitPosition int position,
-            PendingIntent pendingIntent) {
-        ActivityManager.RunningTaskInfo adjacentTaskInfo = null;
+    private String getPackageName(@SplitPosition int position) {
+        ActivityManager.RunningTaskInfo taskInfo;
         if (isSplitScreenVisible()) {
-            adjacentTaskInfo = getTaskInfo(SplitLayout.reversePosition(position));
+            taskInfo = getTaskInfo(position);
         } else {
-            adjacentTaskInfo = mRecentTasksOptional
-                    .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
-            if (!isValidToEnterSplitScreen(adjacentTaskInfo)) {
-                return false;
+            taskInfo = mRecentTasksOptional
+                    .map(recentTasks -> recentTasks.getTopRunningTask())
+                    .orElse(null);
+            if (!isValidToSplit(taskInfo)) {
+                return null;
             }
         }
 
-        if (adjacentTaskInfo == null) {
-            return false;
-        }
-
-        final String targetPackageName = getPackageName(pendingIntent.getIntent());
-        final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
-        return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
-    }
-
-    private boolean launchSameAppAdjacently(PendingIntent pendingIntent, int taskId) {
-        final ActivityManager.RunningTaskInfo adjacentTaskInfo =
-                mTaskOrganizer.getRunningTaskInfo(taskId);
-        if (adjacentTaskInfo == null) {
-            return false;
-        }
-        final String targetPackageName = getPackageName(pendingIntent.getIntent());
-        final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
-        return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
-    }
-
-    private boolean launchSameAppAdjacently(PendingIntent pendingIntent1,
-            PendingIntent pendingIntent2) {
-        final String targetPackageName = getPackageName(pendingIntent1.getIntent());
-        final String adjacentPackageName = getPackageName(pendingIntent2.getIntent());
-        return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+        return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
     }
 
     @VisibleForTesting
-    /** Returns {@code true} if the component supports multi-instances split. */
-    boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
-        if (launching == null) return false;
-
-        final String packageName = launching.getPackageName();
-        for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
-            if (mAppsSupportMultiInstances[i].equals(packageName)) {
-                return true;
+    boolean supportMultiInstancesSplit(String packageName) {
+        if (packageName != null) {
+            for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
+                if (mAppsSupportMultiInstances[i].equals(packageName)) {
+                    return true;
+                }
             }
         }
 
@@ -1011,7 +999,7 @@
                 InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startShortcutAndTaskWithLegacyTransition", (controller) ->
-                            controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
+                            controller.startShortcutAndTaskWithLegacyTransition(
                                     shortcutInfo, options1, taskId, options2, splitPosition,
                                     splitRatio, adapter, instanceId));
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 39cf5f1..5a9170b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -36,12 +36,11 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -76,8 +75,10 @@
 import android.app.IActivityTaskManager;
 import android.app.PendingIntent;
 import android.app.WindowConfiguration;
+import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -87,6 +88,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
 import android.view.Choreographer;
@@ -370,7 +372,7 @@
         int sideStagePosition;
         if (stageType == STAGE_TYPE_MAIN) {
             targetStage = mMainStage;
-            sideStagePosition = SplitLayout.reversePosition(stagePosition);
+            sideStagePosition = reverseSplitPosition(stagePosition);
         } else if (stageType == STAGE_TYPE_SIDE) {
             targetStage = mSideStage;
             sideStagePosition = stagePosition;
@@ -428,6 +430,72 @@
         return mLogger;
     }
 
+    void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
+            Bundle options, UserHandle user) {
+        final boolean isEnteringSplit = !isSplitActive();
+
+        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+            @Override
+            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                    RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
+                    RemoteAnimationTarget[] nonApps,
+                    final IRemoteAnimationFinishedCallback finishedCallback) {
+                boolean openingToSide = false;
+                if (apps != null) {
+                    for (int i = 0; i < apps.length; ++i) {
+                        if (apps[i].mode == MODE_OPENING
+                                && mSideStage.containsTask(apps[i].taskId)) {
+                            openingToSide = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (isEnteringSplit && !openingToSide) {
+                    mMainExecutor.execute(() -> exitSplitScreen(
+                            mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+                            EXIT_REASON_UNKNOWN));
+                }
+
+                if (finishedCallback != null) {
+                    try {
+                        finishedCallback.onAnimationFinished();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error finishing legacy transition: ", e);
+                    }
+                }
+
+                if (!isEnteringSplit && openingToSide) {
+                    final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+                    prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+                    mSyncQueue.queue(evictWct);
+                }
+            }
+            @Override
+            public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                if (isEnteringSplit) {
+                    mMainExecutor.execute(() -> exitSplitScreen(
+                            mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+                            EXIT_REASON_UNKNOWN));
+                }
+            }
+        };
+        options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
+                null /* wct */);
+        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+                0 /* duration */, 0 /* statusBarTransitionDelay */);
+        ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+        activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+        try {
+            LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+            launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+                    activityOptions.toBundle(), user);
+        } catch (ActivityNotFoundException e) {
+            Slog.e(TAG, "Failed to launch shortcut", e);
+        }
+    }
+
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
@@ -899,7 +967,7 @@
             case STAGE_TYPE_MAIN: {
                 if (position != SPLIT_POSITION_UNDEFINED) {
                     // Set the side stage opposite of what we want to the main stage.
-                    setSideStagePosition(SplitLayout.reversePosition(position), wct);
+                    setSideStagePosition(reverseSplitPosition(position), wct);
                 } else {
                     position = getMainStagePosition();
                 }
@@ -923,7 +991,7 @@
 
     @SplitPosition
     int getMainStagePosition() {
-        return SplitLayout.reversePosition(mSideStagePosition);
+        return reverseSplitPosition(mSideStagePosition);
     }
 
     int getTaskId(@SplitPosition int splitPosition) {
@@ -950,7 +1018,7 @@
         mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
                 insets -> {
                     WindowContainerTransaction wct = new WindowContainerTransaction();
-                    setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct);
+                    setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
                     mSyncQueue.queue(wct);
                     mSyncQueue.runInSync(st -> {
                         updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
@@ -1694,12 +1762,6 @@
         }
     }
 
-    boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
-        return taskInfo.supportsMultiWindow
-                && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
-                && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
-    }
-
     @Override
     public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
         final boolean mainStageToTop =
@@ -2108,7 +2170,7 @@
 
             // Use normal animations.
             return false;
-        } else if (mMixedHandler != null && hasDisplayChange(info)) {
+        } else if (mMixedHandler != null && Transitions.hasDisplayChange(info)) {
             // A display-change has been un-expectedly inserted into the transition. Redirect
             // handling to the mixed-handler to deal with splitting it up.
             if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
@@ -2151,15 +2213,6 @@
         return true;
     }
 
-    private boolean hasDisplayChange(TransitionInfo info) {
-        boolean has = false;
-        for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) {
-            final TransitionInfo.Change change = info.getChanges().get(iC);
-            has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0;
-        }
-        return has;
-    }
-
     /** Called to clean-up state and do house-keeping after the animation is done. */
     public void onTransitionAnimationComplete() {
         // If still playing, let it finish.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index b4e0584..02f19eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -93,6 +93,11 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (!Transitions.SHELL_TRANSITIONS_ROTATION && Transitions.hasDisplayChange(info)) {
+            // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
+            // operations of the start transaction may be ignored.
+            return false;
+        }
         RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
         if (pendingRemote == null) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 44d6a0d..b2f61c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
@@ -328,6 +329,17 @@
         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
     }
 
+    /** Returns {@code true} if the transition has a display change. */
+    public static boolean hasDisplayChange(@NonNull TransitionInfo info) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
      */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index ea3af9d..d0e2601 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -27,8 +27,6 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -201,7 +199,6 @@
         ActivityManager.RunningTaskInfo topRunningTask =
                 createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
         doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
-        doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
 
         mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
 
@@ -222,7 +219,6 @@
         ActivityManager.RunningTaskInfo topRunningTask =
                 createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
         doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
-        doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
         // Put the same component into a task in the background
         ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
         doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 28496f1..c5202dc 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -108,133 +108,148 @@
     public static final int PLAYBACK_VOLUME_VARIABLE = 1;
 
     /** @hide */
-    @IntDef({
-            TYPE_UNKNOWN, TYPE_BUILTIN_SPEAKER, TYPE_WIRED_HEADSET,
-            TYPE_WIRED_HEADPHONES, TYPE_BLUETOOTH_A2DP, TYPE_HDMI, TYPE_USB_DEVICE,
-            TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID, TYPE_BLE_HEADSET,
-            TYPE_REMOTE_TV, TYPE_REMOTE_SPEAKER, TYPE_GROUP})
+    @IntDef(
+            prefix = {"TYPE_"},
+            value = {
+                TYPE_UNKNOWN,
+                TYPE_BUILTIN_SPEAKER,
+                TYPE_WIRED_HEADSET,
+                TYPE_WIRED_HEADPHONES,
+                TYPE_BLUETOOTH_A2DP,
+                TYPE_HDMI,
+                TYPE_USB_DEVICE,
+                TYPE_USB_ACCESSORY,
+                TYPE_DOCK,
+                TYPE_USB_HEADSET,
+                TYPE_HEARING_AID,
+                TYPE_BLE_HEADSET,
+                TYPE_REMOTE_TV,
+                TYPE_REMOTE_SPEAKER,
+                TYPE_REMOTE_AUDIO_VIDEO_RECEIVER,
+                TYPE_GROUP
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
 
     /**
-     * The default route type indicating the type is unknown.
+     * Indicates the route's type is unknown or undefined.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_UNKNOWN = 0;
 
     /**
-     * A route type describing the speaker system (i.e. a mono speaker or stereo speakers) built
-     * in a device.
+     * Indicates the route is the speaker system (i.e. a mono speaker or stereo speakers) built into
+     * the device.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
 
     /**
-     * A route type describing a headset, which is the combination of a headphones and microphone.
+     * Indicates the route is a headset, which is the combination of a headphones and a microphone.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET;
 
     /**
-     * A route type describing a pair of wired headphones.
+     * Indicates the route is a pair of wired headphones.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
 
     /**
-     * A route type indicating the presentation of the media is happening
-     * on a bluetooth device such as a bluetooth speaker.
+     * Indicates the route is a bluetooth device, such as a bluetooth speaker or headphones.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
 
     /**
-     * A route type describing an HDMI connection.
+     * Indicates the route is an HDMI connection.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
 
     /**
-     * A route type describing a USB audio device.
+     * Indicates the route is a USB audio device.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE;
 
     /**
-     * A route type describing a USB audio device in accessory mode.
+     * Indicates the route is a USB audio device in accessory mode.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY;
 
     /**
-     * A route type describing the audio device associated with a dock.
+     * Indicates the route is the audio device associated with a dock.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK;
 
     /**
-     * A device type describing a USB audio headset.
+     * Indicates the route is a USB audio headset.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET;
 
     /**
-     * A route type describing a Hearing Aid.
+     * Indicates the route is a hearing aid.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
 
     /**
-     * A route type describing a BLE HEADSET.
+     * Indicates the route is a Bluetooth Low Energy (BLE) HEADSET.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
 
     /**
-     * A route type indicating the presentation of the media is happening on a TV.
+     * Indicates the route is a remote TV.
+     *
+     * <p>A remote device uses a routing protocol managed by the application, as opposed to the
+     * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_TV = 1001;
 
     /**
-     * A route type indicating the presentation of the media is happening on a speaker.
+     * Indicates the route is a remote speaker.
+     *
+     * <p>A remote device uses a routing protocol managed by the application, as opposed to the
+     * routing being done by the system.
      *
      * @see #getType
-     * @hide
      */
     public static final int TYPE_REMOTE_SPEAKER = 1002;
 
     /**
-     * A route type indicating the presentation of the media is happening on multiple devices.
+     * Indicates the route is a remote Audio/Video Receiver (AVR).
+     *
+     * <p>A remote device uses a routing protocol managed by the application, as opposed to the
+     * routing being done by the system.
      *
      * @see #getType
-     * @hide
+     */
+    public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003;
+
+    /**
+     * Indicates the route is a group of devices.
+     *
+     * @see #getType
      */
     public static final int TYPE_GROUP = 2000;
 
@@ -436,16 +451,23 @@
     }
 
     /**
-     * Gets the type of this route.
+     * Returns the type of this route.
      *
-     * @return The type of this route:
-     * {@link #TYPE_UNKNOWN},
-     * {@link #TYPE_BUILTIN_SPEAKER}, {@link #TYPE_WIRED_HEADSET}, {@link #TYPE_WIRED_HEADPHONES},
-     * {@link #TYPE_BLUETOOTH_A2DP}, {@link #TYPE_HDMI}, {@link #TYPE_DOCK},
-     * {@Link #TYPE_USB_DEVICE}, {@link #TYPE_USB_ACCESSORY}, {@link #TYPE_USB_HEADSET}
-     * {@link #TYPE_HEARING_AID},
-     * {@link #TYPE_REMOTE_TV}, {@link #TYPE_REMOTE_SPEAKER}, {@link #TYPE_GROUP}.
-     * @hide
+     * @see #TYPE_UNKNOWN
+     * @see #TYPE_BUILTIN_SPEAKER
+     * @see #TYPE_WIRED_HEADSET
+     * @see #TYPE_WIRED_HEADPHONES
+     * @see #TYPE_BLUETOOTH_A2DP
+     * @see #TYPE_HDMI
+     * @see #TYPE_DOCK
+     * @see #TYPE_USB_DEVICE
+     * @see #TYPE_USB_ACCESSORY
+     * @see #TYPE_USB_HEADSET
+     * @see #TYPE_HEARING_AID
+     * @see #TYPE_REMOTE_TV
+     * @see #TYPE_REMOTE_SPEAKER
+     * @see #TYPE_REMOTE_AUDIO_VIDEO_RECEIVER
+     * @see #TYPE_GROUP
      */
     @Type
     public int getType() {
@@ -657,6 +679,7 @@
         pw.println(indent + "mId=" + mId);
         pw.println(indent + "mName=" + mName);
         pw.println(indent + "mFeatures=" + mFeatures);
+        pw.println(indent + "mType=" + getDeviceTypeString(mType));
         pw.println(indent + "mIsSystem=" + mIsSystem);
         pw.println(indent + "mIconUri=" + mIconUri);
         pw.println(indent + "mDescription=" + mDescription);
@@ -787,6 +810,42 @@
         dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
     }
 
+    private static String getDeviceTypeString(@Type int deviceType) {
+        switch (deviceType) {
+            case TYPE_BUILTIN_SPEAKER:
+                return "BUILTIN_SPEAKER";
+            case TYPE_WIRED_HEADSET:
+                return "WIRED_HEADSET";
+            case TYPE_WIRED_HEADPHONES:
+                return "WIRED_HEADPHONES";
+            case TYPE_BLUETOOTH_A2DP:
+                return "BLUETOOTH_A2DP";
+            case TYPE_HDMI:
+                return "HDMI";
+            case TYPE_DOCK:
+                return "DOCK";
+            case TYPE_USB_DEVICE:
+                return "USB_DEVICE";
+            case TYPE_USB_ACCESSORY:
+                return "USB_ACCESSORY";
+            case TYPE_USB_HEADSET:
+                return "USB_HEADSET";
+            case TYPE_HEARING_AID:
+                return "HEARING_AID";
+            case TYPE_REMOTE_TV:
+                return "REMOTE_TV";
+            case TYPE_REMOTE_SPEAKER:
+                return "REMOTE_SPEAKER";
+            case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
+                return "REMOTE_AUDIO_VIDEO_RECEIVER";
+            case TYPE_GROUP:
+                return "GROUP";
+            case TYPE_UNKNOWN:
+            default:
+                return TextUtils.formatSimple("UNKNOWN(%d)", deviceType);
+        }
+    }
+
     /**
      * Builder for {@link MediaRoute2Info media route info}.
      */
@@ -932,7 +991,8 @@
 
         /**
          * Sets the route's type.
-         * @hide
+         *
+         * @see MediaRoute2Info#getType()
          */
         @NonNull
         public Builder setType(@Type int type) {
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index ab78536..6caedda 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -259,7 +259,7 @@
         @IntDef(
                 flag = true,
                 prefix = {"FLAG_"},
-                value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE, FLAG_ONGOING_SESSION_MANAGED})
+                value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED, FLAG_ONGOING_SESSION_MANAGED})
         public @interface Flags {}
 
         /**
@@ -292,7 +292,7 @@
          * number supported by the UI, the routes listed first in {@link
          * RouteListingPreference#getItems()} will take priority.
          */
-        public static final int FLAG_SUGGESTED_ROUTE = 1 << 2;
+        public static final int FLAG_SUGGESTED = 1 << 2;
 
         /** @hide */
         @Retention(RetentionPolicy.SOURCE)
@@ -399,7 +399,7 @@
          * Returns the flags associated to the route that corresponds to this item.
          *
          * @see #FLAG_ONGOING_SESSION
-         * @see #FLAG_SUGGESTED_ROUTE
+         * @see #FLAG_SUGGESTED
          */
         @Flags
         public int getFlags() {
diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java
index ed44508..230d763 100644
--- a/media/java/android/media/tv/AdBuffer.java
+++ b/media/java/android/media/tv/AdBuffer.java
@@ -24,9 +24,8 @@
 
 /**
  * Buffer for advertisement data.
- * @hide
  */
-public class AdBuffer implements Parcelable {
+public final class AdBuffer implements Parcelable {
     private final int mId;
     @NonNull
     private final String mMimeType;
@@ -60,6 +59,8 @@
 
     /**
      * Gets corresponding AD request ID.
+     *
+     * @return The ID of the ad request
      */
     public int getId() {
         return mId;
@@ -67,6 +68,8 @@
 
     /**
      * Gets the mime type of the data.
+     *
+     * @return The mime type of the data.
      */
     @NonNull
     public String getMimeType() {
@@ -74,7 +77,17 @@
     }
 
     /**
-     * Gets the shared memory which stores the data.
+     * Gets the {@link SharedMemory} which stores the data.
+     *
+     * <p> Information on how the data in this buffer is formatted can be found using
+     * {@link AdRequest#getMetadata()}
+     * <p> This data lives in a {@link SharedMemory} instance because of the
+     * potentially large amount of data needed to store the ad. This optimizes the
+     * data communication between the ad data source and the service responsible for
+     * its display.
+     *
+     * @see SharedMemory#create(String, int)
+     * @return The {@link SharedMemory} that stores the data for this ad buffer.
      */
     @NonNull
     public SharedMemory getSharedMemory() {
@@ -82,28 +95,38 @@
     }
 
     /**
-     * Gets the offset of the buffer.
+     * Gets the offset into the shared memory to begin mapping.
+     *
+     * @see SharedMemory#map(int, int, int)
+     * @return The offset of this ad buffer in the shared memory in bytes.
      */
     public int getOffset() {
         return mOffset;
     }
 
     /**
-     * Gets the data length.
+     * Gets the data length of this ad buffer.
+     *
+     * @return The data length of this ad buffer in bytes.
      */
     public int getLength() {
         return mLength;
     }
 
     /**
-     * Gets the presentation time in microseconds.
+     * Gets the presentation time.
+     *
+     * @return The presentation time in microseconds.
      */
     public long getPresentationTimeUs() {
         return mPresentationTimeUs;
     }
 
     /**
-     * Gets the flags.
+     * Gets the buffer flags for this ad buffer.
+     *
+     * @see android.media.MediaCodec
+     * @return The buffer flags for this ad buffer.
      */
     @BufferFlag
     public int getFlags() {
diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java
index 60dfc5e..d8cddfc 100644
--- a/media/java/android/media/tv/AdRequest.java
+++ b/media/java/android/media/tv/AdRequest.java
@@ -79,7 +79,6 @@
                 mediaFileType, metadata);
     }
 
-    /** @hide */
     public AdRequest(int id, @RequestType int requestType, @Nullable Uri uri, long startTime,
             long stopTime, long echoInterval, @NonNull Bundle metadata) {
         this(id, requestType, null, uri, startTime, stopTime, echoInterval, null, metadata);
@@ -153,7 +152,6 @@
      *
      * @return The URI of the AD media. Can be {@code null} for {@link #REQUEST_TYPE_STOP} or a file
      *         descriptor is used.
-     * @hide
      */
     @Nullable
     public Uri getUri() {
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index a15e8c1..7ec4eb2 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -43,7 +43,6 @@
     public static final int RESPONSE_TYPE_FINISHED = 2;
     public static final int RESPONSE_TYPE_STOPPED = 3;
     public static final int RESPONSE_TYPE_ERROR = 4;
-    /** @hide */
     public static final int RESPONSE_TYPE_BUFFERING = 5;
 
     public static final @NonNull Parcelable.Creator<AdResponse> CREATOR =
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 7a4d988..8166114 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1005,9 +1005,10 @@
 
         /**
          * Notifies the advertisement buffer is consumed.
-         * @hide
+         *
+         * @param buffer the {@link AdBuffer} that was consumed.
          */
-        public void notifyAdBufferConsumed(AdBuffer buffer) {
+        public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -1320,10 +1321,11 @@
         }
 
         /**
-         * Called when advertisement buffer is ready.
-         * @hide
+         * Called when an advertisement buffer is ready for playback.
+         *
+         * @param buffer The {@link AdBuffer} that became ready for playback.
          */
-        public void onAdBuffer(AdBuffer buffer) {
+        public void onAdBuffer(@NonNull AdBuffer buffer) {
         }
 
         /**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index cdaa3e5..8b85fa1 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -896,10 +896,10 @@
 
         /**
          * Called when an advertisement buffer is consumed.
-         * @hide
+         *
+         * @param buffer The {@link AdBuffer} that was consumed.
          */
-        public void onAdBufferConsumed(AdBuffer buffer) {
-
+        public void onAdBufferConsumed(@NonNull AdBuffer buffer) {
         }
 
         /**
@@ -1919,10 +1919,11 @@
 
         /**
          * Notifies when the advertisement buffer is filled and ready to be read.
-         * @hide
+         *
+         * @param buffer The {@link AdBuffer} to be received
          */
         @CallSuper
-        public void notifyAdBuffer(AdBuffer buffer) {
+        public void notifyAdBuffer(@NonNull AdBuffer buffer) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index ab2d815..6c463e1 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -22,7 +22,7 @@
               android:orientation="horizontal"
               android:paddingStart="32dp"
               android:paddingEnd="32dp"
-              android:paddingBottom="14dp">
+              android:paddingTop="12dp">
 
     <ImageView
         android:id="@+id/permission_icon"
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 7397688..b842761 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
     <string name="app_label">Companion Device Manager</string>
 
     <!-- Title of the device association confirmation dialog. -->
-    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access your &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;</string>
+    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;</string>
 
     <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
 
@@ -31,10 +31,10 @@
     <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+    <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+    <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string>
 
     <!-- TODO(b/256140614) To replace all glasses related strings with final versions -->
     <!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -43,10 +43,10 @@
     <string name="profile_name_glasses">glasses</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+    <string name="summary_glasses">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+    <string name="summary_glasses_single_device">The app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
@@ -99,7 +99,10 @@
     <string name="profile_name_generic">device</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_generic"></string>
+    <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>.</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device.</string>
 
     <!-- ================= Buttons ================= -->
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index c5ed5c9..918f9c6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -546,17 +546,16 @@
         }
 
         if (deviceProfile == null) {
-            // Summary is not needed for null profile.
-            mSummary.setVisibility(View.GONE);
+            summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
             mConstraintList.setVisibility(View.GONE);
         } else {
+            summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile),
+                    getString(PROFILES_NAME.get(deviceProfile)), appLabel);
             mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
             setupPermissionList();
         }
 
         title = getHtmlFromResources(this, TITLES.get(deviceProfile), appLabel, deviceName);
-        summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile),
-                getString(PROFILES_NAME.get(deviceProfile)), appLabel);
         profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
 
         mTitle.setText(title);
@@ -586,7 +585,6 @@
 
         if (deviceProfile == null) {
             summary = getHtmlFromResources(this, summaryResourceId);
-            mSummary.setVisibility(View.GONE);
         } else {
             summary = getHtmlFromResources(this, summaryResourceId, profileName, appLabel);
         }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 6f5f4fe..e3fd354 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -88,7 +88,7 @@
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch_single_device);
         map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_single_device);
-        map.put(null, R.string.summary_generic);
+        map.put(null, R.string.summary_generic_single_device);
 
         SUMMARIES = unmodifiableMap(map);
     }
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index d6909719..49ac482 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -13,6 +13,10 @@
   <string name="string_more_options">More options</string>
   <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
   <string name="string_learn_more">Learn more</string>
+  <!-- This is a label for content description for show password icon button. -->
+  <string name="content_description_show_password">Show password</string>
+  <!-- This is a label for content description for hide password icon button. -->
+  <string name="content_description_hide_password">Hide password</string>
   <!-- This string introduces passkeys to the users for the first time they use this method. Tip: to avoid gendered language patterns, this header could be translated as if the original string were "More safety with passkeys". [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_title">Safer with passkeys</string>
   <!-- This string highlight passkey benefits related with the password. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index c66ea5e..6ea1d8d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -64,7 +64,7 @@
         requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
-        ) ?: testCreatePasskeyRequestInfo()
+        ) ?: testCreatePasswordRequestInfo()
 
         providerEnabledList = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE ->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index d8420cd..15acd8c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -198,7 +198,7 @@
                             credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
                             userName = credentialEntry.username.toString(),
                             displayName = credentialEntry.displayName?.toString(),
-                            icon = credentialEntry.icon.loadDrawable(context),
+                            icon = credentialEntry.icon?.loadDrawable(context),
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
                         ))
                     }
@@ -213,7 +213,7 @@
                             credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
                             userName = credentialEntry.username.toString(),
                             displayName = credentialEntry.displayName?.toString(),
-                            icon = credentialEntry.icon.loadDrawable(context),
+                            icon = credentialEntry.icon?.loadDrawable(context),
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
                         ))
                     }
@@ -228,7 +228,7 @@
                             credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
                             userName = credentialEntry.title.toString(),
                             displayName = credentialEntry.subtitle?.toString(),
-                            icon = credentialEntry.icon.loadDrawable(context),
+                            icon = credentialEntry.icon?.loadDrawable(context),
                             lastUsedTimeMillis = credentialEntry.lastUsedTime,
                         ))
                     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
index d0271ab..984057a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
@@ -16,11 +16,23 @@
 
 package com.android.credentialmanager.common.ui
 
+import com.android.credentialmanager.R
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Visibility
+import androidx.compose.material.icons.outlined.VisibilityOff
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun ActionButton(text: String, onClick: () -> Unit) {
@@ -32,4 +44,27 @@
     ) {
         Text(text = text)
     }
+}
+
+@Composable
+fun ToggleVisibilityButton(modifier: Modifier = Modifier, onToggle: (Boolean) -> Unit) {
+    // default state is visibility off
+    val toggleState: MutableState<Boolean> = remember { mutableStateOf(false) }
+
+    IconButton(
+        modifier = modifier,
+        onClick = {
+            toggleState.value = !toggleState.value
+            onToggle(toggleState.value)
+        }
+    ) {
+        Icon(
+            imageVector = if (toggleState.value)
+                Icons.Outlined.Visibility else Icons.Outlined.VisibilityOff,
+            contentDescription = if (toggleState.value)
+                stringResource(R.string.content_description_show_password) else
+                stringResource(R.string.content_description_hide_password),
+            tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
+        )
+    }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index f8d008e..0b9e578 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -10,6 +10,8 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
@@ -26,12 +28,17 @@
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
@@ -48,6 +55,7 @@
 import com.android.credentialmanager.common.ui.TextSecondary
 import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.ContainerCard
+import com.android.credentialmanager.common.ui.ToggleVisibilityButton
 import com.android.credentialmanager.ui.theme.EntryShape
 import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
@@ -851,12 +859,38 @@
                             style = MaterialTheme.typography.titleLarge,
                             modifier = Modifier.padding(top = 16.dp, start = 5.dp),
                         )
-                        TextSecondary(
+                        Row(modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 16.dp,
+                                                                       start = 5.dp),
+                            verticalAlignment = Alignment.CenterVertically) {
+                            val visualTransformation = remember { PasswordVisualTransformation() }
                             // This subtitle would never be null for create password
-                            text = requestDisplayInfo.subtitle ?: "",
-                            style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                        )
+                            val originalPassword by remember {
+                                mutableStateOf(requestDisplayInfo.subtitle ?: "")
+                            }
+                            val displayedPassword = remember {
+                                mutableStateOf(
+                                    visualTransformation.filter(
+                                        AnnotatedString(originalPassword)
+                                    ).text.text
+                                )
+                            }
+                            TextSecondary(
+                                text = displayedPassword.value,
+                                style = MaterialTheme.typography.bodyMedium,
+                                modifier = Modifier.padding(top = 4.dp, bottom = 4.dp),
+                            )
+
+                            ToggleVisibilityButton(modifier = Modifier.padding(start = 4.dp)
+                                .height(24.dp).width(24.dp), onToggle = {
+                                if (it) {
+                                    displayedPassword.value = originalPassword
+                                } else {
+                                    displayedPassword.value = visualTransformation.filter(
+                                        AnnotatedString(originalPassword)
+                                    ).text.text
+                                }
+                            })
+                        }
                     }
                     CredentialType.UNKNOWN -> {
                         if (requestDisplayInfo.subtitle != null) {
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index c35fb3b..4b4cfb7 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -30,5 +30,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.healthconnect",
     ],
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 4b73e94..b92729d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -24,10 +24,6 @@
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.remember
 import com.android.settingslib.spa.framework.compose.LocalNavController
-import com.android.settingslib.spa.framework.util.genEntryId
-
-private const val INJECT_ENTRY_NAME = "INJECT"
-private const val ROOT_ENTRY_NAME = "ROOT"
 
 interface EntryData {
     val pageId: String?
@@ -164,143 +160,3 @@
         }
     }
 }
-
-/**
- * The helper to build a Settings Entry instance.
- */
-class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
-    private var displayName = name
-    private var fromPage: SettingsPage? = null
-    private var toPage: SettingsPage? = null
-
-    // Attributes
-    private var isAllowSearch: Boolean = false
-    private var isSearchDataDynamic: Boolean = false
-    private var hasMutableStatus: Boolean = false
-    private var hasSliceSupport: Boolean = false
-
-    // Functions
-    private var uiLayoutFn: UiLayerRenderer = { }
-    private var statusDataFn: StatusDataGetter = { null }
-    private var searchDataFn: SearchDataGetter = { null }
-    private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
-
-    fun build(): SettingsEntry {
-        val page = fromPage ?: owner
-        val isEnabled = page.isEnabled()
-        return SettingsEntry(
-            id = genEntryId(name, owner, fromPage, toPage),
-            name = name,
-            owner = owner,
-            displayName = displayName,
-
-            // linking data
-            fromPage = fromPage,
-            toPage = toPage,
-
-            // attributes
-            // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately
-            isAllowSearch = isEnabled && isAllowSearch,
-            isSearchDataDynamic = isSearchDataDynamic,
-            hasMutableStatus = hasMutableStatus,
-            hasSliceSupport = isEnabled && hasSliceSupport,
-
-            // functions
-            statusDataImpl = statusDataFn,
-            searchDataImpl = searchDataFn,
-            sliceDataImpl = sliceDataFn,
-            uiLayoutImpl = uiLayoutFn,
-        )
-    }
-
-    fun setDisplayName(displayName: String): SettingsEntryBuilder {
-        this.displayName = displayName
-        return this
-    }
-
-    fun setLink(
-        fromPage: SettingsPage? = null,
-        toPage: SettingsPage? = null
-    ): SettingsEntryBuilder {
-        if (fromPage != null) this.fromPage = fromPage
-        if (toPage != null) this.toPage = toPage
-        return this
-    }
-
-    fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
-        this.isSearchDataDynamic = isDynamic
-        return this
-    }
-
-    fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
-        this.hasMutableStatus = hasMutableStatus
-        return this
-    }
-
-    fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder {
-        setStatusDataFn { fn(it).getStatusData() }
-        setSearchDataFn { fn(it).getSearchData() }
-        setUiLayoutFn {
-            val macro = remember { fn(it) }
-            macro.UiLayout()
-        }
-        return this
-    }
-
-    fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder {
-        this.statusDataFn = fn
-        return this
-    }
-
-    fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
-        this.searchDataFn = fn
-        this.isAllowSearch = true
-        return this
-    }
-
-    fun clearSearchDataFn(): SettingsEntryBuilder {
-        this.searchDataFn = { null }
-        this.isAllowSearch = false
-        return this
-    }
-
-    fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder {
-        this.sliceDataFn = fn
-        this.hasSliceSupport = true
-        return this
-    }
-
-    fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder {
-        this.uiLayoutFn = fn
-        return this
-    }
-
-    companion object {
-        fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
-            return SettingsEntryBuilder(entryName, owner)
-        }
-
-        fun createLinkFrom(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
-            return create(entryName, owner).setLink(fromPage = owner)
-        }
-
-        fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
-            return create(entryName, owner).setLink(toPage = owner)
-        }
-
-        fun create(owner: SettingsPage, entryName: String, displayName: String? = null):
-            SettingsEntryBuilder {
-            return SettingsEntryBuilder(entryName, owner).setDisplayName(displayName ?: entryName)
-        }
-
-        fun createInject(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
-            val name = displayName ?: "${INJECT_ENTRY_NAME}_${owner.displayName}"
-            return createLinkTo(INJECT_ENTRY_NAME, owner).setDisplayName(name)
-        }
-
-        fun createRoot(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
-            val name = displayName ?: "${ROOT_ENTRY_NAME}_${owner.displayName}"
-            return createLinkTo(ROOT_ENTRY_NAME, owner).setDisplayName(name)
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
new file mode 100644
index 0000000..67f9ea5
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.settingslib.spa.framework.common
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.util.genEntryId
+
+private const val INJECT_ENTRY_NAME = "INJECT"
+private const val ROOT_ENTRY_NAME = "ROOT"
+
+/**
+ * The helper to build a Settings Entry instance.
+ */
+class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
+    private var displayName = name
+    private var fromPage: SettingsPage? = null
+    private var toPage: SettingsPage? = null
+
+    // Attributes
+    private var isAllowSearch: Boolean = false
+    private var isSearchDataDynamic: Boolean = false
+    private var hasMutableStatus: Boolean = false
+    private var hasSliceSupport: Boolean = false
+
+    // Functions
+    private var uiLayoutFn: UiLayerRenderer = { }
+    private var statusDataFn: StatusDataGetter = { null }
+    private var searchDataFn: SearchDataGetter = { null }
+    private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
+
+    fun build(): SettingsEntry {
+        val page = fromPage ?: owner
+        val isEnabled = page.isEnabled()
+        return SettingsEntry(
+            id = genEntryId(name, owner, fromPage, toPage),
+            name = name,
+            owner = owner,
+            displayName = displayName,
+
+            // linking data
+            fromPage = fromPage,
+            toPage = toPage,
+
+            // attributes
+            // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately
+            isAllowSearch = isEnabled && isAllowSearch,
+            isSearchDataDynamic = isSearchDataDynamic,
+            hasMutableStatus = hasMutableStatus,
+            hasSliceSupport = isEnabled && hasSliceSupport,
+
+            // functions
+            statusDataImpl = statusDataFn,
+            searchDataImpl = searchDataFn,
+            sliceDataImpl = sliceDataFn,
+            uiLayoutImpl = uiLayoutFn,
+        )
+    }
+
+    fun setDisplayName(displayName: String): SettingsEntryBuilder {
+        this.displayName = displayName
+        return this
+    }
+
+    fun setLink(
+        fromPage: SettingsPage? = null,
+        toPage: SettingsPage? = null
+    ): SettingsEntryBuilder {
+        if (fromPage != null) this.fromPage = fromPage
+        if (toPage != null) this.toPage = toPage
+        return this
+    }
+
+    fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
+        this.isSearchDataDynamic = isDynamic
+        return this
+    }
+
+    fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
+        this.hasMutableStatus = hasMutableStatus
+        return this
+    }
+
+    fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder {
+        setStatusDataFn { fn(it).getStatusData() }
+        setSearchDataFn { fn(it).getSearchData() }
+        setUiLayoutFn {
+            val macro = remember { fn(it) }
+            macro.UiLayout()
+        }
+        return this
+    }
+
+    fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder {
+        this.statusDataFn = fn
+        return this
+    }
+
+    fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
+        this.searchDataFn = fn
+        this.isAllowSearch = true
+        return this
+    }
+
+    fun clearSearchDataFn(): SettingsEntryBuilder {
+        this.searchDataFn = { null }
+        this.isAllowSearch = false
+        return this
+    }
+
+    fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder {
+        this.sliceDataFn = fn
+        this.hasSliceSupport = true
+        return this
+    }
+
+    fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder {
+        this.uiLayoutFn = fn
+        return this
+    }
+
+    companion object {
+        fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
+            return SettingsEntryBuilder(entryName, owner)
+        }
+
+        fun createLinkFrom(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
+            return create(entryName, owner).setLink(fromPage = owner)
+        }
+
+        fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
+            return create(entryName, owner).setLink(toPage = owner)
+        }
+
+        fun create(owner: SettingsPage, entryName: String, displayName: String? = null):
+            SettingsEntryBuilder {
+            return SettingsEntryBuilder(entryName, owner).setDisplayName(displayName ?: entryName)
+        }
+
+        fun createInject(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
+            val name = displayName ?: "${INJECT_ENTRY_NAME}_${owner.displayName}"
+            return createLinkTo(INJECT_ENTRY_NAME, owner).setDisplayName(name)
+        }
+
+        fun createRoot(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
+            val name = displayName ?: "${ROOT_ENTRY_NAME}_${owner.displayName}"
+            return createLinkTo(ROOT_ENTRY_NAME, owner).setDisplayName(name)
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 14dc785..429f97b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -30,14 +30,10 @@
     val injectEntry: SettingsEntry,
 )
 
-private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String {
-    return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments)
-}
-
 /**
  * The repository to maintain all Settings entries
  */
-class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) {
+class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
     // Map of entry unique Id to entry
     private val entryMap: Map<String, SettingsEntry>
 
@@ -66,6 +62,9 @@
             if (page == null || pageWithEntryMap.containsKey(page.id)) continue
             val spp = sppRepository.getProviderOrNull(page.sppName) ?: continue
             val newEntries = spp.buildEntry(page.arguments)
+            // The page id could be existed already, if there are 2+ pages go to the same one.
+            // For now, override the previous ones, which means only the last from-page is kept.
+            // TODO: support multiple from-pages if necessary.
             pageWithEntryMap[page.id] = SettingsPageWithEntry(
                 page = page,
                 entries = newEntries,
@@ -123,7 +122,7 @@
             if (it.toPage == null)
                 defaultTitle
             else {
-                it.toPage.getTitle(sppRepository)
+                it.toPage.getTitle()
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index c810648..724588f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -81,6 +81,10 @@
         return getPageProvider(sppName)?.isEnabled(arguments) ?: false
     }
 
+    fun getTitle(): String {
+        return getPageProvider(sppName)?.getTitle(arguments) ?: ""
+    }
+
     @Composable
     fun UiLayout() {
         getPageProvider(sppName)?.Page(arguments)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 32b283e..62189dc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -24,7 +24,12 @@
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.LayoutDirection
 import com.android.settingslib.spa.framework.compose.LocalNavController
 
 /** Action that navigates back to last page. */
@@ -50,6 +55,7 @@
         Icon(
             imageVector = Icons.Outlined.ArrowBack,
             contentDescription = contentDescription,
+            modifier = Modifier.autoMirrored(),
         )
     }
 }
@@ -75,3 +81,10 @@
         )
     }
 }
+
+private fun Modifier.autoMirrored() = composed {
+    when (LocalLayoutDirection.current) {
+        LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f)
+        else -> this
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index 730aa8f..379b9a7 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -113,6 +113,7 @@
 
     @Test
     fun testGetEntryPath() {
+        SpaEnvironmentFactory.reset(spaEnvironment)
         assertThat(
             entryRepository.getEntryPathWithDisplayName(
                 genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 6fb5555..c036fdb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -73,6 +73,8 @@
     }
 
     @VisibleForTesting
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     int getDrawableResId() {
         int resId;
         switch (mRouteInfo.getType()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index d222b98..77e514f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -443,6 +443,8 @@
         dispatchDeviceListAdded();
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     private void buildAllRoutes() {
         for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
             if (DEBUG) {
@@ -462,6 +464,8 @@
         return infos;
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     private synchronized void buildAvailableRoutes() {
         for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) {
             if (DEBUG) {
@@ -512,6 +516,8 @@
         }
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     @VisibleForTesting
     void addMediaDevice(MediaRoute2Info route) {
         //TODO(b/258141461): Attach flag and disable reason in MediaDevice
@@ -635,8 +641,8 @@
             List<RouteListingPreference.Item> finalizedItemList = new ArrayList<>();
             List<RouteListingPreference.Item> itemList = routeListingPreference.getItems();
             for (RouteListingPreference.Item item : itemList) {
-                //Put suggested devices on the top first before further organization
-                if (item.getFlags() == RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE) {
+                // Put suggested devices on the top first before further organization
+                if (item.getFlags() == RouteListingPreference.Item.FLAG_SUGGESTED) {
                     finalizedItemList.add(0, item);
                 } else {
                     finalizedItemList.add(item);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 50f3713..d242198 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -31,7 +31,7 @@
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
 import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
-import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
+import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED;
 import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
 import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
@@ -114,6 +114,8 @@
         setType(info);
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     private void setType(MediaRoute2Info info) {
         if (info == null) {
             mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
@@ -335,6 +337,8 @@
      *
      * @return true if the RouteInfo equals TYPE_BLE_HEADSET.
      */
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     public boolean isBLEDevice() {
         return mRouteInfo.getType() == TYPE_BLE_HEADSET;
     }
@@ -551,7 +555,7 @@
     private static class Api34Impl {
         @DoNotInline
         static boolean isSuggestedDevice(RouteListingPreference.Item item) {
-            return item != null && (item.getFlags() & FLAG_SUGGESTED_ROUTE) != 0;
+            return item != null && (item.getFlags() & FLAG_SUGGESTED) != 0;
         }
 
         @DoNotInline
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index de16d4a..1c82be9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -56,6 +56,8 @@
         initDeviceRecord();
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     @Override
     public String getName() {
         CharSequence name;
@@ -94,11 +96,15 @@
         return mContext.getDrawable(getDrawableResId());
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     @VisibleForTesting
     int getDrawableResId() {
         return mDeviceIconUtil.getIconResIdFromMediaRouteType(mRouteInfo.getType());
     }
 
+    // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
+    @SuppressWarnings("NewApi")
     @Override
     public String getId() {
         String id;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 31038cd..04c1c31 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -259,8 +259,10 @@
         ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
                 Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
         final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
-        RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder(
-                TEST_ID_4).setFlags(RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE).build();
+        RouteListingPreference.Item item1 =
+                new RouteListingPreference.Item.Builder(TEST_ID_4)
+                        .setFlags(RouteListingPreference.Item.FLAG_SUGGESTED)
+                        .build();
         RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder(
                 TEST_ID_3).build();
         preferenceItemList.add(item1);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 293a590..bf9e428 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -228,6 +228,7 @@
         Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
-        Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER
+        Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
+        Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 31eb009..f0bc1df 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -361,5 +361,6 @@
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3eec565..e55ac1b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -106,6 +106,12 @@
             updateDoubleLineClock();
         }
     };
+    private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
+        @Override
+        public void onChange(boolean change) {
+            setWeatherVisibility();
+        }
+    };
 
     private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
             mKeyguardUnlockAnimationListener =
@@ -216,7 +222,15 @@
                 UserHandle.USER_ALL
         );
 
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
+                false, /* notifyForDescendants */
+                mShowWeatherObserver,
+                UserHandle.USER_ALL
+        );
+
         updateDoubleLineClock();
+        setWeatherVisibility();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
@@ -449,6 +463,14 @@
         }
     }
 
+    private void setWeatherVisibility() {
+        if (mWeatherView != null) {
+            mUiExecutor.execute(
+                    () -> mWeatherView.setVisibility(
+                        mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE));
+        }
+    }
+
     /**
      * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
      * bounds during the unlock transition.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index fb0c0a6..5ca36ab 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -72,7 +72,7 @@
         height = WindowManager.LayoutParams.MATCH_PARENT
         layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
         format = PixelFormat.TRANSLUCENT
-        type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
+        type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
         fitInsetsTypes = 0 // Ignore insets from all system bars
         title = "Wired Charging Animation"
         flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 57c99d1..d02eee0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -208,9 +208,7 @@
         unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
 
     // TODO(b/242908637): Tracking Bug
-    @JvmField
-    val WALLPAPER_FULLSCREEN_PREVIEW =
-        unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true)
+    @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview")
 
     /** Whether the long-press gesture to open wallpaper picker is enabled. */
     // TODO(b/266242192): Tracking Bug
@@ -339,8 +337,7 @@
     @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise")
 
     // TODO(b/263272731): Tracking Bug
-    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
-        unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
+    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
 
     // TODO(b/263512203): Tracking Bug
     val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
@@ -507,11 +504,10 @@
     // 1300 - screenshots
     // TODO(b/254513155): Tracking Bug
     @JvmField
-    val SCREENSHOT_WORK_PROFILE_POLICY =
-        unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
+    val SCREENSHOT_WORK_PROFILE_POLICY = releasedFlag(1301, "screenshot_work_profile_policy")
 
     // TODO(b/264916608): Tracking Bug
-    @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+    @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata", teamfood = true)
 
     // TODO(b/266955521): Tracking bug
     @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection")
@@ -532,13 +528,16 @@
     val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
 
     // TODO(b/266983432) Tracking Bug
-    val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions")
+    val SHARESHEET_CUSTOM_ACTIONS =
+        unreleasedFlag(1501, "sharesheet_custom_actions", teamfood = true)
 
     // TODO(b/266982749) Tracking Bug
-    val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action")
+    val SHARESHEET_RESELECTION_ACTION =
+        unreleasedFlag(1502, "sharesheet_reselection_action", teamfood = true)
 
     // TODO(b/266983474) Tracking Bug
-    val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
+    val SHARESHEET_IMAGE_AND_TEXT_PREVIEW =
+        unreleasedFlag(1503, "sharesheet_image_text_preview", teamfood = true)
 
     // TODO(b/267355521) Tracking Bug
     val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW =
@@ -597,12 +596,11 @@
     // 2500 - output switcher
     // TODO(b/261538825): Tracking Bug
     @JvmField
-    val OUTPUT_SWITCHER_ADVANCED_LAYOUT = unreleasedFlag(2500, "output_switcher_advanced_layout")
+    val OUTPUT_SWITCHER_ADVANCED_LAYOUT = releasedFlag(2500, "output_switcher_advanced_layout")
     @JvmField
-    val OUTPUT_SWITCHER_ROUTES_PROCESSING =
-        unreleasedFlag(2501, "output_switcher_routes_processing")
+    val OUTPUT_SWITCHER_ROUTES_PROCESSING = releasedFlag(2501, "output_switcher_routes_processing")
     @JvmField
-    val OUTPUT_SWITCHER_DEVICE_STATUS = unreleasedFlag(2502, "output_switcher_device_status")
+    val OUTPUT_SWITCHER_DEVICE_STATUS = releasedFlag(2502, "output_switcher_device_status")
 
     // TODO(b/20911786): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 35423f4..d5d7325 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -30,7 +30,6 @@
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
-import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -85,7 +84,6 @@
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.InsetsFrameProvider;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -97,6 +95,7 @@
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -1150,12 +1149,11 @@
     }
 
     @Override
-    public void showTransient(int displayId, @InternalInsetsType int[] types,
-            boolean isGestureOnSystemBar) {
+    public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) {
         if (displayId != mDisplayId) {
             return;
         }
-        if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+        if ((types & WindowInsets.Type.navigationBars()) == 0) {
             return;
         }
         if (!mTransientShown) {
@@ -1166,11 +1164,11 @@
     }
 
     @Override
-    public void abortTransient(int displayId, @InternalInsetsType int[] types) {
+    public void abortTransient(int displayId, @InsetsType int types) {
         if (displayId != mDisplayId) {
             return;
         }
-        if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+        if ((types & WindowInsets.Type.navigationBars()) == 0) {
             return;
         }
         clearTransient();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index f3712e6..c3d7369 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -20,8 +20,6 @@
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -41,7 +39,6 @@
 
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.WindowVisibleState;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -52,6 +49,7 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -68,7 +66,6 @@
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.recents.utilities.Utilities;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -401,11 +398,11 @@
     }
 
     @Override
-    public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) {
+    public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) {
         if (displayId != mDisplayId) {
             return;
         }
-        if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) {
+        if ((types & WindowInsets.Type.navigationBars()) == 0) {
             return;
         }
         if (!mTaskbarTransientShowing) {
@@ -415,11 +412,11 @@
     }
 
     @Override
-    public void abortTransient(int displayId, int[] types) {
+    public void abortTransient(int displayId, @InsetsType int types) {
         if (displayId != mDisplayId) {
             return;
         }
-        if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) {
+        if ((types & WindowInsets.Type.navigationBars()) == 0) {
             return;
         }
         clearTransient();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 335172e..30d2d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.navigationbar.gestural;
 
-import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 
 import android.content.Context;
 import android.view.MotionEvent;
@@ -24,14 +24,12 @@
 
 public final class Utilities {
 
-    private static final int TRACKPAD_GESTURE_SCALE = 60;
+    private static final int TRACKPAD_GESTURE_SCALE = 200;
 
     public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled,
             MotionEvent event) {
-        // TODO: ideally should use event.getClassification(), but currently only the move
-        // events get assigned the correct classification.
         return isTrackpadGestureBackEnabled
-                && (event.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
+                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
     }
 
     public static int getTrackpadScale(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 6bfe1a0..be615d6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -20,9 +20,12 @@
 import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.content.Context
+import android.content.Intent
 import android.content.pm.PackageManager
 import android.os.UserManager
 import android.util.Log
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
@@ -42,11 +45,12 @@
 @Inject
 constructor(
     private val context: Context,
-    private val intentResolver: NoteTaskIntentResolver,
+    private val resolver: NoteTaskInfoResolver,
     private val optionalBubbles: Optional<Bubbles>,
     private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     /**
@@ -64,7 +68,9 @@
      *
      * That will let users open other apps in full screen, and take contextual notes.
      */
-    fun showNoteTask(isInMultiWindowMode: Boolean = false) {
+    @JvmOverloads
+    fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) {
+
         if (!isEnabled) return
 
         val bubbles = optionalBubbles.getOrNull() ?: return
@@ -74,9 +80,12 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        val intent = intentResolver.resolveIntent() ?: return
+        val noteTaskInfo = resolver.resolveInfo() ?: return
+
+        uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
 
         // TODO(b/266686199): We should handle when app not available. For now, we log.
+        val intent = noteTaskInfo.toCreateNoteIntent()
         try {
             if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
                 context.startActivity(intent)
@@ -84,9 +93,7 @@
                 bubbles.showOrHideAppBubble(intent)
             }
         } catch (e: ActivityNotFoundException) {
-            val message =
-                "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}."
-            Log.e(TAG, message, e)
+            Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e)
         }
     }
 
@@ -114,10 +121,47 @@
         )
     }
 
+    /** IDs of UI events accepted by [showNoteTask]. */
+    enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
+        NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
+
+        /* ktlint-disable max-line-length */
+        @UiEvent(
+            doc =
+                "User opened a note by pressing the stylus tail button while the screen was unlocked."
+        )
+        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
+        @UiEvent(
+            doc =
+                "User opened a note by pressing the stylus tail button while the screen was locked."
+        )
+        NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
+        @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
+        NOTE_OPENED_VIA_SHORTCUT(1297);
+
+        override fun getId() = _id
+    }
+
     companion object {
         private val TAG = NoteTaskController::class.simpleName.orEmpty()
 
+        private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
+            return Intent(ACTION_CREATE_NOTE)
+                .setPackage(packageName)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
+                // was used to start it.
+                .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
+        }
+
         // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
         const val NOTE_TASK_KEY_EVENT = 311
+
+        // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
+        const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
+
+        // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
+        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
new file mode 100644
index 0000000..bd822d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.notetask
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.util.Log
+import javax.inject.Inject
+
+internal class NoteTaskInfoResolver
+@Inject
+constructor(
+    private val context: Context,
+    private val roleManager: RoleManager,
+    private val packageManager: PackageManager,
+) {
+    fun resolveInfo(): NoteTaskInfo? {
+        // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
+        val user = context.user
+        val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull()
+
+        if (packageName.isNullOrEmpty()) return null
+
+        return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user))
+    }
+
+    /** Package name and kernel user-ID of a note-taking app. */
+    data class NoteTaskInfo(val packageName: String, val uid: Int)
+
+    companion object {
+        private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
+
+        private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!!
+
+        /**
+         * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot
+         * be found.
+         */
+        private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int {
+            val applicationInfo =
+                try {
+                    getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user)
+                } catch (e: PackageManager.NameNotFoundException) {
+                    Log.e(TAG, "Couldn't find notes app UID", e)
+                    return 0
+                }
+            return applicationInfo.uid
+        }
+
+        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
+        const val ROLE_NOTES = "android.app.role.NOTES"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d5f4a5a..d40bf2b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.notetask
 
+import android.app.KeyguardManager
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import javax.inject.Inject
@@ -30,6 +32,7 @@
     private val noteTaskController: NoteTaskController,
     private val commandQueue: CommandQueue,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
+    private val optionalKeyguardManager: Optional<KeyguardManager>,
 ) {
 
     @VisibleForTesting
@@ -37,11 +40,21 @@
         object : CommandQueue.Callbacks {
             override fun handleSystemKey(keyCode: Int) {
                 if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) {
-                    noteTaskController.showNoteTask()
+                    showNoteTask()
                 }
             }
         }
 
+    private fun showNoteTask() {
+        val uiEvent =
+            if (optionalKeyguardManager.isKeyguardLocked) {
+                NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+            } else {
+                NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+            }
+        noteTaskController.showNoteTask(uiEvent = uiEvent)
+    }
+
     fun initialize() {
         if (isEnabled && optionalBubbles.isPresent) {
             commandQueue.addCallback(callbacks)
@@ -49,3 +62,7 @@
         noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
     }
 }
+
+private val Optional<KeyguardManager>.isKeyguardLocked: Boolean
+    // If there's no KeyguardManager, assume that the keyguard is not locked.
+    get() = getOrNull()?.isKeyguardLocked ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
deleted file mode 100644
index 11dc1d7..0000000
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
+++ /dev/null
@@ -1,54 +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.notetask
-
-import android.app.role.RoleManager
-import android.content.Context
-import android.content.Intent
-import javax.inject.Inject
-
-internal class NoteTaskIntentResolver
-@Inject
-constructor(
-    private val context: Context,
-    private val roleManager: RoleManager,
-) {
-
-    fun resolveIntent(): Intent? {
-        val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()
-
-        if (packageName.isNullOrEmpty()) return null
-
-        return Intent(ACTION_CREATE_NOTE)
-            .setPackage(packageName)
-            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was
-            // used to start it.
-            .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
-    }
-
-    companion object {
-        // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
-        const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
-
-        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
-        const val ROLE_NOTES = "android.app.role.NOTES"
-
-        // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
-        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index ec6a16a..b8800a2 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -51,7 +51,7 @@
             featureFlags: FeatureFlags,
             roleManager: RoleManager,
         ): Boolean {
-            val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
+            val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskInfoResolver.ROLE_NOTES)
             val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
             return isRoleAvailable && isFeatureEnabled
         }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index cfbaa48..43869cc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
 import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.notetask.NoteTaskEnabledKey
 import javax.inject.Inject
 import kotlinx.coroutines.flow.flowOf
@@ -64,7 +65,9 @@
         }
 
     override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
-        noteTaskController.showNoteTask()
+        noteTaskController.showNoteTask(
+            uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+        )
         return OnTriggeredResult.Handled
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index f203e7a..3ac5bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -21,7 +21,7 @@
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskIntentResolver
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import javax.inject.Inject
 
 /** Activity responsible for launching the note experience, and finish. */
@@ -34,7 +34,10 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        noteTaskController.showNoteTask(isInMultiWindowMode)
+        noteTaskController.showNoteTask(
+            isInMultiWindowMode = isInMultiWindowMode,
+            uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+        )
 
         finish()
     }
@@ -46,7 +49,7 @@
             return Intent(context, LaunchNoteTaskActivity::class.java).apply {
                 // Intent's action must be set in shortcuts, or an exception will be thrown.
                 // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
-                action = NoteTaskIntentResolver.ACTION_CREATE_NOTE
+                action = NoteTaskController.ACTION_CREATE_NOTE
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c573080..f53f824 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -685,6 +685,7 @@
     private boolean mInstantExpanding;
     private boolean mAnimateAfterExpanding;
     private boolean mIsFlinging;
+    private boolean mLastFlingWasExpanding;
     private String mViewName;
     private float mInitialExpandY;
     private float mInitialExpandX;
@@ -2142,6 +2143,7 @@
     @VisibleForTesting
     void flingToHeight(float vel, boolean expand, float target,
             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+        mLastFlingWasExpanding = expand;
         mHeadsUpTouchHelper.notifyFling(!expand);
         mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
         setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
@@ -2531,7 +2533,7 @@
         }
         // defer touches on QQS to shade while shade is collapsing. Added margin for error
         // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
-        if (!mSplitShadeEnabled
+        if (!mSplitShadeEnabled && !mLastFlingWasExpanding
                 && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
             mShadeLog.logMotionEvent(event,
                     "handleQsTouch: shade touched while collapsing, QS tracking disabled");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 3aaad87..2cf1f53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,7 +53,6 @@
 import android.os.RemoteException;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -371,22 +370,22 @@
                 String packageName, LetterboxDetails[] letterboxDetails) { }
 
         /**
-         * @see IStatusBar#showTransient(int, int[], boolean).
+         * @see IStatusBar#showTransient(int, int, boolean).
          */
-        default void showTransient(int displayId, @InternalInsetsType int[] types) { }
+        default void showTransient(int displayId, @InsetsType int types) { }
 
         /**
-         * @see IStatusBar#showTransient(int, int[], boolean).
+         * @see IStatusBar#showTransient(int, int, boolean).
          */
-        default void showTransient(int displayId, @InternalInsetsType int[] types,
+        default void showTransient(int displayId, @InsetsType int types,
                 boolean isGestureOnSystemBar) {
             showTransient(displayId, types);
         }
 
         /**
-         * @see IStatusBar#abortTransient(int, int[]).
+         * @see IStatusBar#abortTransient(int, int).
          */
-        default void abortTransient(int displayId, @InternalInsetsType int[] types) { }
+        default void abortTransient(int displayId, @InsetsType int types) { }
 
         /**
          * Called to notify System UI that a warning about the device going to sleep
@@ -1131,17 +1130,23 @@
     }
 
     @Override
-    public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) {
+    public void showTransient(int displayId, int types, boolean isGestureOnSystemBar) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_SHOW_TRANSIENT, displayId, isGestureOnSystemBar ? 1 : 0,
-                    types).sendToTarget();
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = displayId;
+            args.argi2 = types;
+            args.argi3 = isGestureOnSystemBar ? 1 : 0;
+            mHandler.obtainMessage(MSG_SHOW_TRANSIENT, args).sendToTarget();
         }
     }
 
     @Override
-    public void abortTransient(int displayId, int[] types) {
+    public void abortTransient(int displayId, int types) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_ABORT_TRANSIENT, displayId, 0, types).sendToTarget();
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = displayId;
+            args.argi2 = types;
+            mHandler.obtainMessage(MSG_ABORT_TRANSIENT, args).sendToTarget();
         }
     }
 
@@ -1644,17 +1649,21 @@
                     args.recycle();
                     break;
                 case MSG_SHOW_TRANSIENT: {
-                    final int displayId = msg.arg1;
-                    final int[] types = (int[]) msg.obj;
-                    final boolean isGestureOnSystemBar = msg.arg2 != 0;
+                    args = (SomeArgs) msg.obj;
+                    final int displayId = args.argi1;
+                    final int types = args.argi2;
+                    final boolean isGestureOnSystemBar = args.argi3 != 0;
+                    args.recycle();
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).showTransient(displayId, types, isGestureOnSystemBar);
                     }
                     break;
                 }
                 case MSG_ABORT_TRANSIENT: {
-                    final int displayId = msg.arg1;
-                    final int[] types = (int[]) msg.obj;
+                    args = (SomeArgs) msg.obj;
+                    final int displayId = args.argi1;
+                    final int types = args.argi2;
+                    args.recycle();
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).abortTransient(displayId, types);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index fe76c7d..81c7197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -31,6 +31,7 @@
 import android.os.UserHandle
 import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
 import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS
+import android.provider.Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
 import android.util.Log
 import android.view.ContextThemeWrapper
 import android.view.View
@@ -245,6 +246,17 @@
                 datePlugin != null && weatherPlugin != null
     }
 
+    fun isWeatherEnabled(): Boolean {
+       execution.assertIsMainThread()
+       val defaultValue = context.getResources().getBoolean(
+               com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault)
+       val showWeather = secureSettings.getIntForUser(
+           LOCK_SCREEN_WEATHER_ENABLED,
+           if (defaultValue) 1 else 0,
+           userTracker.userId) == 1
+       return showWeather
+    }
+
     private fun updateBypassEnabled() {
         val bypassEnabled = bypassController.bypassEnabled
         smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 856d7de..fecaa3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.containsType;
-
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
 
@@ -36,8 +33,8 @@
 import android.os.Vibrator;
 import android.util.Log;
 import android.util.Slog;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.KeyEvent;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -168,11 +165,11 @@
     }
 
     @Override
-    public void abortTransient(int displayId, @InternalInsetsType int[] types) {
+    public void abortTransient(int displayId, @InsetsType int types) {
         if (displayId != mDisplayId) {
             return;
         }
-        if (!containsType(types, ITYPE_STATUS_BAR)) {
+        if ((types & WindowInsets.Type.statusBars()) == 0) {
             return;
         }
         mCentralSurfaces.clearTransient();
@@ -489,12 +486,11 @@
     }
 
     @Override
-    public void showTransient(int displayId, @InternalInsetsType int[] types,
-            boolean isGestureOnSystemBar) {
+    public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) {
         if (displayId != mDisplayId) {
             return;
         }
-        if (!containsType(types, ITYPE_STATUS_BAR)) {
+        if ((types & WindowInsets.Type.statusBars()) == 0) {
             return;
         }
         mCentralSurfaces.showTransientUnchecked();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index e595ddf..1966a66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -21,8 +21,6 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
@@ -100,6 +98,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
+import android.view.WindowInsets;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -943,7 +942,7 @@
         // Set up the initial notification state. This needs to happen before CommandQueue.disable()
         setUpPresenter();
 
-        if (containsType(result.mTransientBarTypes, ITYPE_STATUS_BAR)) {
+        if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
             showTransientUnchecked();
         }
         mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index e5ab473..5cf01af 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -76,6 +76,9 @@
     /** Whether user switching is currently in progress. */
     val userSwitchingInProgress: Flow<Boolean>
 
+    /** User ID of the main user. */
+    val mainUserId: Int
+
     /** User ID of the last non-guest selected user. */
     val lastSelectedNonGuestUserId: Int
 
@@ -130,7 +133,9 @@
     private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
     override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
 
-    override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+    override var mainUserId: Int = UserHandle.USER_NULL
+        private set
+    override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
         private set
 
     override val isGuestUserAutoCreated: Boolean =
@@ -172,6 +177,11 @@
                         // The guest user is always last, regardless of creation time.
                         .sortedBy { it.isGuest }
             }
+
+            if (mainUserId == UserHandle.USER_NULL) {
+                val mainUser = withContext(backgroundDispatcher) { manager.mainUser }
+                mainUser?.let { mainUserId = it.identifier }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index a374885..0a07439 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -139,11 +139,11 @@
         }
 
         applicationScope.launch {
-            var newUserId = UserHandle.USER_SYSTEM
+            var newUserId = repository.mainUserId
             if (targetUserId == UserHandle.USER_NULL) {
                 // When a target user is not specified switch to last non guest user:
                 val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
-                if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
+                if (lastSelectedNonGuestUserHandle != repository.mainUserId) {
                     val info =
                         withContext(backgroundDispatcher) {
                             manager.getUserInfo(lastSelectedNonGuestUserHandle)
@@ -215,8 +215,11 @@
             // Create a new guest in the foreground, and then immediately switch to it
             val newGuestId = create(showDialog, dismissDialog)
             if (newGuestId == UserHandle.USER_NULL) {
-                Log.e(TAG, "Could not create new guest, switching back to system user")
-                switchUser(UserHandle.USER_SYSTEM)
+                Log.e(TAG, "Could not create new guest, switching back to main user")
+                val mainUser = withContext(backgroundDispatcher) { manager.mainUser?.identifier }
+
+                mainUser?.let { switchUser(it) }
+
                 withContext(backgroundDispatcher) {
                     manager.removeUserWhenPossible(
                         UserHandle.of(currentUser.id),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 36b3f89..ccc4e4a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -300,8 +300,9 @@
         ArgumentCaptor<ContentObserver> observerCaptor =
                 ArgumentCaptor.forClass(ContentObserver.class);
         mController.init();
-        verify(mSecureSettings).registerContentObserverForUser(any(String.class),
-                anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
+        verify(mSecureSettings).registerContentObserverForUser(
+                eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+                    anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
         ContentObserver observer = observerCaptor.getValue();
         mExecutor.runAllReady();
 
@@ -347,6 +348,22 @@
         assertEquals(0, mController.getClockBottom(10));
     }
 
+    @Test
+    public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() {
+        when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+        ArgumentCaptor<ContentObserver> observerCaptor =
+                ArgumentCaptor.forClass(ContentObserver.class);
+        mController.init();
+        verify(mSecureSettings).registerContentObserverForUser(
+                eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
+                    observerCaptor.capture(), eq(UserHandle.USER_ALL));
+        ContentObserver observer = observerCaptor.getValue();
+        mExecutor.runAllReady();
+        // When a settings change has occurred, check that view is visible.
+        observer.onChange(true);
+        mExecutor.runAllReady();
+        assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
+    }
 
     private void verifyAttachment(VerificationMode times) {
         verify(mClockRegistry, times).registerClockChangeListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java
index 509d5f0..70ba306 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.navigationbar.gestural;
 
-import static android.view.InputDevice.SOURCE_TOUCHPAD;
-import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.InputDevice.SOURCE_MOUSE;
 import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET;
 import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -57,14 +57,9 @@
     @Test
     public void onTouchEvent_touchScreen_hasCorrectDisplacements() {
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 100, 100, 0);
-        // TODO: change to use classification after gesture library is ported.
-        down.setSource(SOURCE_TOUCHSCREEN);
         MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 150, 125, 0);
-        move1.setSource(SOURCE_TOUCHSCREEN);
         MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 200, 150, 0);
-        move2.setSource(SOURCE_TOUCHSCREEN);
         MotionEvent up = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 250, 175, 0);
-        up.setSource(SOURCE_TOUCHSCREEN);
 
         mMotionEventsHandler.onMotionEvent(down);
         mMotionEventsHandler.onMotionEvent(move1);
@@ -90,8 +85,8 @@
         downPointerProperties[0].id = 1;
         downPointerProperties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
         MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1,
-                downPointerProperties, downPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
-                SOURCE_TOUCHPAD, 0);
+                downPointerProperties, downPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
+                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
 
         MotionEvent.PointerCoords[] movePointerCoords1 = new MotionEvent.PointerCoords[1];
         movePointerCoords1[0] = new MotionEvent.PointerCoords();
@@ -103,8 +98,8 @@
         movePointerProperties1[0].id = 1;
         movePointerProperties1[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
         MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 1,
-                movePointerProperties1, movePointerCoords1, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD,
-                0);
+                movePointerProperties1, movePointerCoords1, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
+                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
 
         MotionEvent.PointerCoords[] movePointerCoords2 = new MotionEvent.PointerCoords[1];
         movePointerCoords2[0] = new MotionEvent.PointerCoords();
@@ -116,8 +111,8 @@
         movePointerProperties2[0].id = 1;
         movePointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
         MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 1,
-                movePointerProperties2, movePointerCoords2, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD,
-                0);
+                movePointerProperties2, movePointerCoords2, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
+                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
 
         MotionEvent.PointerCoords[] upPointerCoords = new MotionEvent.PointerCoords[1];
         upPointerCoords[0] = new MotionEvent.PointerCoords();
@@ -129,7 +124,8 @@
         upPointerProperties2[0].id = 1;
         upPointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
         MotionEvent up = MotionEvent.obtain(0, 2, MotionEvent.ACTION_UP, 1,
-                upPointerProperties2, upPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD, 0);
+                upPointerProperties2, upPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
+                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
 
         mMotionEventsHandler.onMotionEvent(down);
         mMotionEventsHandler.onMotionEvent(move1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 8440455..39c4e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -23,10 +23,14 @@
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
 import androidx.test.runner.AndroidJUnit4
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
+import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
+import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
@@ -36,8 +40,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
 /**
@@ -50,24 +54,23 @@
 @RunWith(AndroidJUnit4::class)
 internal class NoteTaskControllerTest : SysuiTestCase() {
 
-    private val notesIntent = Intent(ACTION_CREATE_NOTE)
-
     @Mock lateinit var context: Context
     @Mock lateinit var packageManager: PackageManager
-    @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
+    @Mock lateinit var resolver: NoteTaskInfoResolver
     @Mock lateinit var bubbles: Bubbles
     @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var keyguardManager: KeyguardManager
     @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
     @Mock lateinit var optionalUserManager: Optional<UserManager>
     @Mock lateinit var userManager: UserManager
+    @Mock lateinit var uiEventLogger: UiEventLogger
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         whenever(context.packageManager).thenReturn(packageManager)
-        whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
+        whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID))
         whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
         whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
@@ -77,101 +80,182 @@
     private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
         return NoteTaskController(
             context = context,
-            intentResolver = noteTaskIntentResolver,
+            resolver = resolver,
             optionalBubbles = optionalBubbles,
             optionalKeyguardManager = optionalKeyguardManager,
             optionalUserManager = optionalUserManager,
             isEnabled = isEnabled,
+            uiEventLogger = uiEventLogger,
         )
     }
 
     // region showNoteTask
     @Test
-    fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
+    fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
+            )
 
-        verify(context).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(context).startActivity(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        verifyZeroInteractions(bubbles)
+        verify(uiEventLogger)
+            .log(
+                ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
+                NOTES_UID,
+                NOTES_PACKAGE_NAME
+            )
     }
 
     @Test
-    fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
+    fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+            )
 
-        verify(bubbles).showOrHideAppBubble(notesIntent)
-        verify(context, never()).startActivity(notesIntent)
+        verifyZeroInteractions(context)
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        verify(uiEventLogger)
+            .log(
+                ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+                NOTES_UID,
+                NOTES_PACKAGE_NAME
+            )
     }
 
     @Test
-    fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+    fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
 
-        verify(context).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context)
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        verifyZeroInteractions(uiEventLogger)
+    }
+
+    @Test
+    fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = true,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+            )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        verify(context).startActivity(capture(intentCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+            assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        verifyZeroInteractions(bubbles)
+        verify(uiEventLogger)
+            .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME)
     }
 
     @Test
     fun showNoteTask_bubblesIsNull_shouldDoNothing() {
         whenever(optionalBubbles.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+            )
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
     }
 
     @Test
     fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+            )
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
     }
 
     @Test
     fun showNoteTask_userManagerIsNull_shouldDoNothing() {
         whenever(optionalUserManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+            )
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
     }
 
     @Test
     fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
-        whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
+        whenever(resolver.resolveInfo()).thenReturn(null)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+            )
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
     }
 
     @Test
     fun showNoteTask_flagDisabled_shouldDoNothing() {
-        createNoteTaskController(isEnabled = false).showNoteTask()
+        createNoteTaskController(isEnabled = false)
+            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
     }
 
     @Test
     fun showNoteTask_userIsLocked_shouldDoNothing() {
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
-        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+        createNoteTaskController()
+            .showNoteTask(
+                isInMultiWindowMode = false,
+                uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+            )
 
-        verify(context, never()).startActivity(notesIntent)
-        verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+        verifyZeroInteractions(context, bubbles, uiEventLogger)
     }
     // endregion
 
@@ -206,4 +290,9 @@
         assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
     }
     // endregion
+
+    private companion object {
+        const val NOTES_PACKAGE_NAME = "com.android.note.app"
+        const val NOTES_UID = 123456
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
new file mode 100644
index 0000000..d6495d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.notetask
+
+import android.app.role.RoleManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskInfoResolver].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskInfoResolverTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInfoResolverTest : SysuiTestCase() {
+
+    @Mock lateinit var packageManager: PackageManager
+    @Mock lateinit var roleManager: RoleManager
+
+    private lateinit var underTest: NoteTaskInfoResolver
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = NoteTaskInfoResolver(context, roleManager, packageManager)
+    }
+
+    @Test
+    fun resolveInfo_shouldReturnInfo() {
+        val packageName = "com.android.note.app"
+        val uid = 123456
+        whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user))
+            .then { listOf(packageName) }
+        whenever(
+                packageManager.getApplicationInfoAsUser(
+                    eq(packageName),
+                    any<PackageManager.ApplicationInfoFlags>(),
+                    eq(context.user)
+                )
+            )
+            .thenReturn(ApplicationInfo().apply { this.uid = uid })
+
+        val actual = underTest.resolveInfo()
+
+        requireNotNull(actual) { "Note task info must not be null" }
+        assertThat(actual.packageName).isEqualTo(packageName)
+        assertThat(actual.uid).isEqualTo(uid)
+    }
+
+    @Test
+    fun resolveInfo_packageManagerThrowsException_shouldReturnInfoWithZeroUid() {
+        val packageName = "com.android.note.app"
+        whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user))
+            .then { listOf(packageName) }
+        whenever(
+                packageManager.getApplicationInfoAsUser(
+                    eq(packageName),
+                    any<PackageManager.ApplicationInfoFlags>(),
+                    eq(context.user)
+                )
+            )
+            .thenThrow(PackageManager.NameNotFoundException(packageName))
+
+        val actual = underTest.resolveInfo()
+
+        requireNotNull(actual) { "Note task info must not be null" }
+        assertThat(actual.packageName).isEqualTo(packageName)
+        assertThat(actual.uid).isEqualTo(0)
+    }
+
+    @Test
+    fun resolveInfo_noRoleHolderIsSet_shouldReturnNull() {
+        whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskInfoResolver.ROLE_NOTES), any()))
+            .then { listOf<String>() }
+
+        val actual = underTest.resolveInfo()
+
+        assertThat(actual).isNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 010ac5b..53720ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -15,11 +15,14 @@
  */
 package com.android.systemui.notetask
 
+import android.app.KeyguardManager
 import android.test.suitebuilder.annotation.SmallTest
 import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -30,6 +33,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
 /**
@@ -55,12 +59,16 @@
         whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
     }
 
-    private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
+    private fun createNoteTaskInitializer(
+        isEnabled: Boolean = true,
+        optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(),
+    ): NoteTaskInitializer {
         return NoteTaskInitializer(
             optionalBubbles = optionalBubbles,
             noteTaskController = noteTaskController,
             commandQueue = commandQueue,
             isEnabled = isEnabled,
+            optionalKeyguardManager = optionalKeyguardManager,
         )
     }
 
@@ -105,19 +113,44 @@
 
     // region handleSystemKey
     @Test
-    fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
-        createNoteTaskInitializer()
+    fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() {
+        val keyguardManager =
+            mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) }
+        createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
             .callbacks
             .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
 
-        verify(noteTaskController).showNoteTask()
+        verify(noteTaskController)
+            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
+    }
+
+    @Test
+    fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() {
+        val keyguardManager =
+            mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) }
+        createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
+            .callbacks
+            .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
+
+        verify(noteTaskController)
+            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED)
+    }
+
+    @Test
+    fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() {
+        createNoteTaskInitializer(optionalKeyguardManager = Optional.empty())
+            .callbacks
+            .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
+
+        verify(noteTaskController)
+            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
     }
 
     @Test
     fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
         createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
 
-        verify(noteTaskController, never()).showNoteTask()
+        verifyZeroInteractions(noteTaskController)
     }
     // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
deleted file mode 100644
index 18be92b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
+++ /dev/null
@@ -1,83 +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.notetask
-
-import android.app.role.RoleManager
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.MockitoAnnotations
-
-/**
- * Tests for [NoteTaskIntentResolver].
- *
- * Build/Install/Run:
- * - atest SystemUITests:NoteTaskIntentResolverTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-internal class NoteTaskIntentResolverTest : SysuiTestCase() {
-
-    @Mock lateinit var packageManager: PackageManager
-    @Mock lateinit var roleManager: RoleManager
-
-    private lateinit var underTest: NoteTaskIntentResolver
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        underTest = NoteTaskIntentResolver(context, roleManager)
-    }
-
-    @Test
-    fun resolveIntent_shouldReturnIntentInStylusMode() {
-        val packageName = "com.android.note.app"
-        whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
-            .then { listOf(packageName) }
-
-        val actual = underTest.resolveIntent()
-
-        requireNotNull(actual) { "Intent must not be null" }
-        assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
-        assertThat(actual.`package`).isEqualTo(packageName)
-        val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
-        assertThat(expectedExtra).isEqualTo(true)
-        val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
-        assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
-    }
-
-    @Test
-    fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
-        whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
-            .then { listOf<String>() }
-
-        val actual = underTest.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index a1d42a0..cdc683f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
 import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -53,7 +53,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(noteTaskController.showNoteTask()).then {}
     }
 
     private fun createUnderTest(isEnabled: Boolean) =
@@ -96,6 +95,7 @@
 
         underTest.onTriggered(expandable = null)
 
-        verify(noteTaskController).showNoteTask()
+        verify(noteTaskController)
+            .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index b1ca1c0..f581154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -18,8 +18,6 @@
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
 import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -158,7 +156,7 @@
 
     @Test
     public void testShowTransient() {
-        int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+        int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
         mCommandQueue.showTransient(DEFAULT_DISPLAY, types, true /* isGestureOnSystemBar */);
         waitForIdleSync();
         verify(mCallbacks).showTransient(eq(DEFAULT_DISPLAY), eq(types), eq(true));
@@ -166,7 +164,7 @@
 
     @Test
     public void testShowTransientForSecondaryDisplay() {
-        int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+        int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
         mCommandQueue.showTransient(SECONDARY_DISPLAY, types, true /* isGestureOnSystemBar */);
         waitForIdleSync();
         verify(mCallbacks).showTransient(eq(SECONDARY_DISPLAY), eq(types), eq(true));
@@ -174,7 +172,7 @@
 
     @Test
     public void testAbortTransient() {
-        int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+        int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
         mCommandQueue.abortTransient(DEFAULT_DISPLAY, types);
         waitForIdleSync();
         verify(mCallbacks).abortTransient(eq(DEFAULT_DISPLAY), eq(types));
@@ -182,7 +180,7 @@
 
     @Test
     public void testAbortTransientForSecondaryDisplay() {
-        int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+        int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
         mCommandQueue.abortTransient(SECONDARY_DISPLAY, types);
         waitForIdleSync();
         verify(mCallbacks).abortTransient(eq(SECONDARY_DISPLAY), eq(types));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index ccf378a..9312643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -128,6 +128,11 @@
 
     @Test
     fun refreshUsers() = runSelfCancelingTest {
+        val mainUserId = 10
+        val mainUser = mock(UserHandle::class.java)
+        whenever(manager.mainUser).thenReturn(mainUser)
+        whenever(mainUser.identifier).thenReturn(mainUserId)
+
         underTest = create(this)
         val initialExpectedValue =
             setUpUsers(
@@ -166,6 +171,7 @@
         assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
         assertThat(selectedUserInfo?.isGuest).isTrue()
         assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+        assertThat(underTest.mainUserId).isEqualTo(mainUserId)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index fb781e8..cc23485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -204,10 +204,12 @@
         }
 
     @Test
-    fun `exit - last non-guest was removed - returns to system`() =
+    fun `exit - last non-guest was removed - returns to main user`() =
         runBlocking(IMMEDIATE) {
             val removedUserId = 310
+            val mainUserId = 10
             repository.lastSelectedNonGuestUserId = removedUserId
+            repository.mainUserId = mainUserId
             repository.setSelectedUserInfo(GUEST_USER_INFO)
 
             underTest.exit(
@@ -221,7 +223,7 @@
 
             verify(manager, never()).markGuestForDeletion(anyInt())
             verify(manager, never()).removeUser(anyInt())
-            verify(switchUser).invoke(UserHandle.USER_SYSTEM)
+            verify(switchUser).invoke(mainUserId)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1a8e244..53bb340 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -28,6 +28,10 @@
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
+    companion object {
+        // User id to represent a non system (human) user id. We presume this is the main user.
+        private const val MAIN_USER_ID = 10
+    }
 
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
@@ -43,7 +47,8 @@
     override val userSwitchingInProgress: Flow<Boolean>
         get() = _userSwitchingInProgress
 
-    override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
+    override var mainUserId: Int = MAIN_USER_ID
+    override var lastSelectedNonGuestUserId: Int = mainUserId
 
     private var _isGuestUserAutoCreated: Boolean = false
     override val isGuestUserAutoCreated: Boolean
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index cc8aec7..a4fd52d 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -39,6 +39,10 @@
                 }
             ],
             "file_patterns": ["BinaryTransparencyService\\.java"]
+        },
+        {
+            "name": "BinaryTransparencyHostTest",
+            "file_patterns": ["BinaryTransparencyService\\.java"]
         }
     ],
     "presubmit-large": [
@@ -69,11 +73,5 @@
             ],
             "file_patterns": ["ClipboardService\\.java"]
         }
-    ],
-    "postsubmit": [
-        {
-            "name": "BinaryTransparencyHostTest",
-            "file_patterns": ["BinaryTransparencyService\\.java"]
-        }
     ]
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 04c0d64..2b2de81e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3888,7 +3888,7 @@
         return runList;
     }
 
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int getLruSizeLOSP() {
         return mLruProcesses.size();
     }
@@ -3896,7 +3896,7 @@
     /**
      * Return the reference to the LRU list, call this function for read-only access
      */
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     ArrayList<ProcessRecord> getLruProcessesLOSP() {
         return mLruProcesses;
     }
@@ -3904,7 +3904,7 @@
     /**
      * Return the reference to the LRU list, call this function for read/write access
      */
-    @GuardedBy({"mService", "mProfileLock"})
+    @GuardedBy({"mService", "mProcLock"})
     ArrayList<ProcessRecord> getLruProcessesLSP() {
         return mLruProcesses;
     }
@@ -3913,12 +3913,12 @@
      * For test only
      */
     @VisibleForTesting
-    @GuardedBy({"mService", "mProfileLock"})
+    @GuardedBy({"mService", "mProcLock"})
     void setLruProcessServiceStartLSP(int pos) {
         mLruProcessServiceStart = pos;
     }
 
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int getLruProcessServiceStartLOSP() {
         return mLruProcessServiceStart;
     }
@@ -3931,7 +3931,7 @@
      *                       to most recent used ProcessRecord.
      * @param callback The callback interface to accept the current ProcessRecord.
      */
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     void forEachLruProcessesLOSP(boolean iterateForward,
             @NonNull Consumer<ProcessRecord> callback) {
         if (iterateForward) {
@@ -3956,7 +3956,7 @@
      *                 a non-null object, the search will be halted and this object will be used
      *                 as the return value of this search function.
      */
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     <R> R searchEachLruProcessesLOSP(boolean iterateForward,
             @NonNull Function<ProcessRecord, R> callback) {
         if (iterateForward) {
@@ -3977,17 +3977,17 @@
         return null;
     }
 
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     boolean isInLruListLOSP(ProcessRecord app) {
         return mLruProcesses.contains(app);
     }
 
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int getLruSeqLOSP() {
         return mLruSeq;
     }
 
-    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     MyProcessMap getProcessNamesLOSP() {
         return mProcessNames;
     }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 12784bf..60a7f93 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -104,6 +104,7 @@
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
+        DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
         DeviceConfig.NAMESPACE_HDMI_CONTROL
     };
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index 969a174..0b5c1c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -150,6 +150,13 @@
     }
 
     /**
+     * Returns if the sensor is side fps.
+     */
+    public boolean isSfps() {
+        return mSidefpsController.isPresent();
+    }
+
+    /**
      * Consumer for a biometric overlay controller.
      *
      * This behaves like a normal {@link Consumer} except that it will trap and log
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index a90679e..932c0b4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -236,8 +236,14 @@
 
     @Override
     public void onError(int errorCode, int vendorCode) {
-        super.onError(errorCode, vendorCode);
-
+        if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR
+                && vendorCode == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
+                && mSensorOverlays.isSfps()) {
+            super.onError(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+                    0 /* vendorCode */);
+        } else {
+            super.onError(errorCode, vendorCode);
+        }
         if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) {
             BiometricNotificationUtils.showBadCalibrationNotification(getContext());
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 513b3e3..cf54662 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -146,7 +146,14 @@
             }
         });
         mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
-        super.onAcquired(acquiredInfo, vendorCode);
+        if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR
+                && vendorCode == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
+                && mSensorOverlays.isSfps()) {
+            super.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
+                    0 /* vendorCode */);
+        } else {
+            super.onAcquired(acquiredInfo, vendorCode);
+        }
     }
 
     @Override
@@ -274,8 +281,5 @@
     }
 
     @Override
-    public void onPowerPressed() {
-        onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
-                0 /* vendorCode */);
-    }
+    public void onPowerPressed() { }
 }
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 29caefb..91ef167 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -733,7 +733,7 @@
 
     /**
      * Sets the display mode switching type.
-     * @param newType
+     * @param newType new mode switching type
      */
     public void setModeSwitchingType(@DisplayManager.SwitchingType int newType) {
         synchronized (mLock) {
@@ -850,6 +850,18 @@
         notifyDesiredDisplayModeSpecsChangedLocked();
     }
 
+    @GuardedBy("mLock")
+    private float getMaxRefreshRateLocked(int displayId) {
+        Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
+        float maxRefreshRate = 0f;
+        for (Display.Mode mode : modes) {
+            if (mode.getRefreshRate() > maxRefreshRate) {
+                maxRefreshRate = mode.getRefreshRate();
+            }
+        }
+        return maxRefreshRate;
+    }
+
     private void notifyDesiredDisplayModeSpecsChangedLocked() {
         if (mDesiredDisplayModeSpecsListener != null
                 && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) {
@@ -1186,29 +1198,33 @@
         // rest of low priority voters. It votes [0, max(PEAK, MIN)]
         public static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
 
+        // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
+        // rate to max value (same as for PRIORITY_UDFPS) on lock screen
+        public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
+
         // For concurrent displays we want to limit refresh rate on all displays
-        public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 8;
+        public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
 
         // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
         // Settings.Global.LOW_POWER_MODE is on.
-        public static final int PRIORITY_LOW_POWER_MODE = 9;
+        public static final int PRIORITY_LOW_POWER_MODE = 10;
 
         // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
         // higher priority voters' result is a range, it will fix the rate to a single choice.
         // It's used to avoid refresh rate switches in certain conditions which may result in the
         // user seeing the display flickering when the switches occur.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 10;
+        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
 
         // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-        public static final int PRIORITY_SKIN_TEMPERATURE = 11;
+        public static final int PRIORITY_SKIN_TEMPERATURE = 12;
 
         // The proximity sensor needs the refresh rate to be locked in order to function, so this is
         // set to a high priority.
-        public static final int PRIORITY_PROXIMITY = 12;
+        public static final int PRIORITY_PROXIMITY = 13;
 
         // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
         // to function, so this needs to be the highest priority of all votes.
-        public static final int PRIORITY_UDFPS = 13;
+        public static final int PRIORITY_UDFPS = 14;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
         // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1325,6 +1341,8 @@
                     return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
                 case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
                     return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
+                case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
+                    return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
                 default:
                     return Integer.toString(priority);
             }
@@ -2564,6 +2582,7 @@
 
     private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub {
         private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray();
+        private final SparseBooleanArray mAuthenticationPossible = new SparseBooleanArray();
 
         public void observe() {
             StatusBarManagerInternal statusBar =
@@ -2576,38 +2595,38 @@
         @Override
         public void onRequestEnabled(int displayId) {
             synchronized (mLock) {
-                updateRefreshRateStateLocked(displayId, true /*enabled*/);
+                mUdfpsRefreshRateEnabled.put(displayId, true);
+                updateVoteLocked(displayId, true, Vote.PRIORITY_UDFPS);
             }
         }
 
         @Override
         public void onRequestDisabled(int displayId) {
             synchronized (mLock) {
-                updateRefreshRateStateLocked(displayId, false /*enabled*/);
+                mUdfpsRefreshRateEnabled.put(displayId, false);
+                updateVoteLocked(displayId, false, Vote.PRIORITY_UDFPS);
             }
         }
 
-        private void updateRefreshRateStateLocked(int displayId, boolean enabled) {
-            mUdfpsRefreshRateEnabled.put(displayId, enabled);
-            updateVoteLocked(displayId);
+        @Override
+        public void onAuthenticationPossible(int displayId, boolean isPossible) {
+            synchronized (mLock) {
+                mAuthenticationPossible.put(displayId, isPossible);
+                updateVoteLocked(displayId, isPossible,
+                        Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+            }
         }
 
-        private void updateVoteLocked(int displayId) {
+        @GuardedBy("mLock")
+        private void updateVoteLocked(int displayId, boolean enabled, int votePriority) {
             final Vote vote;
-            if (mUdfpsRefreshRateEnabled.get(displayId)) {
-                Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
-                float maxRefreshRate = 0f;
-                for (Display.Mode mode : modes) {
-                    if (mode.getRefreshRate() > maxRefreshRate) {
-                        maxRefreshRate = mode.getRefreshRate();
-                    }
-                }
+            if (enabled) {
+                float maxRefreshRate = DisplayModeDirector.this.getMaxRefreshRateLocked(displayId);
                 vote = Vote.forPhysicalRefreshRates(maxRefreshRate, maxRefreshRate);
             } else {
                 vote = null;
             }
-
-            DisplayModeDirector.this.updateVoteLocked(displayId, Vote.PRIORITY_UDFPS, vote);
+            DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
@@ -2618,6 +2637,13 @@
                 final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled";
                 pw.println("      Display " + displayId + ": " + enabled);
             }
+            pw.println("    mAuthenticationPossible: ");
+            for (int i = 0; i < mAuthenticationPossible.size(); i++) {
+                final int displayId = mAuthenticationPossible.keyAt(i);
+                final String isPossible = mAuthenticationPossible.valueAt(i) ? "possible"
+                        : "impossible";
+                pw.println("      Display " + displayId + ": " + isPossible);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 6090386..1086c55 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -161,7 +161,11 @@
 
     // Indicates the position of the display, POSITION_UNKNOWN could mean it hasn't been specified,
     // or this is a virtual display etc.
-    private int mPosition = Layout.Display.POSITION_UNKNOWN;
+    private int mDevicePosition = Layout.Display.POSITION_UNKNOWN;
+
+    // Indicates that something other than the primary display device info has changed and needs to
+    // be handled in the next update.
+    private boolean mDirty = false;
 
     /**
      * The ID of the brightness throttling data that should be used. This can change e.g. in
@@ -181,11 +185,14 @@
         mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID;
     }
 
-    public void setPositionLocked(int position) {
-        mPosition = position;
+    public void setDevicePositionLocked(int position) {
+        if (mDevicePosition != position) {
+            mDevicePosition = position;
+            mDirty = true;
+        }
     }
-    public int getPositionLocked() {
-        return mPosition;
+    public int getDevicePositionLocked() {
+        return mDevicePosition;
     }
 
     /**
@@ -343,9 +350,11 @@
         // logical display that they are sharing.  (eg. Adjust size for pixel-perfect
         // mirroring over HDMI.)
         DisplayDeviceInfo deviceInfo = mPrimaryDisplayDevice.getDisplayDeviceInfoLocked();
-        if (!Objects.equals(mPrimaryDisplayDeviceInfo, deviceInfo)) {
+        if (!Objects.equals(mPrimaryDisplayDeviceInfo, deviceInfo) || mDirty) {
             mBaseDisplayInfo.layerStack = mLayerStack;
             mBaseDisplayInfo.flags = 0;
+            // Displays default to moving content to the primary display when removed
+            mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY;
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_SUPPORTS_PROTECTED_BUFFERS;
             }
@@ -443,12 +452,20 @@
             mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
             mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
 
-            if (mPosition == Layout.Display.POSITION_REAR) {
+            if (mDevicePosition == Layout.Display.POSITION_REAR) {
+                // A rear display is meant to host a specific experience that is essentially
+                // a presentation to another user or users other than the main user since they
+                // can't actually see that display. Given that, it's a suitable display for
+                // presentations but the content should be destroyed rather than moved to a non-rear
+                // display when the rear display is removed.
                 mBaseDisplayInfo.flags |= Display.FLAG_REAR;
+                mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION;
+                mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
             }
 
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
+            mDirty = false;
         }
     }
 
@@ -857,7 +874,7 @@
         pw.println("mIsEnabled=" + mIsEnabled);
         pw.println("mIsInTransition=" + mIsInTransition);
         pw.println("mLayerStack=" + mLayerStack);
-        pw.println("mPosition=" + mPosition);
+        pw.println("mPosition=" + mDevicePosition);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 56c9056..c695862 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -992,7 +992,7 @@
                 newDisplay.swapDisplaysLocked(oldDisplay);
             }
 
-            newDisplay.setPositionLocked(displayLayout.getPosition());
+            newDisplay.setDevicePositionLocked(displayLayout.getPosition());
             newDisplay.setLeadDisplayLocked(displayLayout.getLeadDisplayId());
             setLayoutLimitedRefreshRate(newDisplay, device, displayLayout);
             setEnabledLocked(newDisplay, displayLayout.isEnabled());
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 13fcff3..e3d92a7 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -92,9 +92,9 @@
         // TODO: b/186428377 update brightness setting when display changes
         mBrightnessSetting = brightnessSetting;
         mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mScreenBrightnessDefault = BrightnessUtils.clampAbsoluteBrightness(defaultScreenBrightness);
         mCurrentScreenBrightness = getScreenBrightnessSetting();
         mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
-        mScreenBrightnessDefault = BrightnessUtils.clampAbsoluteBrightness(defaultScreenBrightness);
         mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
                 displayId);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 14b9121..740d2a3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2481,6 +2481,7 @@
                         Slog.w(TAG, "Local tv device not available to change arc mode.");
                         return;
                     }
+                    tv.startArcAction(enabled);
                 }
             });
         }
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 8ee3a72..a113d01 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -103,9 +103,9 @@
         mObservers.get(uri).accept("setting changed");
     }
 
-    private boolean getBoolean(String settingName) {
+    private boolean getBoolean(String settingName, boolean defaultValue) {
         final int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
-                settingName, 0, UserHandle.USER_CURRENT);
+                settingName, defaultValue ? 1 : 0, UserHandle.USER_CURRENT);
         return setting != 0;
     }
 
@@ -127,20 +127,21 @@
 
     private void updateTouchpadNaturalScrollingEnabled() {
         mNative.setTouchpadNaturalScrollingEnabled(
-                getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING));
+                getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING, true));
     }
 
     private void updateTouchpadTapToClickEnabled() {
-        mNative.setTouchpadTapToClickEnabled(getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK));
+        mNative.setTouchpadTapToClickEnabled(
+                getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK, true));
     }
 
     private void updateTouchpadRightClickZoneEnabled() {
         mNative.setTouchpadRightClickZoneEnabled(
-                getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE));
+                getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, false));
     }
 
     private void updateShowTouches() {
-        mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES));
+        mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
 
     private void updateAccessibilityLargePointer() {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0c99e86..b4fc195 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -30,7 +30,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_SYSTEM;
-import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
@@ -1729,8 +1728,7 @@
         if (newCredential.isPattern()) {
             setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
         }
-        if (DeviceConfig.getBoolean(NAMESPACE_AUTO_PIN_CONFIRMATION,
-                "enable_auto_pin_confirmation", /* defaultValue= */ false)) {
+        if (LockPatternUtils.isAutoPinConfirmFeatureAvailable()) {
             if (newCredential.isPin()) {
                 setLong(LockPatternUtils.PIN_LENGTH, newCredential.size(), userHandle);
             }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c5bcddc..6b9be25 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1116,12 +1116,16 @@
 
             // Flag for bubble
             ActivityOptions options = ActivityOptions.fromBundle(startActivityOptions);
-            if (options != null && options.isApplyActivityFlagsForBubbles()) {
-                // Flag for bubble to make behaviour match documentLaunchMode=always.
-                intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
-                intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+            if (options != null) {
+                if (options.isApplyActivityFlagsForBubbles()) {
+                    // Flag for bubble to make behaviour match documentLaunchMode=always.
+                    intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+                    intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                }
+                if (options.isApplyMultipleTaskFlagForShortcut()) {
+                    intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                }
             }
-
             intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             intents[0].setSourceBounds(sourceBounds);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8beb0b6..002585f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3069,7 +3069,7 @@
                 getOutPrintWriter().printf("Success: user %d is already being removed\n", userId);
                 return 0;
             case UserManager.REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN:
-                getOutPrintWriter().printf("Error: user %d is a permanent admin main user\n",
+                getErrPrintWriter().printf("Error: user %d is a permanent admin main user\n",
                         userId);
                 return 1;
             default:
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index a9edce1..3cbaebe 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -64,11 +64,12 @@
     })
     public @interface UserAssignmentResult {}
 
-    private static final String PREFIX_USER_START_MODE = "USER_START_MODE_";
+    // TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively.
+    @Keep public static final int USER_START_MODE_FOREGROUND = 1;
+    @Keep public static final int USER_START_MODE_BACKGROUND = 2;
+    @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
 
-    /**
-     * Type used to indicate how a user started.
-     */
+    private static final String PREFIX_USER_START_MODE = "USER_START_MODE_";
     @IntDef(flag = false, prefix = {PREFIX_USER_START_MODE}, value = {
             USER_START_MODE_FOREGROUND,
             USER_START_MODE_BACKGROUND,
@@ -76,32 +77,6 @@
     })
     public @interface UserStartMode {}
 
-    // TODO(b/248408342): Move keep annotations below to the method referencing these fields
-    // reflectively.
-
-    /** (Full) user started on foreground (a.k.a. "current user"). */
-    @Keep public static final int USER_START_MODE_FOREGROUND = 1;
-
-    /**
-     * User (full or profile) started on background and is
-     * {@link UserManager#isUserVisible() invisible}.
-     *
-     * <p>This is the "traditional" way of starting a background user, and can be used to start
-     * profiles as well, although starting an invisible profile is not common from the System UI
-     * (it could be done through APIs or adb, though).
-     */
-    @Keep public static final int USER_START_MODE_BACKGROUND = 2;
-
-    /**
-     * User (full or profile) started on background and is
-     * {@link UserManager#isUserVisible() visible}.
-     *
-     * <p>This is the "traditional" way of starting a profile (i.e., when the profile of the current
-     * user is the current foreground user), but it can also be used to start a full user associated
-     * with a display (which is the case on automotives with passenger displays).
-     */
-    @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
-
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index fe8a500..d5cc7ca 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -42,7 +42,6 @@
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
-import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.Display;
 
@@ -56,8 +55,6 @@
 import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -80,11 +77,11 @@
  */
 public final class UserVisibilityMediator implements Dumpable {
 
-    private static final String TAG = UserVisibilityMediator.class.getSimpleName();
-
-    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
     private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
 
+    private static final String TAG = UserVisibilityMediator.class.getSimpleName();
+
     private static final String PREFIX_SECONDARY_DISPLAY_MAPPING = "SECONDARY_DISPLAY_MAPPING_";
     public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1;
     public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2;
@@ -101,7 +98,7 @@
     })
     public @interface SecondaryDisplayMappingStatus {}
 
-    // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
+    // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
     @VisibleForTesting
     static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
 
@@ -135,23 +132,10 @@
     private final SparseIntArray mExtraDisplaysAssignedToUsers;
 
     /**
-     * Mapping of each user that started visible (key) to its profile group id (value).
-     *
-     * <p>It's used to determine not just if the user is visible, but also
-     * {@link #isProfile(int, int) if it's a profile}.
+     * Mapping from each started user to its profile group.
      */
     @GuardedBy("mLock")
-    private final SparseIntArray mStartedVisibleProfileGroupIds = new SparseIntArray();
-
-    /**
-     * List of profiles that have explicitly started invisible.
-     *
-     * <p>Only used for debugging purposes (and set when {@link #DBG} is {@code true}), hence we
-     * don't care about autoboxing.
-     */
-    @GuardedBy("mLock")
-    @Nullable
-    private final List<Integer> mStartedInvisibleProfileUserIds;
+    private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
 
     /**
      * Handler user to call listeners
@@ -180,14 +164,9 @@
             mUsersAssignedToDisplayOnStart = null;
             mExtraDisplaysAssignedToUsers = null;
         }
-        mStartedInvisibleProfileUserIds = DBG ? new ArrayList<>(4) : null;
         mHandler = handler;
-        // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
-        mStartedVisibleProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
-
-        if (DBG) {
-            Slogf.i(TAG, "UserVisibilityMediator created with DBG on");
-        }
+        // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+        mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
     }
 
     /**
@@ -198,8 +177,6 @@
             int displayId) {
         Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
                 userId);
-        validateUserStartMode(userStartMode);
-
         // This method needs to perform 4 actions:
         //
         // 1. Check if the user can be started given the provided arguments
@@ -247,29 +224,14 @@
 
             visibleUsersBefore = getVisibleUsers();
 
-            // Set current user / started users state
-            switch (userStartMode) {
-                case USER_START_MODE_FOREGROUND:
-                    mCurrentUserId = userId;
-                    // Fallthrough
-                case USER_START_MODE_BACKGROUND_VISIBLE:
-                    if (DBG) {
-                        Slogf.d(TAG, "adding visible user / profile group id mapping (%d -> %d)",
-                                userId, profileGroupId);
-                    }
-                    mStartedVisibleProfileGroupIds.put(userId, profileGroupId);
-                    break;
-                case USER_START_MODE_BACKGROUND:
-                    if (mStartedInvisibleProfileUserIds != null
-                            && isProfile(userId, profileGroupId)) {
-                        Slogf.d(TAG, "adding user %d to list of invisible profiles", userId);
-                        mStartedInvisibleProfileUserIds.add(userId);
-                    }
-                    break;
-                default:
-                    Slogf.wtf(TAG,  "invalid userStartMode passed to assignUserToDisplayOnStart: "
-                            + "%d", userStartMode);
+            // Set current user / profiles state
+            if (userStartMode == USER_START_MODE_FOREGROUND) {
+                mCurrentUserId = userId;
             }
+            if (DBG) {
+                Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId);
+            }
+            mStartedProfileGroupIds.put(userId, profileGroupId);
 
             //  Set user / display state
             switch (mappingResult) {
@@ -335,46 +297,39 @@
         boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
         if (displayId != DEFAULT_DISPLAY) {
             if (foreground) {
-                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot start "
+                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
                         + "foreground user on secondary display", userId, profileGroupId,
-                        userStartModeToString(userStartMode), displayId);
+                        foreground, displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
             if (!mVisibleBackgroundUsersEnabled) {
-                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: called on "
+                Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on "
                         + "device that doesn't support multiple users on multiple displays",
-                        userId, profileGroupId, userStartModeToString(userStartMode), displayId);
+                        userId, profileGroupId, foreground, displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
         }
 
         if (isProfile(userId, profileGroupId)) {
             if (displayId != DEFAULT_DISPLAY) {
-                Slogf.w(TAG, "canStartUserLocked(%d, %d, %s, %d) failed: cannot start profile user "
-                        + "on secondary display", userId, profileGroupId,
-                        userStartModeToString(userStartMode), displayId);
+                Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user "
+                        + "on secondary display", userId, profileGroupId, foreground,
+                        displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
-            switch (userStartMode) {
-                case USER_START_MODE_FOREGROUND:
-                    Slogf.w(TAG, "startUser(%d, %d, %s, %d) failed: cannot start profile user in "
-                            + "foreground", userId, profileGroupId,
-                            userStartModeToString(userStartMode), displayId);
-                    return USER_ASSIGNMENT_RESULT_FAILURE;
-                case USER_START_MODE_BACKGROUND_VISIBLE:
-                    boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
-                    if (!isParentVisibleOnDisplay) {
-                        Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot"
-                                + " start profile user visible when its parent is not visible in "
-                                + "that display", userId, profileGroupId,
-                                userStartModeToString(userStartMode), displayId);
-                        return USER_ASSIGNMENT_RESULT_FAILURE;
-                    }
-                    return USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
-                case USER_START_MODE_BACKGROUND:
-                    return USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+            if (foreground) {
+                Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
+                        + "foreground", userId, profileGroupId, foreground, displayId);
+                return USER_ASSIGNMENT_RESULT_FAILURE;
+            } else {
+                boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
+                if (DBG) {
+                    Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay);
+                }
+                return isParentVisibleOnDisplay
+                        ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
+                        : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
             }
-
         }
 
         return foreground || displayId != DEFAULT_DISPLAY
@@ -392,9 +347,8 @@
             if (mVisibleBackgroundUserOnDefaultDisplayAllowed
                     && userStartMode == USER_START_MODE_BACKGROUND_VISIBLE) {
                 int userStartedOnDefaultDisplay = getUserStartedOnDisplay(DEFAULT_DISPLAY);
-                if (userStartedOnDefaultDisplay != USER_NULL
-                        && userStartedOnDefaultDisplay != profileGroupId) {
-                    Slogf.w(TAG, "canAssignUserToDisplayLocked(): cannot start user %d visible on"
+                if (userStartedOnDefaultDisplay != USER_NULL) {
+                    Slogf.w(TAG, "getUserVisibilityOnStartLocked(): cannot start user %d visible on"
                             + " default display because user %d already did so", userId,
                             userStartedOnDefaultDisplay);
                     return SECONDARY_DISPLAY_MAPPING_FAILED;
@@ -500,7 +454,7 @@
                         userId, displayId);
                 return false;
             }
-            if (isStartedVisibleProfileLocked(userId)) {
+            if (isStartedProfile(userId)) {
                 Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is a profile",
                         userId, displayId);
                 return false;
@@ -588,14 +542,10 @@
     @GuardedBy("mLock")
     private void unassignUserFromAllDisplaysOnStopLocked(@UserIdInt int userId) {
         if (DBG) {
-            Slogf.d(TAG, "Removing %d from mStartedVisibleProfileGroupIds (%s)", userId,
-                    mStartedVisibleProfileGroupIds);
+            Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+                    mStartedProfileGroupIds);
         }
-        mStartedVisibleProfileGroupIds.delete(userId);
-        if (mStartedInvisibleProfileUserIds != null) {
-            Slogf.d(TAG, "Removing %d from list of invisible profiles", userId);
-            mStartedInvisibleProfileUserIds.remove(Integer.valueOf(userId));
-        }
+        mStartedProfileGroupIds.delete(userId);
 
         if (!mVisibleBackgroundUsersEnabled) {
             // Don't need to update mUsersAssignedToDisplayOnStart because methods (such as
@@ -625,8 +575,7 @@
      * See {@link UserManagerInternal#isUserVisible(int)}.
      */
     public boolean isUserVisible(@UserIdInt int userId) {
-        // For optimization (as most devices don't support visible background users), check for
-        // current foreground user and their profiles first
+        // First check current foreground user and their profiles (on main display)
         if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
             if (VERBOSE) {
                 Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
@@ -635,31 +584,19 @@
         }
 
         if (!mVisibleBackgroundUsersEnabled) {
-            if (VERBOSE) {
-                Slogf.v(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when"
+            if (DBG) {
+                Slogf.d(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when"
                         + " device doesn't support visible background users", userId);
             }
             return false;
         }
 
-
+        boolean visible;
         synchronized (mLock) {
-            int profileGroupId;
-            synchronized (mLock) {
-                profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
-            }
-            if (isProfile(userId, profileGroupId)) {
-                return isUserAssignedToDisplayOnStartLocked(profileGroupId);
-            }
-            return isUserAssignedToDisplayOnStartLocked(userId);
+            visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0;
         }
-    }
-
-    @GuardedBy("mLock")
-    private boolean isUserAssignedToDisplayOnStartLocked(@UserIdInt int userId) {
-        boolean visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0;
-        if (VERBOSE) {
-            Slogf.v(TAG, "isUserAssignedToDisplayOnStartLocked(%d): %b", userId, visible);
+        if (DBG) {
+            Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
         }
         return visible;
     }
@@ -672,8 +609,7 @@
             return false;
         }
 
-        // For optimization (as most devices don't support visible background users), check for
-        // current user and profile first. Current user is always visible on:
+        // Current user is always visible on:
         // - Default display
         // - Secondary displays when device doesn't support visible bg users
         //   - Or when explicitly added (which is checked below)
@@ -695,26 +631,14 @@
         }
 
         synchronized (mLock) {
-            int profileGroupId;
-            synchronized (mLock) {
-                profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+            if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
+                // User assigned to display on start
+                return true;
             }
-            if (isProfile(userId, profileGroupId)) {
-                return isFullUserVisibleOnBackgroundLocked(profileGroupId, displayId);
-            }
-            return isFullUserVisibleOnBackgroundLocked(userId, displayId);
-        }
-    }
 
-    // NOTE: it doesn't check if the userId is a full user, it's up to the caller to check that
-    @GuardedBy("mLock")
-    private boolean isFullUserVisibleOnBackgroundLocked(@UserIdInt int userId, int displayId) {
-        if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
-            // User assigned to display on start
-            return true;
+            // Check for extra display assignment
+            return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId;
         }
-        // Check for extra display assignment
-        return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId;
     }
 
     /**
@@ -782,7 +706,7 @@
                     continue;
                 }
                 int userId = mUsersAssignedToDisplayOnStart.keyAt(i);
-                if (!isStartedVisibleProfileLocked(userId)) {
+                if (!isStartedProfile(userId)) {
                     return userId;
                 } else if (DBG) {
                     Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
@@ -815,8 +739,8 @@
         // number of users is too small, the gain is probably not worth the increase on complexity.
         IntArray visibleUsers = new IntArray();
         synchronized (mLock) {
-            for (int i = 0; i < mStartedVisibleProfileGroupIds.size(); i++) {
-                int userId = mStartedVisibleProfileGroupIds.keyAt(i);
+            for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
+                int userId = mStartedProfileGroupIds.keyAt(i);
                 if (isUserVisible(userId)) {
                     visibleUsers.add(userId);
                 }
@@ -849,7 +773,7 @@
         }
     }
 
-    // TODO(b/266158156): remove this method if not needed anymore
+    // TODO(b/242195409): remove this method if not needed anymore
     /**
      * Nofify all listeners that the system user visibility changed.
      */
@@ -911,9 +835,6 @@
         ipw.println("UserVisibilityMediator");
         ipw.increaseIndent();
 
-        ipw.print("DBG: ");
-        ipw.println(DBG);
-
         synchronized (mLock) {
             ipw.print("Current user id: ");
             ipw.println(mCurrentUserId);
@@ -921,12 +842,8 @@
             ipw.print("Visible users: ");
             ipw.println(getVisibleUsers());
 
-            dumpSparseIntArray(ipw, mStartedVisibleProfileGroupIds,
-                    "started visible user / profile group", "u", "pg");
-            if (mStartedInvisibleProfileUserIds != null) {
-                ipw.print("Profiles started invisible: ");
-                ipw.println(mStartedInvisibleProfileUserIds);
-            }
+            dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
+                    "u", "pg");
 
             ipw.print("Supports visible background users on displays: ");
             ipw.println(mVisibleBackgroundUsersEnabled);
@@ -1034,25 +951,22 @@
             if (mCurrentUserId == userId) {
                 return true;
             }
-            return mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID)
-                    == mCurrentUserId;
+            return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId;
         }
     }
 
-    @GuardedBy("mLock")
-    private boolean isStartedVisibleProfileLocked(@UserIdInt int userId) {
-        int profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+    private boolean isStartedProfile(@UserIdInt int userId) {
+        int profileGroupId;
+        synchronized (mLock) {
+            profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+        }
         return isProfile(userId, profileGroupId);
     }
 
-    private void validateUserStartMode(@UserStartMode int userStartMode) {
-        switch (userStartMode) {
-            case USER_START_MODE_FOREGROUND:
-            case USER_START_MODE_BACKGROUND:
-            case USER_START_MODE_BACKGROUND_VISIBLE:
-                return;
+    private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
         }
-        throw new IllegalArgumentException("Invalid user start mode: " + userStartMode);
     }
 
     private static String secondaryDisplayMappingStatusToString(
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 2fbf3fb..751f535 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -79,7 +79,12 @@
     private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750;
 
     // Delay for clearing out battery stats for UIDs corresponding to a removed user
-    public static final int UID_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000;
+    public static final int UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 2_000;
+
+    // Delay for the _final_ clean-up of battery stats after a user removal - just in case
+    // some UIDs took longer than UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS to
+    // stop running.
+    public static final int UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000;
 
     private final ScheduledExecutorService mExecutorService =
             Executors.newSingleThreadScheduledExecutor(
@@ -336,11 +341,20 @@
     @Override
     public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
         synchronized (BatteryExternalStatsWorker.this) {
+            // Initial quick clean-up after a user removal
+            mExecutorService.schedule(() -> {
+                synchronized (mStats) {
+                    mStats.clearRemovedUserUidsLocked(userId);
+                }
+            }, UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
+
+            // Final clean-up after a user removal, to take care of UIDs that were running longer
+            // than expected
             return mExecutorService.schedule(() -> {
                 synchronized (mStats) {
                     mStats.clearRemovedUserUidsLocked(userId);
                 }
-            }, UID_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
+            }, UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS);
         }
     }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 5521384..ec052ec 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -21,7 +21,6 @@
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -162,11 +161,10 @@
             LetterboxDetails[] letterboxDetails);
 
     /** @see com.android.internal.statusbar.IStatusBar#showTransient */
-    void showTransient(int displayId, @InternalInsetsType int[] types,
-            boolean isGestureOnSystemBar);
+    void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar);
 
     /** @see com.android.internal.statusbar.IStatusBar#abortTransient */
-    void abortTransient(int displayId, @InternalInsetsType int[] types);
+    void abortTransient(int displayId, @InsetsType int types);
 
     /**
      * @see com.android.internal.statusbar.IStatusBar#showToast(String, IBinder, CharSequence,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 83f4805..4489ba9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -79,12 +79,10 @@
 import android.service.quicksettings.TileService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
@@ -645,7 +643,7 @@
         }
 
         @Override
-        public void showTransient(int displayId, @InternalInsetsType int[] types,
+        public void showTransient(int displayId, @InsetsType int types,
                 boolean isGestureOnSystemBar) {
             getUiState(displayId).showTransient(types);
             if (mBar != null) {
@@ -656,7 +654,7 @@
         }
 
         @Override
-        public void abortTransient(int displayId, @InternalInsetsType int[] types) {
+        public void abortTransient(int displayId, @InsetsType int types) {
             getUiState(displayId).clearTransient(types);
             if (mBar != null) {
                 try {
@@ -1258,7 +1256,7 @@
     private static class UiState {
         private @Appearance int mAppearance = 0;
         private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
-        private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
+        private @InsetsType int mTransientBarTypes;
         private boolean mNavbarColorManagedByIme = false;
         private @Behavior int mBehavior;
         private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@@ -1285,16 +1283,12 @@
             mLetterboxDetails = letterboxDetails;
         }
 
-        private void showTransient(@InternalInsetsType int[] types) {
-            for (int type : types) {
-                mTransientBarTypes.add(type);
-            }
+        private void showTransient(@InsetsType int types) {
+            mTransientBarTypes |= types;
         }
 
-        private void clearTransient(@InternalInsetsType int[] types) {
-            for (int type : types) {
-                mTransientBarTypes.remove(type);
-            }
+        private void clearTransient(@InsetsType int types) {
+            mTransientBarTypes &= ~types;
         }
 
         private int getDisabled1() {
@@ -1410,16 +1404,12 @@
             // TODO(b/118592525): Currently, status bar only works on the default display.
             // Make it aware of multi-display if needed.
             final UiState state = mDisplayUiState.get(DEFAULT_DISPLAY);
-            final int[] transientBarTypes = new int[state.mTransientBarTypes.size()];
-            for (int i = 0; i < transientBarTypes.length; i++) {
-                transientBarTypes[i] = state.mTransientBarTypes.valueAt(i);
-            }
             return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1),
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
                     state.mImeBackDisposition, state.mShowImeSwitcher,
                     gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
                     state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
-                    state.mPackageName, transientBarTypes, state.mLetterboxDetails);
+                    state.mPackageName, state.mTransientBarTypes, state.mLetterboxDetails);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 65127e4..8ce8c46 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -342,6 +342,18 @@
         // Not relevant for the window observer.
     }
 
+    void onWMTransition(int displayId, @WindowManager.TransitionType int type) {
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition",
+                    FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; type=" + type);
+        }
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.onWMTransition(displayId, type);
+        }
+        // Not relevant for the window observer.
+    }
+
     void onWindowTransition(WindowState windowState, int transition) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
                 | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -708,6 +720,28 @@
             }
         }
 
+        void onWMTransition(int displayId, @WindowManager.TransitionType int type) {
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".onWMTransition",
+                        FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; type=" + type);
+            }
+            if (DEBUG_WINDOW_TRANSITIONS) {
+                Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type)
+                        + " displayId: " + displayId);
+            }
+            final boolean magnifying = mMagnifedViewport.isMagnifying();
+            if (magnifying) {
+                // All opening/closing situations.
+                switch (type) {
+                    case WindowManager.TRANSIT_OPEN:
+                    case WindowManager.TRANSIT_TO_FRONT:
+                    case WindowManager.TRANSIT_CLOSE:
+                    case WindowManager.TRANSIT_TO_BACK:
+                        mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
+                }
+            }
+        }
+
         void onWindowTransition(WindowState windowState, int transition) {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".onWindowTransition",
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 50eb356..c37a3d7 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1033,10 +1033,33 @@
             return err;
         }
 
-        boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
-                requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
-                request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
-                resultRootTask);
+        boolean abort;
+        try {
+            abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
+                    requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
+                    request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
+                    resultRootTask);
+        } catch (SecurityException e) {
+            // Return activity not found for the explicit intent if the caller can't see the target
+            // to prevent the disclosure of package existence.
+            final Intent originalIntent = request.ephemeralIntent;
+            if (originalIntent != null && (originalIntent.getComponent() != null
+                    || originalIntent.getPackage() != null)) {
+                final String targetPackageName = originalIntent.getComponent() != null
+                        ? originalIntent.getComponent().getPackageName()
+                        : originalIntent.getPackage();
+                if (mService.getPackageManagerInternalLocked()
+                        .filterAppAccess(targetPackageName, callingUid, userId)) {
+                    if (resultRecord != null) {
+                        resultRecord.sendResult(INVALID_UID, resultWho, requestCode,
+                                RESULT_CANCELED, null /* data */, null /* dataGrants */);
+                    }
+                    SafeActivityOptions.abort(options);
+                    return ActivityManager.START_CLASS_NOT_FOUND;
+                }
+            }
+            throw e;
+        }
         abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                 callingPid, resolvedType, aInfo.applicationInfo);
         abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index cd79f2e..e1fdeca1 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -32,6 +32,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.BackgroundStartPrivileges;
+import android.app.ComponentOptions;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -166,7 +167,8 @@
         final boolean useCallingUidState =
                 originatingPendingIntent == null
                         || checkedOptions == null
-                        || !checkedOptions.getIgnorePendingIntentCreatorForegroundState();
+                        || checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                                != ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
         if (useCallingUidState) {
             if (callingUid == Process.ROOT_UID
                     || callingAppId == Process.SYSTEM_UID
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d93a62d..9b643c5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -47,8 +47,6 @@
 import static android.view.Display.STATE_UNKNOWN;
 import static android.view.Display.isSuspendedState;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
-import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -57,6 +55,7 @@
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -174,6 +173,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -221,7 +221,6 @@
 import android.view.InputDevice;
 import android.view.InsetsSource;
 import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.MagnificationSpec;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RemoteAnimationDefinition;
@@ -248,7 +247,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
-import com.android.internal.util.function.TriConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -466,6 +464,8 @@
     private boolean mSystemGestureExclusionWasRestricted = false;
     private final Region mSystemGestureExclusionUnrestricted = new Region();
     private int mSystemGestureExclusionLimit;
+    private final Rect mSystemGestureFrameLeft = new Rect();
+    private final Rect mSystemGestureFrameRight = new Rect();
 
     private Set<Rect> mRestrictedKeepClearAreas = new ArraySet<>();
     private Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
@@ -1519,31 +1519,6 @@
         return mDisplayRotation;
     }
 
-    void setInsetProvider(@InternalInsetsType int type, WindowContainer win,
-            @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider) {
-        setInsetProvider(type, win, frameProvider, null /* overrideFrameProviders */);
-    }
-
-    /**
-     * Marks a window as providing insets for the rest of the windows in the system.
-     *
-     * @param type The type of inset this window provides.
-     * @param win The window.
-     * @param frameProvider Function to compute the frame, or {@code null} if the just the frame of
-     *                      the window should be taken. Only for non-WindowState providers, nav bar
-     *                      and status bar.
-     * @param overrideFrameProviders Functions to compute the frame when dispatching insets to the
-     *                               given window types, or {@code null} if the normal frame should
-     *                               be taken.
-     */
-    void setInsetProvider(@InternalInsetsType int type, WindowContainer win,
-            @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider,
-            @Nullable SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
-                    overrideFrameProviders) {
-        mInsetsStateController.getSourceProvider(type).setWindowContainer(win, frameProvider,
-                overrideFrameProviders);
-    }
-
     InsetsStateController getInsetsStateController() {
         return mInsetsStateController;
     }
@@ -4048,7 +4023,7 @@
             final int imePid = mInputMethodWindow.mSession.mPid;
             mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer);
         }
-        mInsetsStateController.getSourceProvider(ID_IME).setWindowContainer(win,
+        mInsetsStateController.getImeSourceProvider().setWindowContainer(win,
                 mDisplayPolicy.getImeSourceFrameProvider(), null);
         computeImeTarget(true /* updateImeTarget */);
         updateImeControlTarget();
@@ -5719,10 +5694,12 @@
         final Region unhandled = Region.obtain();
         unhandled.set(0, 0, mDisplayFrames.mWidth, mDisplayFrames.mHeight);
 
-        final Rect leftEdge = mInsetsStateController.getSourceProvider(ITYPE_LEFT_GESTURES)
-                .getSource().getFrame();
-        final Rect rightEdge = mInsetsStateController.getSourceProvider(ITYPE_RIGHT_GESTURES)
-                .getSource().getFrame();
+        final InsetsState state = mInsetsStateController.getRawInsetsState();
+        final Rect df = state.getDisplayFrame();
+        final Insets gestureInsets = state.calculateInsets(df, systemGestures(),
+                false /* ignoreVisibility */);
+        mSystemGestureFrameLeft.set(df.left, df.top, gestureInsets.left, df.bottom);
+        mSystemGestureFrameRight.set(gestureInsets.right, df.top, df.right, df.bottom);
 
         final Region touchableRegion = Region.obtain();
         final Region local = Region.obtain();
@@ -5766,25 +5743,25 @@
             if (needsGestureExclusionRestrictions(w, false /* ignoreRequest */)) {
 
                 // Processes the region along the left edge.
-                remainingLeftRight[0] = addToGlobalAndConsumeLimit(local, outExclusion, leftEdge,
-                        remainingLeftRight[0], w, EXCLUSION_LEFT);
+                remainingLeftRight[0] = addToGlobalAndConsumeLimit(local, outExclusion,
+                        mSystemGestureFrameLeft, remainingLeftRight[0], w, EXCLUSION_LEFT);
 
                 // Processes the region along the right edge.
-                remainingLeftRight[1] = addToGlobalAndConsumeLimit(local, outExclusion, rightEdge,
-                        remainingLeftRight[1], w, EXCLUSION_RIGHT);
+                remainingLeftRight[1] = addToGlobalAndConsumeLimit(local, outExclusion,
+                        mSystemGestureFrameRight, remainingLeftRight[1], w, EXCLUSION_RIGHT);
 
                 // Adds the middle (unrestricted area)
                 final Region middle = Region.obtain(local);
-                middle.op(leftEdge, Op.DIFFERENCE);
-                middle.op(rightEdge, Op.DIFFERENCE);
+                middle.op(mSystemGestureFrameLeft, Op.DIFFERENCE);
+                middle.op(mSystemGestureFrameRight, Op.DIFFERENCE);
                 outExclusion.op(middle, Op.UNION);
                 middle.recycle();
             } else {
                 boolean loggable = needsGestureExclusionRestrictions(w, true /* ignoreRequest */);
                 if (loggable) {
-                    addToGlobalAndConsumeLimit(local, outExclusion, leftEdge,
+                    addToGlobalAndConsumeLimit(local, outExclusion, mSystemGestureFrameLeft,
                             Integer.MAX_VALUE, w, EXCLUSION_LEFT);
-                    addToGlobalAndConsumeLimit(local, outExclusion, rightEdge,
+                    addToGlobalAndConsumeLimit(local, outExclusion, mSystemGestureFrameRight,
                             Integer.MAX_VALUE, w, EXCLUSION_RIGHT);
                 }
                 outExclusion.op(local, Op.UNION);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e87680a..3c4d706 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -171,9 +171,8 @@
     /** Use the transit animation in style resource (see {@link #selectAnimation}). */
     static final int ANIMATION_STYLEABLE = 0;
 
-    private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR,
-            ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
-    private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
+    private static final int SHOW_TYPES_FOR_SWIPE = Type.statusBars() | Type.navigationBars();
+    private static final int SHOW_TYPES_FOR_PANIC = Type.navigationBars();
 
     private final WindowManagerService mService;
     private final Context mContext;
@@ -251,7 +250,7 @@
 
     private boolean mIsFreeformWindowOverlappingWithNavBar;
 
-    private boolean mLastImmersiveMode;
+    private boolean mIsImmersiveMode;
 
     // The windows we were told about in focusChanged.
     private WindowState mFocusedWindow;
@@ -1077,8 +1076,16 @@
                 } else {
                     overrideProviders = null;
                 }
-                mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
-                        overrideProviders);
+                // TODO (b/234093736): Let InsetsFrameProvider have the following fields:
+                //                     - IBinder owner.
+                //                     - int index.
+                //                     - @InsetsType int type.
+                //                     So we can create the id by using InsetsSource#createId.
+                //                     And we won't need toPublicType anymore.
+                final int id = provider.type;
+                final @InsetsType int type = InsetsState.toPublicType(id);
+                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type)
+                        .setWindowContainer(win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
             }
         }
@@ -1171,10 +1178,17 @@
             mLastFocusedWindow = null;
         }
 
-        final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
-        for (int index = sources.size() - 1; index >= 0; index--) {
-            final @InternalInsetsType int type = sources.keyAt(index);
-            mDisplayContent.setInsetProvider(type, null /* win */, null /* frameProvider */);
+        if (win.hasInsetsSourceProvider()) {
+            final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
+            final InsetsStateController controller = mDisplayContent.getInsetsStateController();
+            for (int index = providers.size() - 1; index >= 0; index--) {
+                final InsetsSourceProvider provider = providers.valueAt(index);
+                provider.setWindowContainer(
+                        null /* windowContainer */,
+                        null /* frameProvider */,
+                        null /* overrideFrameProviders */);
+                controller.removeSourceProvider(provider.getSource().getId());
+            }
         }
         mInsetsSourceWindowsExceptIme.remove(win);
     }
@@ -1243,7 +1257,6 @@
      * some temporal states, but doesn't change the window frames used to show on screen.
      */
     void simulateLayoutDisplay(DisplayFrames displayFrames) {
-        final InsetsStateController controller = mDisplayContent.getInsetsStateController();
         sTmpClientFrames.attachedFrame = null;
         for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
             final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
@@ -1252,11 +1265,10 @@
                     displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
                     UNSPECIFIED_LENGTH, win.getRequestedVisibleTypes(), win.mGlobalScale,
                     sTmpClientFrames);
-            final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
+            final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
             final InsetsState state = displayFrames.mInsetsState;
-            for (int index = sources.size() - 1; index >= 0; index--) {
-                final int type = sources.keyAt(index);
-                state.addSource(controller.getSourceProvider(type).createSimulatedSource(
+            for (int index = providers.size() - 1; index >= 0; index--) {
+                state.addSource(providers.valueAt(index).createSimulatedSource(
                         displayFrames, sTmpClientFrames.frame));
             }
         }
@@ -1359,30 +1371,33 @@
             mIsFreeformWindowOverlappingWithNavBar = true;
         }
 
-        final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
-        final Rect bounds = win.getBounds();
-        for (int index = sources.size() - 1; index >= 0; index--) {
-            final InsetsSource source = sources.valueAt(index);
-            if ((source.getType()
-                    & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) {
-                continue;
-            }
-            if (mLeftGestureHost != null && mTopGestureHost != null
-                    && mRightGestureHost != null && mBottomGestureHost != null) {
-                continue;
-            }
-            final Insets insets = source.calculateInsets(bounds, false /* ignoreVisibility */);
-            if (mLeftGestureHost == null && insets.left > 0) {
-                mLeftGestureHost = win;
-            }
-            if (mTopGestureHost == null && insets.top > 0) {
-                mTopGestureHost = win;
-            }
-            if (mRightGestureHost == null && insets.right > 0) {
-                mRightGestureHost = win;
-            }
-            if (mBottomGestureHost == null && insets.bottom > 0) {
-                mBottomGestureHost = win;
+        if (win.hasInsetsSourceProvider()) {
+            final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
+            final Rect bounds = win.getBounds();
+            for (int index = providers.size() - 1; index >= 0; index--) {
+                final InsetsSourceProvider provider = providers.valueAt(index);
+                final InsetsSource source = provider.getSource();
+                if ((source.getType()
+                        & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) {
+                    continue;
+                }
+                if (mLeftGestureHost != null && mTopGestureHost != null
+                        && mRightGestureHost != null && mBottomGestureHost != null) {
+                    continue;
+                }
+                final Insets insets = source.calculateInsets(bounds, false /* ignoreVisibility */);
+                if (mLeftGestureHost == null && insets.left > 0) {
+                    mLeftGestureHost = win;
+                }
+                if (mTopGestureHost == null && insets.top > 0) {
+                    mTopGestureHost = win;
+                }
+                if (mRightGestureHost == null && insets.right > 0) {
+                    mRightGestureHost = win;
+                }
+                if (mBottomGestureHost == null && insets.bottom > 0) {
+                    mBottomGestureHost = win;
+                }
             }
         }
 
@@ -2171,14 +2186,27 @@
         appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible,
                 freeformRootTaskVisible);
 
+        // Show immersive mode confirmation if needed.
+        final boolean wasImmersiveMode = mIsImmersiveMode;
+        final boolean isImmersiveMode = isImmersiveMode(win);
+        if (wasImmersiveMode != isImmersiveMode) {
+            mIsImmersiveMode = isImmersiveMode;
+            // The immersive confirmation window should be attached to the immersive window root.
+            final RootDisplayArea root = win.getRootDisplayArea();
+            final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId;
+            mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, isImmersiveMode,
+                    mService.mPolicy.isUserSetupComplete(),
+                    isNavBarEmpty(disableFlags));
+        }
+
+        // Show transient bars for panic if needed.
         final boolean requestHideNavBar = !win.isRequestedVisible(Type.navigationBars());
         final long now = SystemClock.uptimeMillis();
         final boolean pendingPanic = mPendingPanicGestureUptime != 0
                 && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
         final DisplayPolicy defaultDisplayPolicy =
                 mService.getDefaultDisplayContentLocked().getDisplayPolicy();
-        if (pendingPanic && requestHideNavBar && win != mNotificationShade
-                && getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)
+        if (pendingPanic && requestHideNavBar && isImmersiveMode
                 // TODO (b/111955725): Show keyguard presentation on all external displays
                 && defaultDisplayPolicy.isKeyguardDrawComplete()) {
             // The user performed the panic gesture recently, we're about to hide the bars,
@@ -2190,19 +2218,6 @@
             }
         }
 
-        // update navigation bar
-        boolean oldImmersiveMode = mLastImmersiveMode;
-        boolean newImmersiveMode = isImmersiveMode(win);
-        if (oldImmersiveMode != newImmersiveMode) {
-            mLastImmersiveMode = newImmersiveMode;
-            // The immersive confirmation window should be attached to the immersive window root.
-            final RootDisplayArea root = win.getRootDisplayArea();
-            final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId;
-            mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, newImmersiveMode,
-                    mService.mPolicy.isUserSetupComplete(),
-                    isNavBarEmpty(disableFlags));
-        }
-
         return appearance;
     }
 
@@ -2324,18 +2339,10 @@
         if (win == null) {
             return false;
         }
-        return getNavigationBar() != null
-                && canHideNavigationBar()
-                && getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)
-                && win != getNotificationShade()
-                && !win.isActivityTypeDream();
-    }
-
-    /**
-     * @return whether the navigation bar can be hidden, e.g. the device has a navigation bar
-     */
-    private boolean canHideNavigationBar() {
-        return hasNavigationBar();
+        if (win == getNotificationShade() || win.isActivityTypeDream()) {
+            return false;
+        }
+        return getInsetsPolicy().hasHiddenSources(Type.navigationBars());
     }
 
     private static boolean isNavBarEmpty(int systemUiFlags) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index bd82113..97dd291 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -26,8 +26,6 @@
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -41,8 +39,6 @@
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.res.Resources;
-import android.util.ArrayMap;
-import android.util.IntArray;
 import android.util.SparseArray;
 import android.view.InsetsAnimationControlCallbacks;
 import android.view.InsetsAnimationControlImpl;
@@ -52,7 +48,6 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.InternalInsetsAnimationController;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
@@ -81,7 +76,6 @@
     private final InsetsStateController mStateController;
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mPolicy;
-    private final IntArray mShowingTransientTypes = new IntArray();
 
     /** For resetting visibilities of insets sources. */
     private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
@@ -95,7 +89,7 @@
                 return;
             }
             for (InsetsSourceControl control : controls) {
-                if (mShowingTransientTypes.indexOf(control.getId()) != -1) {
+                if (isTransient(control.getType())) {
                     // The visibilities of transient bars will be handled with animations.
                     continue;
                 }
@@ -117,13 +111,16 @@
     };
 
     private WindowState mFocusedWin;
-    private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
-    private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
+    private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
+    private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
+    private @InsetsType int mShowingTransientTypes;
     private boolean mAnimatingShown;
+
     /**
      * Let remote insets controller control system bars regardless of other settings.
      */
     private boolean mRemoteInsetsControllerControlsSystemBars;
+
     private final boolean mHideNavBarForKeyboard;
     private final float[] mTmpFloat9 = new float[9];
 
@@ -178,37 +175,46 @@
         mNavBar.updateVisibility(navControlTarget, Type.navigationBars());
     }
 
-    boolean isHidden(@InternalInsetsType int type) {
-        final WindowContainerInsetsSourceProvider provider = mStateController
-                .peekSourceProvider(type);
-        return provider != null && provider.hasWindowContainer()
-                && !provider.getSource().isVisible();
+    boolean hasHiddenSources(@InsetsType int types) {
+        final InsetsState state = mStateController.getRawInsetsState();
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            if ((source.getType() & types) == 0) {
+                continue;
+            }
+            if (!source.getFrame().isEmpty() && !source.isVisible()) {
+                return true;
+            }
+        }
+        return false;
     }
 
-    void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) {
-        boolean changed = false;
-        for (int i = types.length - 1; i >= 0; i--) {
-            final @InternalInsetsType int type = types[i];
-            if (!isHidden(type)) {
+    void showTransient(@InsetsType int types, boolean isGestureOnSystemBar) {
+        @InsetsType int showingTransientTypes = mShowingTransientTypes;
+        final InsetsState rawState = mStateController.getRawInsetsState();
+        for (int i = rawState.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = rawState.sourceAt(i);
+            if (source.isVisible()) {
                 continue;
             }
-            if (mShowingTransientTypes.indexOf(type) != -1) {
+            final @InsetsType int type = source.getType();
+            if ((source.getType() & types) == 0) {
                 continue;
             }
-            mShowingTransientTypes.add(type);
-            changed = true;
+            showingTransientTypes |= type;
         }
-        if (changed) {
+        if (mShowingTransientTypes != showingTransientTypes) {
+            mShowingTransientTypes = showingTransientTypes;
             StatusBarManagerInternal statusBarManagerInternal =
                     mPolicy.getStatusBarManagerInternal();
             if (statusBarManagerInternal != null) {
                 statusBarManagerInternal.showTransient(mDisplayContent.getDisplayId(),
-                        mShowingTransientTypes.toArray(), isGestureOnSystemBar);
+                        showingTransientTypes, isGestureOnSystemBar);
             }
             updateBarControlTarget(mFocusedWin);
             dispatchTransientSystemBarsVisibilityChanged(
                     mFocusedWin,
-                    isTransient(ITYPE_STATUS_BAR) || isTransient(ITYPE_NAVIGATION_BAR),
+                    (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0,
                     isGestureOnSystemBar);
 
             // The leashes can be created while updating bar control target. The surface transaction
@@ -224,7 +230,7 @@
     }
 
     void hideTransient() {
-        if (mShowingTransientTypes.size() == 0) {
+        if (mShowingTransientTypes == 0) {
             return;
         }
 
@@ -235,20 +241,25 @@
 
         startAnimation(false /* show */, () -> {
             synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
+                final SparseArray<WindowContainerInsetsSourceProvider> providers =
+                        mStateController.getSourceProviders();
+                for (int i = providers.size() - 1; i >= 0; i--) {
+                    final WindowContainerInsetsSourceProvider provider = providers.valueAt(i);
+                    if (!isTransient(provider.getSource().getType())) {
+                        continue;
+                    }
                     // We are about to clear mShowingTransientTypes, we don't want the transient bar
                     // can cause insets on the client. Restore the client visibility.
-                    final @InternalInsetsType int type = mShowingTransientTypes.get(i);
-                    mStateController.getSourceProvider(type).setClientVisible(false);
+                    provider.setClientVisible(false);
                 }
-                mShowingTransientTypes.clear();
+                mShowingTransientTypes = 0;
                 updateBarControlTarget(mFocusedWin);
             }
         });
     }
 
-    boolean isTransient(@InternalInsetsType int type) {
-        return mShowingTransientTypes.indexOf(type) != -1;
+    boolean isTransient(@InsetsType int type) {
+        return (mShowingTransientTypes & type) != 0;
     }
 
     /**
@@ -280,9 +291,9 @@
                 ? token.getFixedRotationTransformInsetsState()
                 : mStateController.getRawInsetsState();
         outInsetsState.set(srcState, true /* copySources */);
-        for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
-            final InsetsSource source = outInsetsState.peekSource(mShowingTransientTypes.get(i));
-            if (source != null) {
+        for (int i = outInsetsState.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = outInsetsState.sourceAt(i);
+            if (isTransient(source.getType())) {
                 source.setVisible(false);
             }
         }
@@ -333,8 +344,8 @@
             }
         }
 
-        final ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
-                .getSourceProviders();
+        final SparseArray<WindowContainerInsetsSourceProvider> providers =
+                mStateController.getSourceProviders();
         final int windowType = attrs.type;
         for (int i = providers.size() - 1; i >= 0; i--) {
             final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
@@ -365,18 +376,17 @@
 
     private InsetsState adjustVisibilityForTransientTypes(InsetsState originalState) {
         InsetsState state = originalState;
-        for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
-            final @InternalInsetsType int type = mShowingTransientTypes.get(i);
-            final InsetsSource originalSource = state.peekSource(type);
-            if (originalSource != null && originalSource.isVisible()) {
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            if (isTransient(source.getType()) && source.isVisible()) {
                 if (state == originalState) {
                     // The source will be modified, create a non-deep copy to store the new one.
                     state = new InsetsState(originalState);
                 }
                 // Replace the source with a copy in invisible state.
-                final InsetsSource source = new InsetsSource(originalSource);
-                source.setVisible(false);
-                state.addSource(source);
+                final InsetsSource outSource = new InsetsSource(source);
+                outSource.setVisible(false);
+                state.addSource(outSource);
             }
         }
         return state;
@@ -385,18 +395,23 @@
     private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
             boolean copyState) {
         if (w.mIsImWindow) {
+            InsetsState state = originalState;
             // If navigation bar is not hidden by IME, IME should always receive visible
             // navigation bar insets.
             final boolean navVisible = !mHideNavBarForKeyboard;
-            final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
-            if (originalNavSource != null && originalNavSource.isVisible() != navVisible) {
-                final InsetsState state = copyState ? new InsetsState(originalState)
-                        : originalState;
-                final InsetsSource navSource = new InsetsSource(originalNavSource);
+            for (int i = originalState.sourceSize() - 1; i >= 0; i--) {
+                final InsetsSource source = originalState.sourceAt(i);
+                if (source.getType() != Type.navigationBars() || source.isVisible() == navVisible) {
+                    continue;
+                }
+                if (state == originalState && copyState) {
+                    state = new InsetsState(originalState);
+                }
+                final InsetsSource navSource = new InsetsSource(source);
                 navSource.setVisible(navVisible);
                 state.addSource(navSource);
-                return state;
             }
+            return state;
         } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
             // During switching tasks with gestural navigation, before the next IME input target
             // starts the input, we should adjust and freeze the last IME visibility of the window
@@ -447,23 +462,22 @@
      * @param caller who changed the insets state.
      */
     private void checkAbortTransient(InsetsControlTarget caller) {
-        if (mShowingTransientTypes.size() != 0) {
-            final IntArray abortTypes = new IntArray();
-            final boolean imeRequestedVisible = caller.isRequestedVisible(Type.ime());
-            for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
-                final @InternalInsetsType int type = mShowingTransientTypes.get(i);
-                if ((mStateController.isFakeTarget(type, caller)
-                                && caller.isRequestedVisible(InsetsState.toPublicType(type)))
-                        || (type == ITYPE_NAVIGATION_BAR && imeRequestedVisible)) {
-                    mShowingTransientTypes.remove(i);
-                    abortTypes.add(type);
-                }
-            }
-            StatusBarManagerInternal statusBarManagerInternal =
-                    mPolicy.getStatusBarManagerInternal();
-            if (abortTypes.size() > 0 && statusBarManagerInternal != null) {
-                statusBarManagerInternal.abortTransient(
-                        mDisplayContent.getDisplayId(), abortTypes.toArray());
+        if (mShowingTransientTypes == 0) {
+            return;
+        }
+        final boolean isImeVisible = mStateController.getImeSourceProvider().isClientVisible();
+        final @InsetsType int fakeControllingTypes =
+                mStateController.getFakeControllingTypes(caller);
+        final @InsetsType int abortTypes =
+                (fakeControllingTypes & caller.getRequestedVisibleTypes())
+                | (isImeVisible ? Type.navigationBars() : 0);
+        mShowingTransientTypes &= ~abortTypes;
+        if (abortTypes != 0) {
+            mDisplayContent.setLayoutNeeded();
+            mDisplayContent.mWmService.requestTraversal();
+            final StatusBarManagerInternal statusBarManager = mPolicy.getStatusBarManagerInternal();
+            if (statusBarManager != null) {
+                statusBarManager.abortTransient(mDisplayContent.getDisplayId(), abortTypes);
             }
         }
     }
@@ -473,12 +487,16 @@
      * updateBarControlTarget(mFocusedWin) after this invocation.
      */
     private void abortTransient() {
-        StatusBarManagerInternal statusBarManagerInternal = mPolicy.getStatusBarManagerInternal();
-        if (statusBarManagerInternal != null) {
-            statusBarManagerInternal.abortTransient(
-                    mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray());
+        if (mShowingTransientTypes == 0) {
+            return;
         }
-        mShowingTransientTypes.clear();
+        final StatusBarManagerInternal statusBarManager = mPolicy.getStatusBarManagerInternal();
+        if (statusBarManager != null) {
+            statusBarManager.abortTransient(mDisplayContent.getDisplayId(), mShowingTransientTypes);
+        }
+        mShowingTransientTypes = 0;
+        mDisplayContent.setLayoutNeeded();
+        mDisplayContent.mWmService.requestTraversal();
 
         dispatchTransientSystemBarsVisibilityChanged(
                 mFocusedWin,
@@ -488,7 +506,7 @@
 
     private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
             boolean fake) {
-        if (!fake && isShowingTransientTypes(Type.statusBars())) {
+        if (!fake && isTransient(Type.statusBars())) {
             return mDummyControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
@@ -541,7 +559,7 @@
             // configured to be hidden by the IME.
             return null;
         }
-        if (!fake && isShowingTransientTypes(Type.navigationBars())) {
+        if (!fake && isTransient(Type.navigationBars())) {
             return mDummyControlTarget;
         }
         if (focusedWin == mPolicy.getNotificationShade()) {
@@ -577,16 +595,6 @@
         return focusedWin;
     }
 
-    private boolean isShowingTransientTypes(@InsetsType int types) {
-        final IntArray showingTransientTypes = mShowingTransientTypes;
-        for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
-            if ((InsetsState.toPublicType(showingTransientTypes.get(i)) & types) != 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Determines whether the remote insets controller should take control of system bars for all
      * windows.
@@ -622,21 +630,17 @@
 
     @VisibleForTesting
     void startAnimation(boolean show, Runnable callback) {
-        int typesReady = 0;
-        final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
-        final IntArray showingTransientTypes = mShowingTransientTypes;
-        for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
-            final int sourceId = showingTransientTypes.get(i);
-            final WindowContainerInsetsSourceProvider provider =
-                    mStateController.getSourceProvider(sourceId);
-            final InsetsSourceControl control = provider.getControl(mDummyControlTarget);
-            if (control == null || control.getLeash() == null) {
-                continue;
+        @InsetsType int typesReady = 0;
+        final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>();
+        final InsetsSourceControl[] controls =
+                mStateController.getControlsForDispatch(mDummyControlTarget);
+        for (InsetsSourceControl control : controls) {
+            if (isTransient(control.getType()) && control.getLeash() != null) {
+                typesReady |= control.getType();
+                controlsReady.put(control.getId(), new InsetsSourceControl(control));
             }
-            typesReady |= control.getType();
-            controls.put(sourceId, new InsetsSourceControl(control));
         }
-        controlAnimationUnchecked(typesReady, controls, show, callback);
+        controlAnimationUnchecked(typesReady, controlsReady, show, callback);
     }
 
     private void controlAnimationUnchecked(int typesReady,
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f5af292..2b7a451 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -164,7 +164,7 @@
             // TODO: Ideally, we should wait for the animation to finish so previous window can
             // animate-out as new one animates-in.
             mWindowContainer.cancelAnimation();
-            mWindowContainer.getProvidedInsetsSources().remove(mSource.getId());
+            mWindowContainer.getInsetsSourceProviders().remove(mSource.getId());
             mSeamlessRotating = false;
         }
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
@@ -180,7 +180,7 @@
             mSource.setInsetsRoundedCornerFrame(false);
             mSourceFrame.setEmpty();
         } else {
-            mWindowContainer.getProvidedInsetsSources().put(mSource.getId(), mSource);
+            mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
                 if (mPendingControlTarget != null) {
@@ -192,13 +192,6 @@
     }
 
     /**
-     * @return Whether there is a window container which backs this source.
-     */
-    boolean hasWindowContainer() {
-        return mWindowContainer != null;
-    }
-
-    /**
      * The source frame can affect the layout of other windows, so this should be called once the
      * window container gets laid out.
      */
@@ -363,9 +356,9 @@
     }
 
     /**
-     * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
+     * @see InsetsStateController#onControlTargetChanged
      */
-    void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) {
+    void updateFakeControlTarget(@Nullable InsetsControlTarget fakeTarget) {
         if (fakeTarget == mFakeControlTarget) {
             return;
         }
@@ -570,6 +563,10 @@
         return mControlTarget;
     }
 
+    InsetsControlTarget getFakeControlTarget() {
+        return mFakeControlTarget;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
@@ -609,15 +606,15 @@
         }
         if (mControlTarget != null) {
             pw.print(prefix + "mControlTarget=");
-            pw.println(mControlTarget.getWindow());
+            pw.println(mControlTarget);
         }
         if (mPendingControlTarget != null) {
             pw.print(prefix + "mPendingControlTarget=");
-            pw.println(mPendingControlTarget.getWindow());
+            pw.println(mPendingControlTarget);
         }
         if (mFakeControlTarget != null) {
             pw.print(prefix + "mFakeControlTarget=");
-            pw.println(mFakeControlTarget.getWindow());
+            pw.println(mFakeControlTarget);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index a3f62b2..0e1e63e 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -18,10 +18,6 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
@@ -38,10 +34,11 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -49,7 +46,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Manages global window inset state in the system represented by {@link InsetsState}.
@@ -60,14 +56,11 @@
     private final InsetsState mState = new InsetsState();
     private final DisplayContent mDisplayContent;
 
-    private final ArrayMap<Integer, WindowContainerInsetsSourceProvider> mProviders =
-            new ArrayMap<>();
-    private final ArrayMap<InsetsControlTarget, ArrayList<Integer>> mControlTargetTypeMap =
-            new ArrayMap<>();
-    private final SparseArray<InsetsControlTarget> mTypeControlTargetMap = new SparseArray<>();
-
-    /** @see #onControlFakeTargetChanged */
-    private final SparseArray<InsetsControlTarget> mTypeFakeControlTargetMap = new SparseArray<>();
+    private final SparseArray<WindowContainerInsetsSourceProvider> mProviders = new SparseArray<>();
+    private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
+            mControlTargetProvidersMap = new ArrayMap<>();
+    private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
+    private final SparseArray<InsetsControlTarget> mIdFakeControlTargetMap = new SparseArray<>();
 
     private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
 
@@ -92,15 +85,8 @@
         }
     };
 
-    private final Function<Integer, WindowContainerInsetsSourceProvider> mSourceProviderFunc;
-
     InsetsStateController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
-        mSourceProviderFunc = id -> (id == ID_IME)
-                ? new ImeInsetsSourceProvider(mState.getOrCreateSource(
-                        id, ime()), this, mDisplayContent)
-                : new WindowContainerInsetsSourceProvider(mState.getOrCreateSource(
-                        id, InsetsState.toPublicType(id)), this, mDisplayContent);
     }
 
     InsetsState getRawInsetsState() {
@@ -108,39 +94,55 @@
     }
 
     @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
-        ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
+        final ArrayList<InsetsSourceProvider> controlled = mControlTargetProvidersMap.get(target);
         if (controlled == null) {
             return null;
         }
         final int size = controlled.size();
         final InsetsSourceControl[] result = new InsetsSourceControl[size];
         for (int i = 0; i < size; i++) {
-            result[i] = mProviders.get(controlled.get(i)).getControl(target);
+            result[i] = controlled.get(i).getControl(target);
         }
         return result;
     }
 
-    ArrayMap<Integer, WindowContainerInsetsSourceProvider> getSourceProviders() {
+    SparseArray<WindowContainerInsetsSourceProvider> getSourceProviders() {
         return mProviders;
     }
 
     /**
      * @return The provider of a specific source ID.
      */
-    WindowContainerInsetsSourceProvider getSourceProvider(int id) {
-        return mProviders.computeIfAbsent(id, mSourceProviderFunc);
+    WindowContainerInsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
+        WindowContainerInsetsSourceProvider provider = mProviders.get(id);
+        if (provider != null) {
+            return provider;
+        }
+        final InsetsSource source = mState.getOrCreateSource(id, type);
+        provider = id == ID_IME
+                ? new ImeInsetsSourceProvider(source, this, mDisplayContent)
+                : new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+        mProviders.put(id, provider);
+        return provider;
     }
 
     ImeInsetsSourceProvider getImeSourceProvider() {
-        return (ImeInsetsSourceProvider) getSourceProvider(ID_IME);
+        return (ImeInsetsSourceProvider) getOrCreateSourceProvider(ID_IME, ime());
+    }
+
+    void removeSourceProvider(int id) {
+        if (id != ID_IME) {
+            mState.removeSource(id);
+            mProviders.remove(id);
+        }
     }
 
     /**
-     * @return The provider of a specific type or null if we don't have it.
+     * @return The provider of a source ID or null if we don't have it.
      */
     @Nullable
-    WindowContainerInsetsSourceProvider peekSourceProvider(@InternalInsetsType int type) {
-        return mProviders.get(type);
+    WindowContainerInsetsSourceProvider peekSourceProvider(int id) {
+        return mProviders.get(id);
     }
 
     /**
@@ -208,8 +210,16 @@
         }
     }
 
-    boolean isFakeTarget(@InternalInsetsType int type, InsetsControlTarget target) {
-        return mTypeFakeControlTargetMap.get(type) == target;
+    @InsetsType int getFakeControllingTypes(InsetsControlTarget target) {
+        @InsetsType int types = 0;
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            final InsetsSourceProvider provider = mProviders.valueAt(i);
+            final InsetsControlTarget fakeControlTarget = provider.getFakeControlTarget();
+            if (target == fakeControlTarget) {
+                types |= provider.getSource().getType();
+            }
+        }
+        return types;
     }
 
     void onImeControlTargetChanged(@Nullable InsetsControlTarget imeTarget) {
@@ -217,7 +227,7 @@
         // Make sure that we always have a control target for the IME, even if the IME target is
         // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible.
         InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget;
-        onControlChanged(ID_IME, target);
+        onControlTargetChanged(getImeSourceProvider(), target, false /* fake */);
         ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s",
                 target != null ? target.getWindow() : "null");
         notifyPendingInsetsControlChanged();
@@ -235,101 +245,88 @@
             @Nullable InsetsControlTarget fakeStatusControlling,
             @Nullable InsetsControlTarget navControlling,
             @Nullable InsetsControlTarget fakeNavControlling) {
-        onControlChanged(ITYPE_STATUS_BAR, statusControlling);
-        onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
-        onControlChanged(ITYPE_CLIMATE_BAR, statusControlling);
-        onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling);
-        onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
-        onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
-        onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling);
-        onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling);
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            final InsetsSourceProvider provider = mProviders.valueAt(i);
+            final @InsetsType int type = provider.getSource().getType();
+            if (type == WindowInsets.Type.statusBars()) {
+                onControlTargetChanged(provider, statusControlling, false /* fake */);
+                onControlTargetChanged(provider, fakeStatusControlling, true /* fake */);
+            } else if (type == WindowInsets.Type.navigationBars()) {
+                onControlTargetChanged(provider, navControlling, false /* fake */);
+                onControlTargetChanged(provider, fakeNavControlling, true /* fake */);
+            }
+        }
         notifyPendingInsetsControlChanged();
     }
 
     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
-        removeFromControlMaps(previousControlTarget, provider.getSource().getId(),
-                false /* fake */);
+        removeFromControlMaps(previousControlTarget, provider, false /* fake */);
     }
 
-    private void onControlChanged(@InternalInsetsType int type,
-            @Nullable InsetsControlTarget target) {
-        final InsetsControlTarget previous = mTypeControlTargetMap.get(type);
-        if (target == previous) {
-            return;
-        }
-        final WindowContainerInsetsSourceProvider provider = mProviders.get(type);
-        if (provider == null) {
+    private void onControlTargetChanged(InsetsSourceProvider provider,
+            @Nullable InsetsControlTarget target, boolean fake) {
+        final InsetsControlTarget lastTarget = fake
+                ? mIdFakeControlTargetMap.get(provider.getSource().getId())
+                : mIdControlTargetMap.get(provider.getSource().getId());
+        if (target == lastTarget) {
             return;
         }
         if (!provider.isControllable()) {
             return;
         }
-        provider.updateControlForTarget(target, false /* force */);
-        target = provider.getControlTarget();
-        if (previous != null) {
-            removeFromControlMaps(previous, type, false /* fake */);
-            mPendingControlChanged.add(previous);
+        if (fake) {
+            // The fake target updated here will be used to pretend to the app that it's still under
+            // control of the bars while it's not really, but we still need to find out the apps
+            // intentions around showing/hiding. For example, when the transient bars are showing,
+            // and the fake target requests to show system bars, the transient state will be
+            // aborted.
+            provider.updateFakeControlTarget(target);
+        } else {
+            provider.updateControlForTarget(target, false /* force */);
+
+            // Get control target again in case the provider didn't accept the one we passed to it.
+            target = provider.getControlTarget();
+            if (target == lastTarget) {
+                return;
+            }
+        }
+        if (lastTarget != null) {
+            removeFromControlMaps(lastTarget, provider, fake);
+            mPendingControlChanged.add(lastTarget);
         }
         if (target != null) {
-            addToControlMaps(target, type, false /* fake */);
+            addToControlMaps(target, provider, fake);
             mPendingControlChanged.add(target);
         }
     }
 
-    /**
-     * The fake target saved here will be used to pretend to the app that it's still under control
-     * of the bars while it's not really, but we still need to find out the apps intentions around
-     * showing/hiding. For example, when the transient bars are showing, and the fake target
-     * requests to show system bars, the transient state will be aborted.
-     */
-    void onControlFakeTargetChanged(@InternalInsetsType int type,
-            @Nullable InsetsControlTarget fakeTarget) {
-        final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type);
-        if (fakeTarget == previous) {
-            return;
-        }
-        final WindowContainerInsetsSourceProvider provider = mProviders.get(type);
-        if (provider == null) {
-            return;
-        }
-        provider.updateControlForFakeTarget(fakeTarget);
-        if (previous != null) {
-            removeFromControlMaps(previous, type, true /* fake */);
-            mPendingControlChanged.add(previous);
-        }
-        if (fakeTarget != null) {
-            addToControlMaps(fakeTarget, type, true /* fake */);
-            mPendingControlChanged.add(fakeTarget);
-        }
-    }
-
     private void removeFromControlMaps(@NonNull InsetsControlTarget target,
-            @InternalInsetsType int type, boolean fake) {
-        final ArrayList<Integer> array = mControlTargetTypeMap.get(target);
+            InsetsSourceProvider provider, boolean fake) {
+        final ArrayList<InsetsSourceProvider> array = mControlTargetProvidersMap.get(target);
         if (array == null) {
             return;
         }
-        array.remove((Integer) type);
+        array.remove(provider);
         if (array.isEmpty()) {
-            mControlTargetTypeMap.remove(target);
+            mControlTargetProvidersMap.remove(target);
         }
         if (fake) {
-            mTypeFakeControlTargetMap.remove(type);
+            mIdFakeControlTargetMap.remove(provider.getSource().getId());
         } else {
-            mTypeControlTargetMap.remove(type);
+            mIdControlTargetMap.remove(provider.getSource().getId());
         }
     }
 
     private void addToControlMaps(@NonNull InsetsControlTarget target,
-            @InternalInsetsType int type, boolean fake) {
-        final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target,
-                key -> new ArrayList<>());
-        array.add(type);
+            InsetsSourceProvider provider, boolean fake) {
+        final ArrayList<InsetsSourceProvider> array = mControlTargetProvidersMap.computeIfAbsent(
+                target, key -> new ArrayList<>());
+        array.add(provider);
         if (fake) {
-            mTypeFakeControlTargetMap.put(type, target);
+            mIdFakeControlTargetMap.put(provider.getSource().getId(), target);
         } else {
-            mTypeControlTargetMap.put(type, target);
+            mIdControlTargetMap.put(provider.getSource().getId(), target);
         }
     }
 
@@ -351,7 +348,7 @@
             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
                 final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
                 controlTarget.notifyInsetsControlChanged();
-                if (mControlTargetTypeMap.containsKey(controlTarget)) {
+                if (mControlTargetProvidersMap.containsKey(controlTarget)) {
                     // We only collect targets who get controls, not lose controls.
                     newControlTargets.add(controlTarget);
                 }
@@ -377,10 +374,25 @@
         prefix = prefix + "  ";
         mState.dump(prefix, pw);
         pw.println(prefix + "Control map:");
-        for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {
+        for (int i = mControlTargetProvidersMap.size() - 1; i >= 0; i--) {
+            final InsetsControlTarget controlTarget = mControlTargetProvidersMap.keyAt(i);
             pw.print(prefix + "  ");
-            pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "
-                    + mTypeControlTargetMap.valueAt(i));
+            pw.print(controlTarget);
+            pw.println(":");
+            final ArrayList<InsetsSourceProvider> providers = mControlTargetProvidersMap.valueAt(i);
+            for (int j = providers.size() - 1; j >= 0; j--) {
+                final InsetsSourceProvider provider = providers.get(j);
+                if (provider != null) {
+                    pw.print(prefix + "    ");
+                    if (controlTarget == provider.getFakeControlTarget()) {
+                        pw.print("(fake) ");
+                    }
+                    pw.println(provider.getControl(controlTarget));
+                }
+            }
+        }
+        if (mControlTargetProvidersMap.isEmpty()) {
+            pw.print(prefix + "  none");
         }
         pw.println(prefix + "InsetsSourceProviders:");
         for (int i = mProviders.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2343906..110cce2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -385,9 +385,9 @@
             }
 
             ProtoLog.d(WM_DEBUG_TASKS, "Comparing existing cls=%s /aff=%s to new cls=%s /aff=%s",
-                    r.getTask().rootAffinity, mIntent.getComponent().flattenToShortString(),
-                    mInfo.taskAffinity, (task.realActivity != null
-                            ? task.realActivity.flattenToShortString() : ""));
+                    (task.realActivity != null ? task.realActivity.flattenToShortString() : ""),
+                    task.rootAffinity, mIntent.getComponent().flattenToShortString(),
+                    mTaskAffinity);
             // TODO Refactor to remove duplications. Check if logic can be simplified.
             if (task.realActivity != null && task.realActivity.compareTo(cls) == 0
                     && Objects.equals(documentData, taskDocumentData)) {
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 98ca9ae..5860776 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -146,8 +146,8 @@
                 .setLaunchRootTask(options.getLaunchRootTask())
                 .setPendingIntentBackgroundActivityStartMode(
                         options.getPendingIntentBackgroundActivityStartMode())
-                .setIgnorePendingIntentCreatorForegroundState(
-                        options.getIgnorePendingIntentCreatorForegroundState());
+                .setPendingIntentCreatorBackgroundActivityStartMode(
+                        options.getPendingIntentCreatorBackgroundActivityStartMode());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ee11f68..1e53cc3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -149,7 +149,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -171,6 +170,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
+import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.RemoteAnimationAdapter;
 import android.view.Surface;
@@ -2833,14 +2833,13 @@
     void adjustAnimationBoundsForTransition(Rect animationBounds) {
         TaskTransitionSpec spec = mWmService.mTaskTransitionSpec;
         if (spec != null) {
-            for (@InsetsState.InternalInsetsType int insetType : spec.animationBoundInsets) {
-                WindowContainerInsetsSourceProvider insetProvider = getDisplayContent()
-                        .getInsetsStateController()
-                        .getSourceProvider(insetType);
-
-                Insets insets = insetProvider.getSource().calculateVisibleInsets(
-                        animationBounds);
-                animationBounds.inset(insets);
+            final InsetsState state =
+                    getDisplayContent().getInsetsStateController().getRawInsetsState();
+            for (int id : spec.animationBoundInsets) {
+                final InsetsSource source = state.peekSource(id);
+                if (source != null) {
+                    animationBounds.inset(source.calculateVisibleInsets(animationBounds));
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f0c099f..e82dc82 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -216,6 +216,12 @@
 
     final TransitionController.Logger mLogger = new TransitionController.Logger();
 
+    /**
+     * {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware
+     * of it). Currently, this happens before the display is ready since nothing can be seen yet.
+     */
+    boolean mIsPlayerEnabled = true;
+
     Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -777,7 +783,7 @@
      * be called directly; use {@link TransitionController#finishTransition} instead.
      */
     void finishTransition() {
-        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                     System.identityHashCode(this));
         }
@@ -1112,7 +1118,7 @@
             controller.setupStartTransaction(transaction);
         }
         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
-        if (mController.getTransitionPlayer() != null) {
+        if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
             mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
             try {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
@@ -1128,11 +1134,21 @@
             } catch (RemoteException e) {
                 // If there's an exception when trying to send the mergedTransaction to the
                 // client, we should finish and apply it here so the transactions aren't lost.
-                cleanUpOnFailure();
+                postCleanupOnFailure();
+            }
+            final AccessibilityController accessibilityController =
+                    dc.mWmService.mAccessibilityController;
+            if (accessibilityController.hasCallbacks()) {
+                accessibilityController.onWMTransition(dc.getDisplayId(), mType);
             }
         } else {
-            // No player registered, so just finish/apply immediately
-            cleanUpOnFailure();
+            // No player registered or it's not enabled, so just finish/apply immediately
+            if (!mIsPlayerEnabled) {
+                mLogger.mSendTimeNs = SystemClock.uptimeNanos();
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately"
+                        + " because player is disabled for transition #%d .", mSyncId);
+            }
+            postCleanupOnFailure();
         }
         mController.mLoggerHandler.post(mLogger::logOnSend);
         mOverrideOptions = null;
@@ -1143,6 +1159,14 @@
         info.releaseAnimSurfaces();
     }
 
+    private void postCleanupOnFailure() {
+        mController.mAtm.mH.post(() -> {
+            synchronized (mController.mAtm.mGlobalLock) {
+                cleanUpOnFailure();
+            }
+        });
+    }
+
     /**
      * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
      * this directly, it's designed to by called by {@link TransitionController} only.
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 18788bf..2c23b5d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -126,6 +126,13 @@
 
     final Handler mLoggerHandler = FgThread.getHandler();
 
+    /**
+     * {@code true} While this waits for the display to become enabled (during boot). While waiting
+     * for the display, all core-initiated transitions will be "local".
+     * Note: This defaults to false so that it doesn't interfere with unit tests.
+     */
+    boolean mIsWaitingForDisplayEnabled = false;
+
     TransitionController(ActivityTaskManagerService atm,
             TaskSnapshotController taskSnapshotController,
             TransitionTracer transitionTracer) {
@@ -486,6 +493,15 @@
     Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+        if (mIsWaitingForDisplayEnabled) {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Disabling player for transition"
+                    + " #%d because display isn't enabled yet", transition.getSyncId());
+            transition.mIsPlayerEnabled = false;
+            transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos();
+            mAtm.mH.post(() -> mAtm.mWindowOrganizerController.startTransition(
+                    transition.getToken(), null));
+            return transition;
+        }
         try {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Requesting StartTransition: %s", transition);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b06bdb1..7173980 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -170,9 +170,9 @@
     protected InsetsSourceProvider mControllableInsetProvider;
 
     /**
-     * The insets sources provided by this windowContainer.
+     * The {@link InsetsSourceProvider}s provided by this window container.
      */
-    protected SparseArray<InsetsSource> mProvidedInsetsSources = null;
+    protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
 
     // List of children for this window container. List is in z-order as the children appear on
     // screen with the top-most window container at the tail of the list.
@@ -366,7 +366,7 @@
      * {@link WindowState#mMergedLocalInsetsSources} by visiting the entire hierarchy.
      *
      * {@link WindowState#mAboveInsetsState} is updated by visiting all the windows in z-order
-     * top-to-bottom manner and considering the {@link WindowContainer#mProvidedInsetsSources}
+     * top-to-bottom manner and considering the {@link WindowContainer#mInsetsSourceProviders}
      * provided by the {@link WindowState}s at the top.
      * {@link WindowState#updateAboveInsetsState(InsetsState, SparseArray, ArraySet)} visits the
      * IME container in the correct order to make sure the IME insets are passed correctly to the
@@ -1035,11 +1035,21 @@
         }
     }
 
-    public SparseArray<InsetsSource> getProvidedInsetsSources() {
-        if (mProvidedInsetsSources == null) {
-            mProvidedInsetsSources = new SparseArray<>();
+    /**
+     * Returns {@code true} if this node provides insets.
+     */
+    public boolean hasInsetsSourceProvider() {
+        return mInsetsSourceProviders != null;
+    }
+
+    /**
+     * Returns {@link InsetsSourceProvider}s provided by this node.
+     */
+    public SparseArray<InsetsSourceProvider> getInsetsSourceProviders() {
+        if (mInsetsSourceProviders == null) {
+            mInsetsSourceProviders = new SparseArray<>();
         }
-        return mProvidedInsetsSources;
+        return mInsetsSourceProviders;
     }
 
     public DisplayContent getDisplayContent() {
@@ -4102,13 +4112,13 @@
             }
         }
 
-        private void hideInsetSourceViewOverflows(Set<Integer> insetTypes) {
-            final ArrayList<SurfaceControl> surfaceControls =
-                    new ArrayList<>(insetTypes.size());
-
-            for (int insetType : insetTypes) {
-                WindowContainerInsetsSourceProvider insetProvider = getDisplayContent()
-                        .getInsetsStateController().getSourceProvider(insetType);
+        private void hideInsetSourceViewOverflows(Set<Integer> sourceIds) {
+            final InsetsStateController controller = getDisplayContent().getInsetsStateController();
+            for (int id : sourceIds) {
+                final InsetsSourceProvider insetProvider = controller.peekSourceProvider(id);
+                if (insetProvider == null) {
+                    return;
+                }
 
                 // Will apply it immediately to current leash and to all future inset animations
                 // until we disable it.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b46a720..06a8869 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,7 @@
 import com.android.internal.policy.IKeyguardLockedStateListener;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils;
@@ -3776,6 +3777,12 @@
 
         // Make sure the last requested orientation has been applied.
         updateRotationUnchecked(false, false);
+
+        synchronized (mGlobalLock) {
+            mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Notified TransitionController "
+                    + "that the display is ready.");
+        }
     }
 
     private boolean checkBootAnimationCompleteLocked() {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e9c9cf..73e417e 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -168,6 +168,7 @@
     void setWindowManager(WindowManagerService wms) {
         mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController,
                 wms.mTransitionTracer);
+        mTransitionController.mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled;
         mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7b880fe..e8aa38e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1392,15 +1392,18 @@
     }
 
     void updateSourceFrame(Rect winFrame) {
+        if (!hasInsetsSourceProvider()) {
+            // This window doesn't provide any insets.
+            return;
+        }
         if (mGivenInsetsPending) {
             // The given insets are pending, and they are not reliable for now. The source frame
             // should be updated after the new given insets are sent to window manager.
             return;
         }
-        final SparseArray<InsetsSource> providedSources = getProvidedInsetsSources();
-        final InsetsStateController controller = getDisplayContent().getInsetsStateController();
-        for (int i = providedSources.size() - 1; i >= 0; i--) {
-            controller.getSourceProvider(providedSources.keyAt(i)).updateSourceFrame(winFrame);
+        final SparseArray<InsetsSourceProvider> providers = getInsetsSourceProviders();
+        for (int i = providers.size() - 1; i >= 0; i--) {
+            providers.valueAt(i).updateSourceFrame(winFrame);
         }
     }
 
@@ -1879,11 +1882,11 @@
     }
 
     boolean providesNonDecorInsets() {
-        if (mProvidedInsetsSources == null) {
+        if (mInsetsSourceProviders == null) {
             return false;
         }
-        for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) {
-            final InsetsSource source = mProvidedInsetsSources.valueAt(i);
+        for (int i = mInsetsSourceProviders.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mInsetsSourceProviders.valueAt(i).getSource();
             if (source.getType() == WindowInsets.Type.navigationBars()) {
                 return true;
             }
@@ -4840,10 +4843,10 @@
                 insetsChangedWindows.add(w);
             }
 
-            final SparseArray<InsetsSource> providedSources = w.mProvidedInsetsSources;
-            if (providedSources != null) {
-                for (int i = providedSources.size() - 1; i >= 0; i--) {
-                    aboveInsetsState.addSource(providedSources.valueAt(i));
+            final SparseArray<InsetsSourceProvider> providers = w.mInsetsSourceProviders;
+            if (providers != null) {
+                for (int i = providers.size() - 1; i >= 0; i--) {
+                    aboveInsetsState.addSource(providers.valueAt(i).getSource());
                 }
             }
         }, true /* traverseTopToBottom */);
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 4cb7a8f..dd757bc 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -29,8 +29,17 @@
 #include <utils/Log.h>
 
 #include <map>
+#include <set>
 #include <string>
 
+/**
+ * Log debug messages about native virtual input devices.
+ * Enable this via "adb shell setprop log.tag.InputController DEBUG"
+ */
+static bool isDebug() {
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
+}
+
 namespace android {
 
 enum class DeviceType {
@@ -194,6 +203,15 @@
         {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
 };
 
+/*
+ * Map from the uinput touchscreen fd to the pointers present in the previous touch events that
+ * hasn't been lifted.
+ * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual
+ * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id to go
+ * up to MAX_POINTERS_ID.
+ */
+static std::map<int32_t, std::bitset<MAX_POINTERS>> unreleasedTouches;
+
 /** Creates a new uinput device and assigns a file descriptor. */
 static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
                       DeviceType deviceType, jint screenHeight, jint screenWidth) {
@@ -366,6 +384,12 @@
 
 static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
     ioctl(fd, UI_DEV_DESTROY);
+    if (auto touchesOnFd = unreleasedTouches.find(fd); touchesOnFd != unreleasedTouches.end()) {
+        const size_t remainingPointers = touchesOnFd->second.size();
+        unreleasedTouches.erase(touchesOnFd);
+        ALOGW_IF(remainingPointers > 0, "Closing touchscreen %d, erased %zu unreleased pointers.",
+                 fd, remainingPointers);
+    }
     return close(fd);
 }
 
@@ -425,6 +449,69 @@
     return true;
 }
 
+static bool handleTouchUp(int fd, int pointerId) {
+    if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) {
+        return false;
+    }
+    auto touchesOnFd = unreleasedTouches.find(fd);
+    if (touchesOnFd == unreleasedTouches.end()) {
+        ALOGE("PointerId %d action UP received with no prior events on touchscreen %d.", pointerId,
+              fd);
+        return false;
+    }
+    ALOGD_IF(isDebug(), "Unreleased touches found for touchscreen %d in the map", fd);
+
+    // When a pointer is no longer in touch, remove the pointer id from the corresponding
+    // entry in the unreleased touches map.
+    if (pointerId < 0 || pointerId >= MAX_POINTERS) {
+        ALOGE("Virtual touch event has invalid pointer id %d; value must be between 0 and %zu",
+              pointerId, MAX_POINTERS - 1);
+        return false;
+    }
+    if (!touchesOnFd->second.test(pointerId)) {
+        ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.",
+              pointerId, fd);
+        return false;
+    }
+    touchesOnFd->second.reset(pointerId);
+    ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, fd);
+
+    // Only sends the BTN UP event when there's no pointers on the touchscreen.
+    if (touchesOnFd->second.none()) {
+        unreleasedTouches.erase(touchesOnFd);
+        if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) {
+            return false;
+        }
+        ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", fd);
+    }
+    return true;
+}
+
+static bool handleTouchDown(int fd, int pointerId) {
+    // When a new pointer is down on the touchscreen, add the pointer id in the corresponding
+    // entry in the unreleased touches map.
+    auto touchesOnFd = unreleasedTouches.find(fd);
+    if (touchesOnFd == unreleasedTouches.end()) {
+        // Only sends the BTN Down event when the first pointer on the touchscreen is down.
+        if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) {
+            return false;
+        }
+        touchesOnFd = unreleasedTouches.insert({fd, {}}).first;
+        ALOGD_IF(isDebug(), "New touchscreen with fd %d added in the unreleased touches map.", fd);
+    }
+    if (touchesOnFd->second.test(pointerId)) {
+        ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.",
+              pointerId);
+        return false;
+    }
+    touchesOnFd->second.set(pointerId);
+    ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, fd);
+    if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) {
+        return false;
+    }
+    return true;
+}
+
 static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint pointerId, jint toolType,
                                   jint action, jfloat locationX, jfloat locationY, jfloat pressure,
                                   jfloat majorAxisSize) {
@@ -446,15 +533,10 @@
         return false;
     }
     UinputAction uinputAction = actionIterator->second;
-    if (uinputAction == UinputAction::PRESS || uinputAction == UinputAction::RELEASE) {
-        if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(uinputAction))) {
-            return false;
-        }
-        if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID,
-                             static_cast<int32_t>(uinputAction == UinputAction::PRESS ? pointerId
-                                                                                      : -1))) {
-            return false;
-        }
+    if (uinputAction == UinputAction::PRESS && !handleTouchDown(fd, pointerId)) {
+        return false;
+    } else if (uinputAction == UinputAction::RELEASE && !handleTouchUp(fd, pointerId)) {
+        return false;
     }
     if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_X, locationX)) {
         return false;
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index be60946..447c67f 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -23,6 +23,7 @@
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.RequestInfo;
+import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialProviderInfo;
@@ -41,9 +42,9 @@
 
     public ClearRequestSession(Context context, int userId, int callingUid,
             IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
-            CallingAppInfo callingAppInfo) {
+            CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) {
         super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED,
-                callingAppInfo);
+                callingAppInfo, cancellationSignal);
     }
 
     /**
@@ -111,6 +112,12 @@
 
     private void respondToClientWithResponseAndFinish() {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
+        if (isSessionCancelled()) {
+            // TODO: Differentiate btw cancelled and false
+            logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         try {
             mClientCallback.onSuccess();
             logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
@@ -118,18 +125,24 @@
             Log.i(TAG, "Issue while propagating the response to the client");
             logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
         }
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         Log.i(TAG, "respondToClientWithErrorAndFinish");
+        if (isSessionCancelled()) {
+            // TODO: Differentiate btw cancelled and false
+            logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true);
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         try {
             mClientCallback.onError(errorType, errorMsg);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
         logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false);
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
     private void processResponses() {
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 351afb9..2345e3f 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -27,6 +27,7 @@
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.RequestInfo;
+import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialProviderInfo;
@@ -47,9 +48,10 @@
     CreateRequestSession(@NonNull Context context, int userId, int callingUid,
             CreateCredentialRequest request,
             ICreateCredentialCallback callback,
-            CallingAppInfo callingAppInfo) {
+            CallingAppInfo callingAppInfo,
+            CancellationSignal cancellationSignal) {
         super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE,
-                callingAppInfo);
+                callingAppInfo, cancellationSignal);
     }
 
     /**
@@ -119,6 +121,12 @@
 
     private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
+        if (isSessionCancelled()) {
+            // TODO: Differentiate btw cancelled and false
+            logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         try {
             mClientCallback.onResponse(response);
             logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
@@ -126,18 +134,24 @@
             Log.i(TAG, "Issue while responding to client: " + e.getMessage());
             logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false);
         }
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         Log.i(TAG, "respondToClientWithErrorAndFinish");
+        if (isSessionCancelled()) {
+            // TODO: Differentiate btw cancelled and false
+            logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true);
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         try {
             mClientCallback.onError(errorType, errorMsg);
         } catch (RemoteException e) {
             Log.i(TAG, "Issue while responding to client: " + e.getMessage());
         }
         logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false);
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
     @Override
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index fbdcc44..3d504ef 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -19,35 +19,72 @@
 import android.credentials.CredentialDescription;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
+import android.service.credentials.CredentialEntry;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
 
 /** Contains information on what CredentialProvider has what provisioned Credential. */
-public class CredentialDescriptionRegistry {
+public final class CredentialDescriptionRegistry {
 
     private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
     private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
-    private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
+    @GuardedBy("sLock")
+    private static final SparseArray<CredentialDescriptionRegistry>
+            sCredentialDescriptionSessionPerUser;
+    private static final ReentrantLock sLock;
 
     static {
         sCredentialDescriptionSessionPerUser = new SparseArray<>();
+        sLock = new ReentrantLock();
     }
 
-    // TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
-    /** Get and/or create a {@link  CredentialDescription} for the given user id. */
-    public static CredentialDescriptionRegistry forUser(int userId) {
-        CredentialDescriptionRegistry session =
-                sCredentialDescriptionSessionPerUser.get(userId, null);
+    /** Represents the results of a given query into the registry. */
+    public static final class FilterResult {
+        final String mPackageName;
+        final List<CredentialEntry> mCredentialEntries;
 
-        if (session == null) {
-            session = new CredentialDescriptionRegistry();
-            sCredentialDescriptionSessionPerUser.put(userId, session);
+        private FilterResult(String packageName,
+                List<CredentialEntry> credentialEntries) {
+            mPackageName = packageName;
+            mCredentialEntries = credentialEntries;
         }
-        return session;
+    }
+
+    /** Get and/or create a {@link  CredentialDescription} for the given user id. */
+    @GuardedBy("sLock")
+    public static CredentialDescriptionRegistry forUser(int userId) {
+        sLock.lock();
+        try {
+            CredentialDescriptionRegistry session =
+                    sCredentialDescriptionSessionPerUser.get(userId, null);
+
+            if (session == null) {
+                session = new CredentialDescriptionRegistry();
+                sCredentialDescriptionSessionPerUser.put(userId, session);
+            }
+            return session;
+        } finally {
+            sLock.unlock();
+        }
+    }
+
+    /** Clears an existing session for a given user identifier. */
+    @GuardedBy("sLock")
+    public static void clearUserSession(int userId) {
+        sLock.lock();
+        try {
+            sCredentialDescriptionSessionPerUser.remove(userId);
+        } finally {
+            sLock.unlock();
+        }
     }
 
     private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
@@ -74,7 +111,7 @@
             int size = mCredentialDescriptions.get(callingPackageName).size();
             mCredentialDescriptions.get(callingPackageName)
                     .addAll(descriptions);
-            mTotalDescriptionCount += size - mCredentialDescriptions.get(callingPackageName).size();
+            mTotalDescriptionCount += mCredentialDescriptions.get(callingPackageName).size() - size;
         }
 
     }
@@ -93,21 +130,33 @@
         }
     }
 
+    /** Returns package names and entries of a CredentialProviders that can satisfy a given
+     * {@link CredentialDescription}. */
+    public Set<FilterResult> getFilteredResultForProvider(String packageName,
+            List<String> flatRequestStrings) {
+        Set<FilterResult> result = new HashSet<>();
+        Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
+        for (CredentialDescription containedDescription: currentSet) {
+            if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) {
+                result.add(new FilterResult(packageName, containedDescription
+                        .getCredentialEntries()));
+            }
+        }
+        return result;
+    }
+
     /** Returns package names of CredentialProviders that can satisfy a given
      * {@link CredentialDescription}. */
-    public Set<String> filterCredentials(String flatRequestString) {
-
+    public Set<String> getMatchingProviders(Set<String> flatRequestString) {
         Set<String> result = new HashSet<>();
-
-        for (String componentName: mCredentialDescriptions.keySet()) {
-            Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
-            for (CredentialDescription containedDescription: currentSet) {
-                if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
-                    result.add(componentName);
+        for (String packageName: mCredentialDescriptions.keySet()) {
+            Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
+            for (CredentialDescription containedDescription : currentSet) {
+                if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) {
+                    result.add(packageName);
                 }
             }
         }
-
         return result;
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index ff72ed7..ea63c30 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -61,13 +61,11 @@
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * Entry point service for credential management.
@@ -196,7 +194,6 @@
     }
 
 
-
     @GuardedBy("mLock")
     private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
             int resolvedUserId) {
@@ -236,6 +233,7 @@
         concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
         return concatenatedServices;
     }
+
     public static boolean isCredentialDescriptionApiEnabled() {
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
@@ -244,44 +242,38 @@
     @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
     // to be guarded by 'service.mLock', which is the same as mLock.
     private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
-            RequestSession session,
-            List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
+            GetRequestSession session,
+            List<String> requestOptions, Set<String> activeCredentialContainers) {
         List<ProviderSession> providerSessions = new ArrayList<>();
         // Invoke all services of a user to initiate a provider session
-        runForUser((service) -> {
-            if (activeCredentialContainers.contains(service.getComponentName())) {
-                ProviderSession providerSession = service
-                        .initiateProviderSessionForRequestLocked(session, requestOptions);
-                if (providerSession != null) {
-                    providerSessions.add(providerSession);
-                }
-            }
-        });
+        for (String packageName: activeCredentialContainers) {
+            providerSessions.add(ProviderRegistryGetSession.createNewSession(
+                    mContext,
+                    UserHandle.getCallingUserId(),
+                    session,
+                    packageName,
+                    requestOptions));
+        }
         return providerSessions;
     }
 
     @NonNull
-    private Set<String> getMatchingProviders(GetCredentialRequest request) {
+    private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
         // Session for active/provisioned credential descriptions;
         CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
                 .forUser(UserHandle.getCallingUserId());
 
         // All requested credential descriptions based on the given request.
         Set<String> requestedCredentialDescriptions =
-                request.getCredentialOptions().stream().map(
-                        credentialOption -> credentialOption
+                options.stream().map(
+                        getCredentialOption -> getCredentialOption
                                         .getCredentialRetrievalData()
                                         .getString(CredentialOption
                                                 .FLATTENED_REQUEST))
                         .collect(Collectors.toSet());
 
         // All requested credential descriptions based on the given request.
-        return requestedCredentialDescriptions.stream()
-                .map(registry::filterCredentials)
-                .flatMap(
-                        (Function<Set<String>, Stream<String>>)
-                                Collection::stream)
-                .collect(Collectors.toSet());
+        return registry.getMatchingProviders(requestedCredentialDescriptions);
     }
 
     @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -304,6 +296,13 @@
         return providerSessions;
     }
 
+    @Override
+    @GuardedBy("CredentialDescriptionRegistry.sLock")
+    public void onUserStopped(@NonNull TargetUser user) {
+        super.onUserStopped(user);
+        CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier());
+    }
+
     private CallingAppInfo constructCallingAppInfo(String packageName, int userId) {
         final PackageInfo packageInfo;
         try {
@@ -338,15 +337,60 @@
                             callingUid,
                             callback,
                             request,
-                            constructCallingAppInfo(callingPackage, userId));
+                            constructCallingAppInfo(callingPackage, userId),
+                            CancellationSignal.fromTransport(cancelTransport));
 
-            // Initiate all provider sessions
-            List<ProviderSession> providerSessions =
-                    initiateProviderSessions(
-                            session,
-                            request.getCredentialOptions().stream()
-                                    .map(CredentialOption::getType)
-                                    .collect(Collectors.toList()));
+            List<ProviderSession> providerSessions;
+
+            if (isCredentialDescriptionApiEnabled()) {
+                List<CredentialOption> optionsThatRequireActiveCredentials =
+                        request.getCredentialOptions().stream()
+                                .filter(getCredentialOption ->
+                                        !TextUtils.isEmpty(getCredentialOption
+                                                .getCredentialRetrievalData().getString(
+                                                CredentialOption
+                                                        .FLATTENED_REQUEST, null)))
+                                .toList();
+
+                List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
+                        request.getCredentialOptions().stream()
+                                .filter(getCredentialOption ->
+                                        TextUtils.isEmpty(getCredentialOption
+                                                .getCredentialRetrievalData().getString(
+                                                        CredentialOption
+                                                                .FLATTENED_REQUEST, null)))
+                                .toList();
+
+                List<ProviderSession> sessionsWithoutRemoteService =
+                        initiateProviderSessionsWithActiveContainers(session,
+                                optionsThatRequireActiveCredentials
+                                        .stream().map(getCredentialOption ->
+                                                getCredentialOption.getCredentialRetrievalData()
+                                                        .getString(CredentialOption
+                                                .FLATTENED_REQUEST))
+                                        .collect(Collectors.toList()),
+                                getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
+
+                List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions(
+                        session,
+                        optionsThatDoNotRequireActiveCredentials.stream()
+                                .map(CredentialOption::getType)
+                                .collect(Collectors.toList()));
+
+                Set<ProviderSession> all = new LinkedHashSet<>();
+                all.addAll(sessionsWithRemoteService);
+                all.addAll(sessionsWithoutRemoteService);
+
+                providerSessions = new ArrayList<>(all);
+            } else {
+                // Initiate all provider sessions
+                providerSessions =
+                        initiateProviderSessions(
+                                session,
+                                request.getCredentialOptions().stream()
+                                        .map(CredentialOption::getType)
+                                        .collect(Collectors.toList()));
+            }
 
             if (providerSessions.isEmpty()) {
                 try {
@@ -360,9 +404,8 @@
                                     + e.getMessage());
                 }
             }
-
-            // Iterate over all provider sessions and invoke the request
             providerSessions.forEach(ProviderSession::invokeSession);
+
             return cancelTransport;
         }
 
@@ -385,7 +428,8 @@
                             callingUid,
                             request,
                             callback,
-                            constructCallingAppInfo(callingPackage, userId));
+                            constructCallingAppInfo(callingPackage, userId),
+                            CancellationSignal.fromTransport(cancelTransport));
 
             // Initiate all provider sessions
             List<ProviderSession> providerSessions =
@@ -405,8 +449,7 @@
             }
 
             // Iterate over all provider sessions and invoke the request
-            providerSessions.forEach(
-                    ProviderSession::invokeSession);
+            providerSessions.forEach(ProviderSession::invokeSession);
             return cancelTransport;
         }
 
@@ -497,7 +540,8 @@
                             callingUid,
                             callback,
                             request,
-                            constructCallingAppInfo(callingPackage, userId));
+                            constructCallingAppInfo(callingPackage, userId),
+                            CancellationSignal.fromTransport(cancelTransport));
 
             // Initiate all provider sessions
             // TODO: Determine if provider needs to have clear capability in their manifest
@@ -518,8 +562,7 @@
             }
 
             // Iterate over all provider sessions and invoke the request
-            providerSessions.forEach(
-                    ProviderSession::invokeSession);
+            providerSessions.forEach(ProviderSession::invokeSession);
             return cancelTransport;
         }
 
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index e3a27ec..e732c23 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -25,6 +25,7 @@
 import android.credentials.IGetCredentialCallback;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.RequestInfo;
+import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.CredentialProviderInfo;
@@ -43,8 +44,9 @@
 
     public GetRequestSession(Context context, int userId, int callingUid,
             IGetCredentialCallback callback, GetCredentialRequest request,
-            CallingAppInfo callingAppInfo) {
-        super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET, callingAppInfo);
+            CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) {
+        super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET,
+                callingAppInfo, cancellationSignal);
     }
 
     /**
@@ -102,6 +104,12 @@
     }
 
     private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
+        if (isSessionCancelled()) {
+            // TODO: Differentiate btw cancelled and false
+            logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         try {
             mClientCallback.onResponse(response);
             logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ true);
@@ -109,18 +117,22 @@
             Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
             logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
         }
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+        if (isSessionCancelled()) {
+            logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         try {
             mClientCallback.onError(errorType, errorMsg);
         } catch (RemoteException e) {
             Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
-
         }
         logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false);
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
     @Override
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index b112649..b20f0cd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -118,8 +118,8 @@
 
     @Override
     protected void invokeSession() {
-        this.mRemoteCredentialService.onClearCredentialState(
-                this.getProviderRequest(),
-                /*callback=*/this);
+        if (mRemoteCredentialService != null) {
+            mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
+        }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index cc5a8ab..ade40ad 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -51,6 +51,8 @@
 
     // Key to be used as an entry key for a save entry
     private static final String SAVE_ENTRY_KEY = "save_entry_key";
+    // Key to be used as an entry key for a remote entry
+    private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
 
     @NonNull
     private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
@@ -199,9 +201,9 @@
 
     @Override
     protected void invokeSession() {
-        this.mRemoteCredentialService.onCreateCredential(
-                this.getProviderRequest(),
-                /*callback=*/this);
+        if (mRemoteCredentialService != null) {
+            mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
+        }
     }
 
     private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index dec3432..3ccead1 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -59,14 +59,14 @@
         implements
         RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
     private static final String TAG = "ProviderGetSession";
-
-    // Key to be used as an entry key for a credential entry
-    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
-
     // Key to be used as the entry key for an action entry
     private static final String ACTION_ENTRY_KEY = "action_key";
     // Key to be used as the entry key for the authentication entry
     private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
+    // Key to be used as an entry key for a remote entry
+    private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
+    // Key to be used as an entry key for a credential entry
+    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
 
     @NonNull
     private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@@ -101,23 +101,8 @@
         return null;
     }
 
-    private static BeginGetCredentialRequest constructQueryPhaseRequest(
-            android.credentials.GetCredentialRequest filteredRequest,
-            CallingAppInfo callingAppInfo
-    ) {
-        return new BeginGetCredentialRequest.Builder(callingAppInfo)
-                .setBeginGetCredentialOptions(
-                        filteredRequest.getCredentialOptions().stream().map(
-                                option -> {
-                                    return new BeginGetCredentialOption(
-                                            option.getType(),
-                                            option.getCandidateQueryData());
-                                }).collect(Collectors.toList()))
-                .build();
-    }
-
     @Nullable
-    private static android.credentials.GetCredentialRequest filterOptions(
+    protected static android.credentials.GetCredentialRequest filterOptions(
             List<String> providerCapabilities,
             android.credentials.GetCredentialRequest clientRequest
     ) {
@@ -142,6 +127,21 @@
         return null;
     }
 
+    private static BeginGetCredentialRequest constructQueryPhaseRequest(
+            android.credentials.GetCredentialRequest filteredRequest,
+            CallingAppInfo callingAppInfo
+    ) {
+        return new BeginGetCredentialRequest.Builder(callingAppInfo)
+                .setBeginGetCredentialOptions(
+                        filteredRequest.getCredentialOptions().stream().map(
+                                option -> {
+                                    return new BeginGetCredentialOption(
+                                            option.getType(),
+                                            option.getCandidateQueryData());
+                                }).collect(Collectors.toList()))
+                .build();
+    }
+
     public ProviderGetSession(Context context,
             CredentialProviderInfo info,
             ProviderInternalCallback<GetCredentialResponse> callbacks,
@@ -232,9 +232,9 @@
 
     @Override
     protected void invokeSession() {
-        this.mRemoteCredentialService.onBeginGetCredential(
-                        this.getProviderRequest(),
-                        /*callback=*/this);
+        if (mRemoteCredentialService != null) {
+            mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
+        }
     }
 
     @Override // Call from request session to data to be shown on the UI
@@ -379,6 +379,28 @@
         invokeCallbackOnInternalInvalidState();
     }
 
+    @Nullable
+    protected GetCredentialException maybeGetPendingIntentException(
+            ProviderPendingIntentResponse pendingIntentResponse) {
+        if (pendingIntentResponse == null) {
+            Log.i(TAG, "pendingIntentResponse is null");
+            return null;
+        }
+        if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
+            GetCredentialException exception = PendingIntentResultHandler
+                    .extractGetCredentialException(pendingIntentResponse.getResultData());
+            if (exception != null) {
+                Log.i(TAG, "Pending intent contains provider exception");
+                return exception;
+            }
+        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
+            return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
+        } else {
+            return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
+        }
+        return null;
+    }
+
     private void onAuthenticationEntrySelected(
             @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
         //TODO: Other provider intent statuses
@@ -431,28 +453,6 @@
         updateStatusAndInvokeCallback(Status.NO_CREDENTIALS);
     }
 
-    @Nullable
-    private GetCredentialException maybeGetPendingIntentException(
-            ProviderPendingIntentResponse pendingIntentResponse) {
-        if (pendingIntentResponse == null) {
-            Log.i(TAG, "pendingIntentResponse is null");
-            return null;
-        }
-        if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
-            GetCredentialException exception = PendingIntentResultHandler
-                    .extractGetCredentialException(pendingIntentResponse.getResultData());
-            if (exception != null) {
-                Log.i(TAG, "Pending intent contains provider exception");
-                return exception;
-            }
-        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
-            return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
-        } else {
-            return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
-        }
-        return null;
-    }
-
     /**
      * When an invalid state occurs, e.g. entry mismatch or no response from provider,
      * we send back a TYPE_UNKNOWN error as to the developer.
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
new file mode 100644
index 0000000..461f447
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -0,0 +1,261 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.Intent;
+import android.credentials.CredentialOption;
+import android.credentials.GetCredentialException;
+import android.credentials.GetCredentialRequest;
+import android.credentials.GetCredentialResponse;
+import android.credentials.ui.Entry;
+import android.credentials.ui.GetCredentialProviderData;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CallingAppInfo;
+import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderService;
+import android.telecom.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Central provider session that utilizes {@link CredentialDescriptionRegistry} and therefor is able
+ * to bypass having to use a {@link RemoteCredentialService}.
+ *
+ * @hide
+ */
+public class ProviderRegistryGetSession extends ProviderSession<GetCredentialRequest,
+        Set<CredentialDescriptionRegistry.FilterResult>> {
+
+    private static final String TAG = "ProviderRegistryGetSession";
+    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable
+    public static ProviderRegistryGetSession createNewSession(
+            @NonNull Context context,
+            @UserIdInt int userId,
+            @NonNull GetRequestSession getRequestSession,
+            @NonNull String credentialProviderPackageName,
+            @NonNull List<String> requestOptions) {
+        return new ProviderRegistryGetSession(
+                context,
+                userId,
+                getRequestSession,
+                getRequestSession.mClientRequest,
+                getRequestSession.mClientAppInfo,
+                credentialProviderPackageName,
+                requestOptions);
+    }
+
+    @NonNull
+    private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
+    @NonNull
+    private final CredentialDescriptionRegistry mCredentialDescriptionRegistry;
+    @NonNull
+    private final CallingAppInfo mCallingAppInfo;
+    @NonNull
+    private final String mCredentialProviderPackageName;
+    @NonNull
+    private final GetRequestSession mGetRequestSession;
+    @NonNull
+    private final List<String> mRequestOptions;
+    private List<CredentialEntry> mCredentialEntries;
+
+    protected ProviderRegistryGetSession(@NonNull Context context,
+            @NonNull int userId,
+            @NonNull GetRequestSession session,
+            @NonNull GetCredentialRequest request,
+            @NonNull CallingAppInfo callingAppInfo,
+            @NonNull String servicePackageName,
+            @NonNull List<String> requestOptions) {
+        super(context, null, request, session, userId, null);
+        mGetRequestSession = session;
+        mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
+        mCallingAppInfo = callingAppInfo;
+        mCredentialProviderPackageName = servicePackageName;
+        mRequestOptions = requestOptions;
+    }
+
+    private List<Entry> prepareUiCredentialEntries(
+            @NonNull List<CredentialEntry> credentialEntries) {
+        Log.i(TAG, "in prepareUiProviderDataWithCredentials");
+        List<Entry> credentialUiEntries = new ArrayList<>();
+
+        // Populate the credential entries
+        for (CredentialEntry credentialEntry : credentialEntries) {
+            String entryId = generateEntryId();
+            mUiCredentialEntries.put(entryId, credentialEntry);
+            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+            credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+                    credentialEntry.getSlice(),
+                    setUpFillInIntent(credentialEntry.getType())));
+        }
+        return credentialUiEntries;
+    }
+
+    private Intent setUpFillInIntent(String type) {
+        Intent intent = new Intent();
+        for (CredentialOption option : mProviderRequest.getCredentialOptions()) {
+            if (option.getType().equals(type)) {
+                intent.putExtra(
+                        CredentialProviderService
+                                .EXTRA_GET_CREDENTIAL_REQUEST,
+                        new android.service.credentials.GetCredentialRequest(
+                                mCallingAppInfo, option));
+                return intent;
+            }
+        }
+        return intent;
+    }
+
+    @Override
+    protected ProviderData prepareUiData() {
+        Log.i(TAG, "In prepareUiData");
+        if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
+                    + mComponentName.flattenToString());
+            return null;
+        }
+        if (mProviderResponse == null) {
+            Log.i(TAG, "In prepareUiData response null");
+            throw new IllegalStateException("Response must be in completion mode");
+        }
+        return new GetCredentialProviderData.Builder(
+                mComponentName.flattenToString()).setActionChips(null)
+                .setCredentialEntries(prepareUiCredentialEntries(
+                        mProviderResponse.stream().flatMap((Function<CredentialDescriptionRegistry
+                                        .FilterResult,
+                                        Stream<CredentialEntry>>) filterResult ->
+                                        filterResult.mCredentialEntries.stream())
+                                .collect(Collectors.toList())))
+                .build();
+    }
+
+    @Override // Selection call from the request provider
+    protected void onUiEntrySelected(String entryType, String entryKey,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        switch (entryType) {
+            case CREDENTIAL_ENTRY_KEY:
+                CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
+                if (credentialEntry == null) {
+                    Log.i(TAG, "Unexpected credential entry key");
+                    return;
+                }
+                onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
+                break;
+            default:
+                Log.i(TAG, "Unsupported entry type selected");
+        }
+    }
+
+    private void onCredentialEntrySelected(CredentialEntry credentialEntry,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        if (!mCredentialEntries.contains(credentialEntry)) {
+            invokeCallbackWithError("",
+                    "");
+        }
+
+        if (providerPendingIntentResponse != null) {
+            // Check if pending intent has an error
+            GetCredentialException exception = maybeGetPendingIntentException(
+                    providerPendingIntentResponse);
+            if (exception != null) {
+                invokeCallbackWithError(exception.getType(),
+                        exception.getMessage());
+                return;
+            }
+
+            // Check if pending intent has a credential
+            GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
+                    .extractGetCredentialResponse(
+                            providerPendingIntentResponse.getResultData());
+            if (getCredentialResponse != null) {
+                if (mCallbacks != null) {
+                    mCallbacks.onFinalResponseReceived(mComponentName,
+                            getCredentialResponse);
+                }
+                return;
+            }
+
+            Log.i(TAG, "Pending intent response contains no credential, or error");
+        }
+        Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+    }
+
+    @Override
+    public void onProviderResponseSuccess(
+            @Nullable Set<CredentialDescriptionRegistry.FilterResult> response) {
+        // No need to do anything since this class does not rely on a remote service.
+    }
+
+    @Override
+    public void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e) {
+        // No need to do anything since this class does not rely on a remote service.
+    }
+
+    @Override
+    public void onProviderServiceDied(RemoteCredentialService service) {
+        // No need to do anything since this class does not rely on a remote service.
+    }
+
+    @Override
+    protected void invokeSession() {
+        mProviderResponse = mCredentialDescriptionRegistry
+                .getFilteredResultForProvider(mCredentialProviderPackageName,
+                        mRequestOptions);
+        mCredentialEntries = mProviderResponse.stream().flatMap(
+                        (Function<CredentialDescriptionRegistry.FilterResult,
+                                Stream<CredentialEntry>>) filterResult
+                        -> filterResult.mCredentialEntries.stream())
+                .collect(Collectors.toList());
+        setStatus(Status.CREDENTIALS_RECEIVED);
+    }
+
+    @Nullable
+    protected GetCredentialException maybeGetPendingIntentException(
+            ProviderPendingIntentResponse pendingIntentResponse) {
+        if (pendingIntentResponse == null) {
+            android.util.Log.i(TAG, "pendingIntentResponse is null");
+            return null;
+        }
+        if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
+            GetCredentialException exception = PendingIntentResultHandler
+                    .extractGetCredentialException(pendingIntentResponse.getResultData());
+            if (exception != null) {
+                android.util.Log.i(TAG, "Pending intent contains provider exception");
+                return exception;
+            }
+        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
+            return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
+        } else {
+            return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
+        }
+        return null;
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 8e0d6f8..d6f97ff 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -23,8 +23,11 @@
 import android.credentials.Credential;
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
 import android.util.Pair;
 
 import java.util.UUID;
@@ -38,17 +41,16 @@
         implements RemoteCredentialService.ProviderCallbacks<R> {
 
     private static final String TAG = "ProviderSession";
-    // Key to be used as an entry key for a remote entry
-    protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
 
     @NonNull protected final Context mContext;
     @NonNull protected final ComponentName mComponentName;
-    @NonNull protected final CredentialProviderInfo mProviderInfo;
-    @NonNull protected final RemoteCredentialService mRemoteCredentialService;
+    @Nullable protected final CredentialProviderInfo mProviderInfo;
+    @Nullable protected final RemoteCredentialService mRemoteCredentialService;
     @NonNull protected final int mUserId;
     @NonNull protected Status mStatus = Status.NOT_STARTED;
-    @NonNull protected final ProviderInternalCallback mCallbacks;
+    @Nullable protected final ProviderInternalCallback mCallbacks;
     @Nullable protected Credential mFinalCredentialResponse;
+    @Nullable protected ICancellationSignal mProviderCancellationSignal;
     @NonNull protected final T mProviderRequest;
     @Nullable protected R mProviderResponse;
     @NonNull protected Boolean mProviderResponseSet = false;
@@ -109,9 +111,9 @@
 
     protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
             @NonNull T providerRequest,
-            @NonNull ProviderInternalCallback callbacks,
+            @Nullable ProviderInternalCallback callbacks,
             @NonNull int userId,
-            @NonNull RemoteCredentialService remoteCredentialService) {
+            @Nullable RemoteCredentialService remoteCredentialService) {
         mContext = context;
         mProviderInfo = info;
         mProviderRequest = providerRequest;
@@ -151,6 +153,18 @@
         return  mFinalCredentialResponse;
     }
 
+    /** Propagates cancellation signal to the remote provider service. */
+    public void cancelProviderRemoteSession() {
+        try {
+            if (mProviderCancellationSignal != null) {
+                mProviderCancellationSignal.cancel();
+            }
+            setStatus(Status.CANCELED);
+        } catch (RemoteException e) {
+            Log.i(TAG, "Issue while cancelling provider session: " + e.getMessage());
+        }
+    }
+
     protected void setStatus(@NonNull Status status) {
         mStatus = status;
     }
@@ -165,7 +179,7 @@
         return mComponentName;
     }
 
-    @NonNull
+    @Nullable
     protected RemoteCredentialService getRemoteCredentialService() {
         return mRemoteCredentialService;
     }
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 8cad6ac..2dea8bd 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -110,7 +110,7 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderGetSession} class that maintains provider state
      */
-    public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
+    public ICancellationSignal onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
             ProviderCallbacks<BeginGetCredentialResponse> callback) {
         Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
@@ -149,6 +149,8 @@
         futureRef.set(connectThenExecute);
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
                 handleExecutionResponse(result, error, cancellationSink, callback)));
+
+        return cancellationSink.get();
     }
 
     /** Main entry point to be called for executing a beginCreateCredential call on the remote
@@ -157,7 +159,7 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderCreateSession} class that maintains provider state
      */
-    public void onCreateCredential(@NonNull BeginCreateCredentialRequest request,
+    public ICancellationSignal onCreateCredential(@NonNull BeginCreateCredentialRequest request,
             ProviderCallbacks<BeginCreateCredentialResponse> callback) {
         Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
@@ -196,6 +198,8 @@
         futureRef.set(connectThenExecute);
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
                 handleExecutionResponse(result, error, cancellationSink, callback)));
+
+        return cancellationSink.get();
     }
 
     /** Main entry point to be called for executing a clearCredentialState call on the remote
@@ -204,7 +208,7 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderClearSession} class that maintains provider state
      */
-    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+    public ICancellationSignal onClearCredentialState(@NonNull ClearCredentialStateRequest request,
             ProviderCallbacks<Void> callback) {
         Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
@@ -243,6 +247,8 @@
         futureRef.set(connectThenExecute);
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
                 handleExecutionResponse(result, error, cancellationSink, callback)));
+
+        return cancellationSink.get();
     }
 
     private <T> void handleExecutionResponse(T result,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index f92ffe2..9f1bd8f 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -29,6 +29,7 @@
 import android.credentials.ui.ProviderData;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.Binder;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -83,13 +84,16 @@
     private final int mCallingUid;
     @NonNull
     protected final CallingAppInfo mClientAppInfo;
+    @NonNull
+    protected final CancellationSignal mCancellationSignal;
 
     protected final Map<String, ProviderSession> mProviders = new HashMap<>();
 
     protected RequestSession(@NonNull Context context,
             @UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback,
             @NonNull String requestType,
-            CallingAppInfo callingAppInfo) {
+            CallingAppInfo callingAppInfo,
+            CancellationSignal cancellationSignal) {
         mContext = context;
         mUserId = userId;
         mCallingUid = callingUid;
@@ -97,6 +101,7 @@
         mClientCallback = clientCallback;
         mRequestType = requestType;
         mClientAppInfo = callingAppInfo;
+        mCancellationSignal = cancellationSignal;
         mHandler = new Handler(Looper.getMainLooper(), null, true);
         mRequestId = new Binder();
         mCredentialManagerUi = new CredentialManagerUi(mContext,
@@ -112,6 +117,10 @@
 
     @Override // from CredentialManagerUiCallbacks
     public void onUiSelection(UserSelectionDialogResult selection) {
+        if (isSessionCancelled()) {
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         String providerId = selection.getProviderId();
         Log.i(TAG, "onUiSelection, providerId: " + providerId);
         ProviderSession providerSession = mProviders.get(providerId);
@@ -127,18 +136,19 @@
     @Override // from CredentialManagerUiCallbacks
     public void onUiCancellation(boolean isUserCancellation) {
         Log.i(TAG, "Ui canceled. Canceled by user: " + isUserCancellation);
+        if (isSessionCancelled()) {
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
         // User canceled the activity
-        finishSession();
+        finishSession(/*propagateCancellation=*/false);
     }
 
-    protected void finishSession() {
+    protected void finishSession(boolean propagateCancellation) {
         Log.i(TAG, "finishing session");
-        clearProviderSessions();
-    }
-
-    protected void clearProviderSessions() {
-        Log.i(TAG, "Clearing sessions");
-        //TODO: Implement
+        if (propagateCancellation) {
+            mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
+        }
         mProviders.clear();
     }
 
@@ -178,6 +188,10 @@
                 isSuccessful ? METRICS_API_STATUS_SUCCESS : METRICS_API_STATUS_FAILURE);
     }
 
+    protected boolean isSessionCancelled() {
+        return mCancellationSignal.isCanceled();
+    }
+
     /**
      * Returns true if at least one provider is ready for UI invocation, and no
      * provider is pending a response.
@@ -197,6 +211,11 @@
         Log.i(TAG, "In getProviderDataAndInitiateUi");
         Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
 
+        if (isSessionCancelled()) {
+            finishSession(/*propagateCancellation=*/true);
+            return;
+        }
+
         ArrayList<ProviderData> providerDataList = new ArrayList<>();
         for (ProviderSession session : mProviders.values()) {
             Log.i(TAG, "preparing data for : " + session.getComponentName());
@@ -208,7 +227,11 @@
         }
         if (!providerDataList.isEmpty()) {
             Log.i(TAG, "provider list not empty about to initiate ui");
-            launchUiWithProviderData(providerDataList);
+            if (isSessionCancelled()) {
+                Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
+            } else {
+                launchUiWithProviderData(providerDataList);
+            }
         }
     }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f412dd3..cae6c39 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -38,6 +38,7 @@
 import android.app.SystemServiceRegistry;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -2902,6 +2903,27 @@
                 t.traceEnd();
             }
 
+            if (isWatch) {
+                t.traceBegin("StartWearService");
+                String wearServiceComponentNameString =
+                    context.getString(R.string.config_wearServiceComponent);
+
+                if (!TextUtils.isEmpty(wearServiceComponentNameString)) {
+                    ComponentName wearServiceComponentName = ComponentName.unflattenFromString(
+                        wearServiceComponentNameString);
+
+                    if (wearServiceComponentName != null) {
+                        Intent intent = new Intent();
+                        intent.setComponent(wearServiceComponentName);
+                        intent.addFlags(Intent.FLAG_DIRECT_BOOT_AUTO);
+                        context.startServiceAsUser(intent, UserHandle.SYSTEM);
+                    } else {
+                        Slog.d(TAG, "Null wear service component name.");
+                    }
+                }
+                t.traceEnd();
+            }
+
             // Enable airplane mode in safe mode. setAirplaneMode() cannot be called
             // earlier as it sends broadcasts to other services.
             // TODO: This may actually be too late if radio firmware already started leaking
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 02da25d..59551a3 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -196,7 +196,9 @@
 
         val changedPermissionNames = IndexedSet<String>()
         trimPermissions(packageName, changedPermissionNames)
-        trimPermissionStates(appId)
+        if (appId in newState.systemState.appIds) {
+            trimPermissionStates(appId)
+        }
         changedPermissionNames.forEachIndexed { _, permissionName ->
             evaluatePermissionStateForAllPackages(permissionName, null)
         }
@@ -1001,10 +1003,7 @@
         permissionName: String
     ): Boolean? {
         val permissionAllowlist = newState.systemState.permissionAllowlist
-        val apexModuleName = permissionAllowlist.apexPrivilegedAppAllowlists
-            .firstNotNullOfOrNullIndexed { _, apexModuleName, apexAllowlist ->
-                if (packageState.apexModuleName in apexAllowlist) apexModuleName else null
-            }
+        val apexModuleName = packageState.apexModuleName
         val packageName = packageState.packageName
         return when {
             packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
@@ -1071,7 +1070,7 @@
         state: AccessState = newState,
         action: (PackageState) -> Unit
     ) {
-        val packageNames = state.systemState.appIds[appId]
+        val packageNames = state.systemState.appIds[appId]!!
         packageNames.forEachIndexed { _, packageName ->
             val packageState = state.systemState.packageStates[packageName]!!
             if (packageState.androidPackage != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index 8979585..38cf634 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -75,8 +75,8 @@
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         // Make sure another user cannot be started on default display
-        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
-                DEFAULT_DISPLAY);
+        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
                 "when user (%d) is starting on default display after it was started by user %d",
                 otherUserId, visibleBgUserId);
@@ -119,8 +119,8 @@
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         // Make sure another user cannot be started on default display
-        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
-                DEFAULT_DISPLAY);
+        int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId,
+                BG_VISIBLE, DEFAULT_DISPLAY);
         assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
                 "when user (%d) is starting on default display after it was started by user %d",
                 otherUserId, visibleBgUserId);
@@ -128,6 +128,7 @@
 
         listener.verify();
     }
+  /* TODO: re-add
 
     @Test
     public void
@@ -226,4 +227,5 @@
 
         listener.verify();
     }
+  */
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 566084a..5176d68 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -44,6 +44,7 @@
 import android.util.IntArray;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
 import com.android.server.ExtendedMockitoTestCase;
 
 import org.junit.Before;
@@ -148,12 +149,6 @@
     }
 
     @Test
-    public final void testAssignUserToDisplayOnStart_invalidUserStartMode() {
-        assertThrows(IllegalArgumentException.class, () -> mMediator
-                .assignUserToDisplayOnStart(USER_ID, USER_ID, 666, DEFAULT_DISPLAY));
-    }
-
-    @Test
     public final void testStartFgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -288,7 +283,7 @@
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
                 BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
@@ -304,14 +299,14 @@
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
                 BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
 
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
         expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
 
-        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
 
         listener.verify();
     }
@@ -337,41 +332,6 @@
     }
 
     @Test
-    public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForNoEvents();
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
-
-        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
-        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForNoEvents();
-        startBackgroundUser(PARENT_USER_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
-
-        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
-
-        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
-
-        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        listener.verify();
-    }
-
-    @Test
     public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -525,6 +485,8 @@
      * se.
      */
     protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) {
+        Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
+                "must pass a secondary display, not %d", displayId);
         Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
         int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId);
         if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
index f084063..49c6a88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -108,6 +108,34 @@
     }
 
     @Test
+    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+        expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public final void testStartFgUser_onInvalidDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -240,83 +268,14 @@
     }
 
     @Test
-    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(PARENT_USER_ID),
-                onVisible(PROFILE_USER_ID));
-        startForegroundUser(PARENT_USER_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-        expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-
-        expectUserIsVisible(PROFILE_USER_ID);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
-        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
-        assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        listener.verify();
-    }
-
-    @Test
     public final void
-            testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnAnotherDisplay()
-            throws Exception {
+            testStartVisibleBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
+                    throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
         startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
                 BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
-        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
-        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
-
-        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        listener.verify();
-    }
-
-    // Not supported - profiles can only be started on default display
-    @Test
-    public final void
-            testStartVisibleBgProfile_onSecondaryDisplay_whenParentIsStartedVisibleOnThatDisplay()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
-        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
-        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
-        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
-
-        assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public final void
-            testStartProfile_onDefaultDisplay_whenParentIsStartedVisibleOnSecondaryDisplay()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
-        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
-                DEFAULT_DISPLAY);
         assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 99f7905..e605a31 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -35,6 +35,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.common.OperationContext;
@@ -364,6 +365,16 @@
         showHideOverlay(c -> c.onLockoutPermanent());
     }
 
+    @Test
+    public void testPowerPressForwardsErrorMessage() throws RemoteException {
+        final FingerprintAuthenticationClient client = createClient();
+
+        client.onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR,
+                BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED);
+
+        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+                eq(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED), eq(0));
+    }
     private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
             throws RemoteException {
         final FingerprintAuthenticationClient client = createClient();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 26524d7..a40d3fe 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
-
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -29,6 +27,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
@@ -276,11 +275,12 @@
     @Test
     public void testPowerPressForwardsAcquireMessage() throws RemoteException {
         final FingerprintEnrollClient client = createClient();
-        client.start(mCallback);
-        client.onPowerPressed();
+
+        client.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR,
+                BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED);
 
         verify(mClientMonitorCallbackConverter).onAcquired(anyInt(),
-                eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt());
+                eq(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED), eq(0));
     }
 
     private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 27d912b..0eff0da 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -2402,6 +2402,74 @@
                 eq(lightSensorTwo), anyInt(), any(Handler.class));
     }
 
+    @Test
+    public void testAuthenticationPossibleSetsPhysicalRateRangesToMax() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // don't call director.start(createMockSensorManager());
+        // DisplayObserver will reset mSupportedModesByDisplay
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testAuthenticationPossibleUnsetsVote() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, false);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUdfpsRequestSetsPhysicalRateRangesToMax() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // don't call director.start(createMockSensorManager());
+        // DisplayObserver will reset mSupportedModesByDisplay
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+
+        captor.getValue().onRequestEnabled(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testUdfpsRequestUnsetsUnsetsVote() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+        captor.getValue().onRequestEnabled(DISPLAY_ID);
+        captor.getValue().onRequestDisabled(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertNull(vote);
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index ac1dca9..488c533 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -836,9 +836,9 @@
         assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
 
         assertEquals(POSITION_UNKNOWN,
-                mLogicalDisplayMapper.getDisplayLocked(device1).getPositionLocked());
+                mLogicalDisplayMapper.getDisplayLocked(device1).getDevicePositionLocked());
         assertEquals(POSITION_REAR,
-                mLogicalDisplayMapper.getDisplayLocked(device2).getPositionLocked());
+                mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
     }
 
     /////////////////
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index 1d70fc6..d28050d 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -26,12 +26,15 @@
 
 import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
+import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.layout.Layout;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -47,6 +50,7 @@
 
     private LogicalDisplay mLogicalDisplay;
     private DisplayDevice mDisplayDevice;
+    private DisplayDeviceRepository mDeviceRepo;
     private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
 
     @Before
@@ -66,7 +70,7 @@
         // Disable binder caches in this process.
         PropertyInvalidatedCache.disableForTestMode();
 
-        DisplayDeviceRepository repo = new DisplayDeviceRepository(
+        mDeviceRepo = new DisplayDeviceRepository(
                 new DisplayManagerService.SyncRoot(),
                 new PersistentDataStore(new PersistentDataStore.Injector() {
                     @Override
@@ -82,8 +86,8 @@
                     @Override
                     public void finishWrite(OutputStream os, boolean success) {}
                 }));
-        repo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-        mLogicalDisplay.updateLocked(repo);
+        mDeviceRepo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
     }
 
     @Test
@@ -137,4 +141,29 @@
         verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
         reset(t);
     }
+
+    @Test
+    public void testRearDisplaysArePresentationDisplaysThatDestroyContentOnRemoval() {
+        // Assert that the display isn't a presentation display by default, with a default remove
+        // mode
+        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+
+        // Update position and test to see that it's been updated to a rear, presentation display
+        // that destroys content on removal
+        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertEquals(Display.FLAG_REAR | Display.FLAG_PRESENTATION,
+                mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_DESTROY_CONTENT,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+
+        // And then check the unsetting the position resets both
+        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_UNKNOWN);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index d4ab794..ebd63a0 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -47,7 +47,7 @@
 @RunWith(AndroidJUnit4.class)
 public final class DisplayBrightnessControllerTest {
     private static final int DISPLAY_ID = 1;
-    private static final float DEFAULT_BRIGHTNESS = 0.4f;
+    private static final float DEFAULT_BRIGHTNESS = 0.15f;
 
     @Mock
     private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
@@ -70,11 +70,18 @@
                 return mDisplayBrightnessStrategySelector;
             }
         };
+        when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
         mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector,
                 DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable);
     }
 
     @Test
+    public void testIfFirstScreenBrightnessIsDefault() {
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), DEFAULT_BRIGHTNESS,
+                0.0f);
+    }
+
+    @Test
     public void testUpdateBrightness() {
         DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
         DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index cff3bf8..b27ba88 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -38,7 +38,6 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -51,8 +50,8 @@
 
     private static final long POLL_INTERVAL_MS = 500;
     private static final long USER_REMOVE_TIMEOUT_MS = 5_000;
-    private static final long STOP_USER_TIMEOUT_MS = 10_000;
-    private static final long USER_UIDS_REMOVE_TIMEOUT_MS = 15_000;
+    private static final long STOP_USER_TIMEOUT_MS = 20_000;
+    private static final long USER_UIDS_REMOVE_TIMEOUT_MS = 20_000;
     private static final long BATTERYSTATS_POLLING_TIMEOUT_MS = 5_000;
 
     private static final String CPU_DATA_TAG = "cpu";
@@ -79,26 +78,29 @@
         batteryOnScreenOff();
     }
 
-    @Ignore("b/244349060")
     @Test
     public void testNoCpuDataForRemovedUser() throws Exception {
         mIam.startUserInBackground(mTestUserId);
         waitUntilTrue("No uids for started user " + mTestUserId,
                 () -> getNumberOfUidsInBatteryStats() > 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
 
+        final boolean[] userStopped = new boolean[1];
         CountDownLatch stopUserLatch = new CountDownLatch(1);
         mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
             @Override
             public void userStopped(int userId) throws RemoteException {
+                userStopped[0] = true;
                 stopUserLatch.countDown();
             }
 
             @Override
             public void userStopAborted(int userId) throws RemoteException {
+                stopUserLatch.countDown();
             }
         });
-        assertTrue("User " + mTestUserId + " could not be stopped",
+        assertTrue("User " + mTestUserId + " could not be stopped in " + STOP_USER_TIMEOUT_MS,
                 stopUserLatch.await(STOP_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertTrue("User " + mTestUserId + " could not be stopped", userStopped[0]);
 
         mUm.removeUser(mTestUserId);
         waitUntilTrue("Unable to remove user " + mTestUserId, () -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 56d59b4..e5fe32a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3177,7 +3177,7 @@
     public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
+        mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer(
                 mImeWindow, null, null);
         mImeWindow.getControllableInsetProvider().setServerVisible(true);
 
@@ -3222,7 +3222,7 @@
         final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
         final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
 
-        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
+        mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer(
                 mImeWindow, null, null);
         mImeWindow.getControllableInsetProvider().setServerVisible(true);
 
@@ -3288,7 +3288,7 @@
         makeWindowVisibleAndDrawn(app1, app2);
 
         final InsetsStateController controller = mDisplayContent.getInsetsStateController();
-        controller.getSourceProvider(ID_IME).setWindowContainer(
+        controller.getImeSourceProvider().setWindowContainer(
                 ime, null, null);
         ime.getControllableInsetProvider().setServerVisible(true);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 45cf530..6656f4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -163,19 +163,20 @@
         InsetsStateController controller = mDisplayContent.getInsetsStateController();
         controller.onPostLayout();
 
-        InsetsSourceProvider statusBarProvider = controller.getSourceProvider(ITYPE_STATUS_BAR);
+        InsetsSourceProvider statusBarProvider = controller.peekSourceProvider(ITYPE_STATUS_BAR);
         assertEquals(new Rect(0, 0, 500, 100), statusBarProvider.getSource().getFrame());
         assertEquals(Insets.of(0, 100, 0, 0),
                 statusBarProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
                         false /* ignoreVisibility */));
 
-        InsetsSourceProvider topGesturesProvider = controller.getSourceProvider(ITYPE_TOP_GESTURES);
+        InsetsSourceProvider topGesturesProvider = controller.peekSourceProvider(
+                ITYPE_TOP_GESTURES);
         assertEquals(new Rect(0, 0, 500, 100), topGesturesProvider.getSource().getFrame());
         assertEquals(Insets.of(0, 100, 0, 0),
                 topGesturesProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
                         false /* ignoreVisibility */));
 
-        InsetsSourceProvider navigationBarProvider = controller.getSourceProvider(
+        InsetsSourceProvider navigationBarProvider = controller.peekSourceProvider(
                 ITYPE_NAVIGATION_BAR);
         assertNotEquals(new Rect(0, 0, 500, 100), navigationBarProvider.getSource().getFrame());
     }
@@ -194,7 +195,7 @@
         mDisplayContent.getInsetsStateController().onPostLayout();
 
         InsetsSourceProvider provider =
-                mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR);
+                mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_STATUS_BAR);
         // In the new flexible insets setup, the insets frame should always respect the window
         // layout result.
         assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index dba2995..1a126cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -280,8 +280,7 @@
         assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
                 .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
 
-        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
-                true /* isGestureOnSystemBar */);
+        policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */);
         waitUntilWindowAnimatorIdle();
         final InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -308,11 +307,11 @@
         spyOn(policy);
         doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
-        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+        policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
         waitUntilWindowAnimatorIdle();
-        assertTrue(policy.isTransient(ITYPE_STATUS_BAR));
-        assertFalse(policy.isTransient(ITYPE_NAVIGATION_BAR));
+        assertTrue(policy.isTransient(statusBars()));
+        assertFalse(policy.isTransient(navigationBars()));
         final InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
 
@@ -344,7 +343,7 @@
         spyOn(policy);
         doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
-        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+        policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
         waitUntilWindowAnimatorIdle();
         InsetsSourceControl[] controls =
@@ -393,13 +392,13 @@
         spyOn(policy);
         doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(app);
-        policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+        policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
         final InsetsSourceControl[] controls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(app);
         policy.updateBarControlTarget(app2);
-        assertFalse(policy.isTransient(ITYPE_STATUS_BAR));
-        assertFalse(policy.isTransient(ITYPE_NAVIGATION_BAR));
+        assertFalse(policy.isTransient(statusBars()));
+        assertFalse(policy.isTransient(navigationBars()));
     }
 
     private WindowState addNavigationBar() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 88ecd3f..74fde65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -20,11 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -49,6 +46,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 
@@ -64,6 +62,15 @@
 @RunWith(WindowTestRunner.class)
 public class InsetsStateControllerTest extends WindowTestsBase {
 
+    private static final int ID_STATUS_BAR =
+            InsetsSource.createId(null /* owner */, 0 /* index */, statusBars());
+    private static final int ID_NAVIGATION_BAR =
+            InsetsSource.createId(null /* owner */, 0 /* index */, navigationBars());
+    private static final int ID_CLIMATE_BAR =
+            InsetsSource.createId(null /* owner */, 1 /* index */, statusBars());
+    private static final int ID_EXTRA_NAVIGATION_BAR =
+            InsetsSource.createId(null /* owner */, 1 /* index */, navigationBars());
+
     @Test
     public void testStripForDispatch_navBar() {
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
@@ -73,14 +80,15 @@
         // IME cannot be the IME target.
         ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
-        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(ime, null, null);
 
         assertNull(navBar.getInsetsState().peekSource(ID_IME));
-        assertNull(navBar.getInsetsState().peekSource(ITYPE_STATUS_BAR));
+        assertNull(navBar.getInsetsState().peekSource(ID_STATUS_BAR));
     }
 
     @Test
@@ -89,14 +97,14 @@
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
         app.setWindowingMode(WINDOWING_MODE_PINNED);
 
-        assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
-        assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
         assertNull(app.getInsetsState().peekSource(ID_IME));
     }
 
@@ -106,14 +114,14 @@
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
         app.setWindowingMode(WINDOWING_MODE_FREEFORM);
 
-        assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
-        assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
     }
 
     @Test
@@ -122,21 +130,22 @@
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
         app.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         app.setAlwaysOnTop(true);
 
-        assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
-        assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_independentSources() {
-        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
 
         final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
         final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
@@ -151,7 +160,8 @@
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_belowIme() {
-        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         app.mAboveInsetsState.getOrCreateSource(ID_IME, ime())
@@ -165,7 +175,8 @@
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_aboveIme() {
-        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
@@ -184,7 +195,8 @@
 
         // Make IME and stay visible during the test.
         mImeWindow.setHasSurface(true);
-        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
         getController().onImeControlTargetChanged(
                 mDisplayContent.getImeInputTarget().getWindowState());
         mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
@@ -228,7 +240,8 @@
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_childWindow_altFocusable() {
-        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
@@ -248,7 +261,8 @@
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_childWindow_splitScreen() {
-        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
@@ -274,20 +288,21 @@
         ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
 
         WindowContainerInsetsSourceProvider statusBarProvider =
-                getController().getSourceProvider(ITYPE_STATUS_BAR);
+                getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
         final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders =
                 new SparseArray<>();
         imeOverrideProviders.put(TYPE_INPUT_METHOD, ((displayFrames, windowState, rect) ->
                 rect.set(0, 1, 2, 3)));
         statusBarProvider.setWindowContainer(statusBar, null, imeOverrideProviders);
-        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(ime, null, null);
         statusBar.setControllableInsetProvider(statusBarProvider);
         statusBar.updateSourceFrame(statusBar.getFrame());
 
         statusBarProvider.onPostLayout();
 
         final InsetsState state = ime.getInsetsState();
-        assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_STATUS_BAR).getFrame());
+        assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ID_STATUS_BAR).getFrame());
     }
 
     @Test
@@ -297,15 +312,14 @@
         final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar");
         final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindowContainer(climateBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindowContainer(
-                extraNavBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
+        getController().getOrCreateSourceProvider(ID_CLIMATE_BAR, statusBars())
+                .setWindowContainer(climateBar, null, null);
+        getController().getOrCreateSourceProvider(ID_EXTRA_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(extraNavBar, null, null);
         getController().onBarControlTargetChanged(app, null, app, null);
         InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
         assertEquals(4, controls.length);
@@ -315,8 +329,8 @@
     public void testControlRevoked() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
         getController().onBarControlTargetChanged(app, null, null, null);
         assertNotNull(getController().getControlsForDispatch(app));
         getController().onBarControlTargetChanged(null, null, null, null);
@@ -327,8 +341,8 @@
     public void testControlRevoked_animation() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
         getController().onBarControlTargetChanged(app, null, null, null);
         assertNotNull(getController().getControlsForDispatch(app));
         statusBar.cancelAnimation();
@@ -340,22 +354,22 @@
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowContainerInsetsSourceProvider provider = getController()
-                .getSourceProvider(ITYPE_STATUS_BAR);
+                .getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
         provider.setWindowContainer(statusBar, null, null);
 
         final InsetsState rotatedState = new InsetsState(app.getInsetsState(),
                 true /* copySources */);
-        rotatedState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars());
+        rotatedState.getOrCreateSource(ID_STATUS_BAR, statusBars());
         spyOn(app.mToken);
         doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
-        assertTrue(rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        assertTrue(rotatedState.isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars()));
 
         provider.getSource().setVisible(false);
-        mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR },
+        mDisplayContent.getInsetsPolicy().showTransient(statusBars(),
                 true /* isGestureOnSystemBar */);
 
-        assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
-        assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars()));
+        assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars()));
     }
 
     @Test
@@ -364,18 +378,18 @@
         final WindowState statusBar = createTestWindow("statusBar");
         final WindowState navBar = createTestWindow("navBar");
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
 
-        assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNull(navBar.mAboveInsetsState.peekSource(ID_STATUS_BAR));
 
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
 
-        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNotNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNull(navBar.mAboveInsetsState.peekSource(ID_STATUS_BAR));
 
         verify(app, atLeastOnce()).notifyInsetsChanged();
     }
@@ -386,18 +400,18 @@
         final WindowState statusBar = createTestWindow("statusBar");
         final WindowState navBar = createTestWindow("navBar");
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
 
-        assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNull(app.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR));
 
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
 
-        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+        assertNotNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNotNull(app.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR));
 
         verify(app, atLeastOnce()).notifyInsetsChanged();
     }
@@ -409,19 +423,21 @@
         final WindowState statusBar = createTestWindow("statusBar");
         final WindowState navBar = createTestWindow("navBar");
 
-        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
+        final InsetsSourceProvider imeSourceProvider =
+                getController().getOrCreateSourceProvider(ID_IME, ime());
+        imeSourceProvider.setWindowContainer(ime, null, null);
 
         waitUntilHandlersIdle();
         clearInvocations(mDisplayContent);
-        getController().getSourceProvider(ID_IME).setClientVisible(true);
+        imeSourceProvider.setClientVisible(true);
         waitUntilHandlersIdle();
         // The visibility change should trigger a traversal to notify the change.
         verify(mDisplayContent).notifyInsetsChanged(any());
 
-        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
-                null);
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars())
+                .setWindowContainer(statusBar, null, null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
 
         getController().updateAboveInsetsState(false /* notifyInsetsChange */);
 
@@ -429,8 +445,8 @@
         assertNull(app.mAboveInsetsState.peekSource(ID_IME));
         assertNull(statusBar.mAboveInsetsState.peekSource(ID_IME));
         assertNull(navBar.mAboveInsetsState.peekSource(ID_IME));
-        assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+        assertNotNull(ime.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNotNull(ime.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR));
 
         ime.getParent().positionChildAt(POSITION_TOP, ime, true /* includingParents */);
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
@@ -439,8 +455,8 @@
         assertNotNull(app.mAboveInsetsState.peekSource(ID_IME));
         assertNotNull(statusBar.mAboveInsetsState.peekSource(ID_IME));
         assertNotNull(navBar.mAboveInsetsState.peekSource(ID_IME));
-        assertNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
-        assertNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(ime.mAboveInsetsState.peekSource(ID_STATUS_BAR));
+        assertNull(ime.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR));
 
         verify(ime, atLeastOnce()).notifyInsetsChanged();
         verify(app, atLeastOnce()).notifyInsetsChanged();
@@ -454,7 +470,8 @@
         final WindowState ime = createWindow(null,  TYPE_INPUT_METHOD, imeToken, "ime");
         final WindowState app = createTestWindow("app");
 
-        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
+        getController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(ime, null, null);
         ime.getControllableInsetProvider().setServerVisible(true);
 
         app.mActivityRecord.setVisibility(true);
@@ -489,12 +506,12 @@
     @Test
     public void testDispatchGlobalInsets() {
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
-        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
-                null);
+        getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars())
+                .setWindowContainer(navBar, null, null);
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
         app.mAttrs.receiveInsetsIgnoringZOrder = true;
-        assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
+        assertNotNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
@@ -504,7 +521,8 @@
         final WindowState app2 = createTestWindow("app2");
 
         makeWindowVisible(mImeWindow);
-        final InsetsSourceProvider imeInsetsProvider = getController().getSourceProvider(ID_IME);
+        final InsetsSourceProvider imeInsetsProvider =
+                getController().getOrCreateSourceProvider(ID_IME, ime());
         imeInsetsProvider.setWindowContainer(mImeWindow, null, null);
         imeInsetsProvider.updateSourceFrame(mImeWindow.getFrame());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 42bbd2d..94193f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -352,6 +352,7 @@
         mAtmService.setWindowManager(mWmService);
         mWmService.mDisplayEnabled = true;
         mWmService.mDisplayReady = true;
+        mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false;
         // Set configuration for default display
         mWmService.getDefaultDisplayContentLocked().reconfigureDisplayLocked();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 1e5ec4c..7d13de8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -170,10 +170,10 @@
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindowContainer(statusBar, null, null);
-        mProvider.updateControlForFakeTarget(target);
+        mProvider.updateFakeControlTarget(target);
         assertNotNull(mProvider.getControl(target));
         assertNull(mProvider.getControl(target).getLeash());
-        mProvider.updateControlForFakeTarget(null);
+        mProvider.updateFakeControlTarget(null);
         assertNull(mProvider.getControl(target));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index c44869b..17b44ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -437,13 +437,15 @@
         final WindowState app = mAppWindow;
         statusBar.mHasSurface = true;
         assertTrue(statusBar.isVisible());
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
+        mDisplayContent.getInsetsStateController()
+                .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars())
                 .setWindowContainer(statusBar, null /* frameProvider */,
                         null /* imeFrameProvider */);
         mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
                 app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
         app.setRequestedVisibleTypes(0, statusBars());
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
+        mDisplayContent.getInsetsStateController()
+                .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars())
                 .updateClientVisibility(app);
         waitUntilHandlersIdle();
         assertFalse(statusBar.isVisible());
@@ -1169,8 +1171,8 @@
         mNotificationShadeWindow.setHasSurface(true);
         mNotificationShadeWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
         assertTrue(mNotificationShadeWindow.canBeImeTarget());
-        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
-                mImeWindow, null, null);
+        mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(ID_IME, ime())
+                .setWindowContainer(mImeWindow, null, null);
 
         mDisplayContent.computeImeTarget(true);
         assertEquals(mNotificationShadeWindow, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index bd63560..714eb4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -39,6 +39,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets;
 import android.window.WindowContext;
 
 import androidx.test.filters.SmallTest;
@@ -262,8 +263,9 @@
     @Test
     public void testSetInsetsFrozen_notAffectImeWindowState() {
         // Pre-condition: make the IME window be controlled by IME insets provider.
-        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
-                mDisplayContent.mInputMethodWindow, null, null);
+        mDisplayContent.getInsetsStateController()
+                .getOrCreateSourceProvider(ID_IME, WindowInsets.Type.ime())
+                .setWindowContainer(mDisplayContent.mInputMethodWindow, null, null);
 
         // Simulate an app window to be the IME layering target, assume the app window has no
         // frozen insets state by default.
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 7c86a75a..20564d6 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -951,6 +951,23 @@
     public static final String EXTRA_CALL_SOURCE = "android.telecom.extra.CALL_SOURCE";
 
     /**
+     * Intent action to trigger "switch to managed profile" dialog for call in SystemUI
+     *
+     * @hide
+     */
+    public static final String ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG =
+            "android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG";
+
+    /**
+     * Extra specifying the managed profile user id.
+     * This is used with {@link TelecomManager#ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG}
+     *
+     * @hide
+     */
+    public static final String EXTRA_MANAGED_PROFILE_USER_ID =
+            "android.telecom.extra.MANAGED_PROFILE_USER_ID";
+
+    /**
      * Indicating the call is initiated via emergency dialer's shortcut button.
      *
      * @hide
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
index 51ecfb0..836cb53 100644
--- a/telephony/java/android/telephony/CallState.java
+++ b/telephony/java/android/telephony/CallState.java
@@ -285,12 +285,12 @@
     /**
      * Builder of {@link CallState}
      *
-     * <p>The example below shows how you might create a new {@code CallState}:
+     * <p>The example below shows how you might create a new {@code CallState}. A precise call state
+     * {@link PreciseCallStates} is mandatory to build a CallState.
      *
      * <pre><code>
      *
-     * CallState = new CallState.Builder()
-     *     .setCallState(3)
+     * CallState = new CallState.Builder({@link PreciseCallStates})
      *     .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
      *     .setCallQuality({@link CallQuality})
      *     .setImsCallSessionId({@link String})
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c238f24..819b0b2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14894,7 +14894,10 @@
 
     /**
      * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the
-     * the country code in ISO-3166-1 alpha-2 format.
+     * the country code in ISO-3166-1 alpha-2 format. This is the same country code returned by
+     * {@link #getNetworkCountryIso()}. This might be an empty string when the country code is not
+     * available.
+     *
      * <p class="note">
      * Retrieve with {@link android.content.Intent#getStringExtra(String)}.
      */
@@ -14903,11 +14906,11 @@
 
     /**
      * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the
-     * last known the country code in ISO-3166-1 alpha-2 format.
+     * last known the country code in ISO-3166-1 alpha-2 format. This might be an empty string when
+     * the country code was never available. The last known country code persists across reboot.
+     *
      * <p class="note">
      * Retrieve with {@link android.content.Intent#getStringExtra(String)}.
-     *
-     * @hide
      */
     public static final String EXTRA_LAST_KNOWN_NETWORK_COUNTRY =
             "android.telephony.extra.LAST_KNOWN_NETWORK_COUNTRY";
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1ea7fdc..d07edeb 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -27,6 +27,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telecom.VideoProfile;
+import android.telephony.CallState;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting;
 import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
@@ -78,7 +79,9 @@
     public static final int SERVICE_TYPE_EMERGENCY = 2;
 
     /**
-     * Call type none
+     * This value is returned if there is no valid IMS call type defined for the call. For example,
+     * if an ongoing call is circuit-switched and {@link CallState#getImsCallType()} is called, this
+     * value will be returned.
      */
     public static final int CALL_TYPE_NONE = 0;
     /**
diff --git a/wifi/java/Android.bp b/wifi/java/Android.bp
index 225e750..434226d 100644
--- a/wifi/java/Android.bp
+++ b/wifi/java/Android.bp
@@ -27,7 +27,10 @@
 
 filegroup {
     name: "framework-wifi-non-updatable-sources-internal",
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
     path: "src",
     visibility: ["//visibility:private"],
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.aidl
new file mode 100644
index 0000000..35d5c15
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+parcelable DeviceInfo;
\ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java
new file mode 100644
index 0000000..7874b2a
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A data class representing a device providing connectivity.
+ * This class is used in IPC calls between the implementer of {@link SharedConnectivityService} and
+ * the consumers of {@link com.android.wifitrackerlib}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceInfo implements Parcelable {
+
+    /**
+     * Device type providing connectivity is unknown.
+     */
+    public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+    /**
+     * Device providing connectivity is a mobile phone.
+     */
+    public static final int DEVICE_TYPE_PHONE = 1;
+
+    /**
+     * Device providing connectivity is a tablet.
+     */
+    public static final int DEVICE_TYPE_TABLET = 2;
+
+    /**
+     * Device providing connectivity is a laptop.
+     */
+    public static final int DEVICE_TYPE_LAPTOP = 3;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DEVICE_TYPE_UNKNOWN,
+            DEVICE_TYPE_PHONE,
+            DEVICE_TYPE_TABLET,
+            DEVICE_TYPE_LAPTOP
+    })
+    public @interface DeviceType {}
+
+    @DeviceType private final int mDeviceType;
+    private final String mDeviceName;
+    private final String mModelName;
+    private final int mBatteryPercentage;
+    private final int mConnectionStrength;
+
+    /**
+     * Builder class for {@link DeviceInfo}.
+     */
+    public static final class Builder {
+        private int mDeviceType;
+        private String mDeviceName;
+        private String mModelName;
+        private int mBatteryPercentage;
+        private int mConnectionStrength;
+
+        public Builder() {}
+
+        /**
+         * Sets the device type that provides connectivity.
+         *
+         * @param deviceType Device type as represented by IntDef {@link DeviceType}.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setDeviceType(@DeviceType int deviceType) {
+            mDeviceType = deviceType;
+            return this;
+        }
+
+        /**
+         * Sets the device name of the remote device.
+         *
+         * @param deviceName The user configurable device name.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setDeviceName(@NonNull String deviceName) {
+            mDeviceName = deviceName;
+            return this;
+        }
+
+        /**
+         * Sets the model name of the remote device.
+         *
+         * @param modelName The OEM configured name for the device model.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setModelName(@NonNull String modelName) {
+            mModelName = modelName;
+            return this;
+        }
+
+        /**
+         * Sets the battery charge percentage of the remote device.
+         *
+         * @param batteryPercentage The battery charge percentage in the range 0 to 100.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setBatteryPercentage(@IntRange(from = 0, to = 100) int batteryPercentage) {
+            mBatteryPercentage = batteryPercentage;
+            return this;
+        }
+
+        /**
+         * Sets the displayed connection strength of the remote device to the internet.
+         *
+         * @param connectionStrength Connection strength in range 0 to 3.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+            mConnectionStrength = connectionStrength;
+            return this;
+        }
+
+        /**
+         * Builds the {@link DeviceInfo} object.
+         *
+         * @return Returns the built {@link DeviceInfo} object.
+         */
+        @NonNull
+        public DeviceInfo build() {
+            return new DeviceInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
+                    mConnectionStrength);
+        }
+    }
+
+    private static void validate(int deviceType, String deviceName, String modelName,
+            int batteryPercentage, int connectionStrength) {
+        if (deviceType != DEVICE_TYPE_UNKNOWN && deviceType != DEVICE_TYPE_PHONE
+                && deviceType != DEVICE_TYPE_TABLET && deviceType != DEVICE_TYPE_LAPTOP) {
+            throw new IllegalArgumentException("Illegal device type");
+        }
+        if (Objects.isNull(deviceName)) {
+            throw new IllegalArgumentException("DeviceName must be set");
+        }
+        if (Objects.isNull(modelName)) {
+            throw new IllegalArgumentException("ModelName must be set");
+        }
+        if (batteryPercentage < 0 || batteryPercentage > 100) {
+            throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
+        }
+        if (connectionStrength < 0 || connectionStrength > 3) {
+            throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+        }
+    }
+
+    private DeviceInfo(@DeviceType int deviceType, @NonNull String deviceName,
+            @NonNull String modelName, int batteryPercentage, int connectionStrength) {
+        validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength);
+        mDeviceType = deviceType;
+        mDeviceName = deviceName;
+        mModelName = modelName;
+        mBatteryPercentage = batteryPercentage;
+        mConnectionStrength = connectionStrength;
+    }
+
+    /**
+     * Gets the device type that provides connectivity.
+     *
+     * @return Returns the device type as represented by IntDef {@link DeviceType}.
+     */
+    @DeviceType
+    public int getDeviceType() {
+        return mDeviceType;
+    }
+
+    /**
+     * Gets the device name of the remote device.
+     *
+     * @return Returns the user configurable device name.
+     */
+    @NonNull
+    public String getDeviceName() {
+        return mDeviceName;
+    }
+
+    /**
+     * Gets the model name of the remote device.
+     *
+     * @return Returns the OEM configured name for the device model.
+     */
+    @NonNull
+    public String getModelName() {
+        return mModelName;
+    }
+
+    /**
+     * Gets the battery charge percentage of the remote device.
+     *
+     * @return Returns the battery charge percentage in the range 0 to 100.
+     */
+    @NonNull
+    @IntRange(from = 0, to = 100)
+    public int getBatteryPercentage() {
+        return mBatteryPercentage;
+    }
+
+    /**
+     * Gets the displayed connection strength of the remote device to the internet.
+     *
+     * @return Returns the connection strength in range 0 to 3.
+     */
+    @NonNull
+    @IntRange(from = 0, to = 3)
+    public int getConnectionStrength() {
+        return mConnectionStrength;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DeviceInfo)) return false;
+        DeviceInfo other = (DeviceInfo) obj;
+        return mDeviceType == other.getDeviceType()
+                && Objects.equals(mDeviceName, other.mDeviceName)
+                && Objects.equals(mModelName, other.mModelName)
+                && mBatteryPercentage == other.mBatteryPercentage
+                && mConnectionStrength == other.mConnectionStrength;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
+                mConnectionStrength);
+    }
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mDeviceType);
+        dest.writeString(mDeviceName);
+        dest.writeString(mModelName);
+        dest.writeInt(mBatteryPercentage);
+        dest.writeInt(mConnectionStrength);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<DeviceInfo> CREATOR = new Creator<DeviceInfo>() {
+        @Override
+        public DeviceInfo createFromParcel(Parcel in) {
+            return new DeviceInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
+                    in.readInt());
+        }
+
+        @Override
+        public DeviceInfo[] newArray(int size) {
+            return new DeviceInfo[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return new StringBuilder("DeviceInfo[")
+                .append("deviceType=").append(mDeviceType)
+                .append(", deviceName=").append(mDeviceName)
+                .append(", modelName=").append(mModelName)
+                .append(", batteryPercentage=").append(mBatteryPercentage)
+                .append(", connectionStrength=").append(mConnectionStrength)
+                .append("]").toString();
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl
new file mode 100644
index 0000000..140d72a
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+parcelable KnownNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
new file mode 100644
index 0000000..34b7e94
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiAnnotations.SecurityType;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A data class representing a known Wifi network.
+ *
+ * @hide
+ */
+@SystemApi
+public final class KnownNetwork implements Parcelable {
+    /**
+     * Network is known by a nearby device with the same user account.
+     */
+    public static final int NETWORK_SOURCE_NEARBY_SELF = 0;
+
+    /**
+     * Network is known via cloud storage associated with this device's user account.
+     */
+    public static final int NETWORK_SOURCE_CLOUD_SELF = 1;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            NETWORK_SOURCE_NEARBY_SELF,
+            NETWORK_SOURCE_CLOUD_SELF
+    })
+    public @interface NetworkSource {}
+
+    @NetworkSource private final int mNetworkSource;
+    private final String mSsid;
+    @SecurityType private final int[] mSecurityTypes;
+    private final DeviceInfo mDeviceInfo;
+
+    /**
+     * Builder class for {@link KnownNetwork}.
+     */
+    public static final class Builder {
+        @NetworkSource private int mNetworkSource = -1;
+        private String mSsid;
+        @SecurityType private int[] mSecurityTypes;
+        private android.net.wifi.sharedconnectivity.app.DeviceInfo mDeviceInfo;
+
+        public Builder() {}
+
+        /**
+         * Sets the indicated source of the known network.
+         *
+         * @param networkSource The network source as defined by IntDef {@link NetworkSource}.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setNetworkSource(@NetworkSource int networkSource) {
+            mNetworkSource = networkSource;
+            return this;
+        }
+
+        /**
+         * Sets the SSID of the known network.
+         *
+         * @param ssid The SSID of the known network. Surrounded by double quotes if UTF-8.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setSsid(@NonNull String ssid) {
+            mSsid = ssid;
+            return this;
+        }
+
+        /**
+         * Sets the security types of the known network.
+         *
+         * @param securityTypes The array of security types supported by the known network.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setSecurityTypes(@NonNull @SecurityType int[] securityTypes) {
+            mSecurityTypes = securityTypes;
+            return this;
+        }
+
+        /**
+         * Sets the device information of the device providing connectivity.
+         *
+         * @param deviceInfo The array of security types supported by the known network.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setDeviceInfo(@NonNull DeviceInfo deviceInfo) {
+            mDeviceInfo = deviceInfo;
+            return this;
+        }
+
+        /**
+         * Builds the {@link KnownNetwork} object.
+         *
+         * @return Returns the built {@link KnownNetwork} object.
+         */
+        @NonNull
+        public KnownNetwork build() {
+            return new KnownNetwork(
+                    mNetworkSource,
+                    mSsid,
+                    mSecurityTypes,
+                    mDeviceInfo);
+        }
+    }
+
+    private static void validate(int networkSource, String ssid, int [] securityTypes) {
+        if (networkSource != NETWORK_SOURCE_CLOUD_SELF && networkSource
+                != NETWORK_SOURCE_NEARBY_SELF) {
+            throw new IllegalArgumentException("Illegal network source");
+        }
+        if (TextUtils.isEmpty(ssid)) {
+            throw new IllegalArgumentException("SSID must be set");
+        }
+        if (securityTypes == null || securityTypes.length == 0) {
+            throw new IllegalArgumentException("SecurityTypes must be set");
+        }
+    }
+
+    private KnownNetwork(
+            @NetworkSource int networkSource,
+            @NonNull String ssid,
+            @NonNull @SecurityType int[] securityTypes,
+            @NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo deviceInfo) {
+        validate(networkSource, ssid, securityTypes);
+        mNetworkSource = networkSource;
+        mSsid = ssid;
+        mSecurityTypes = securityTypes;
+        mDeviceInfo = deviceInfo;
+    }
+
+    /**
+     * Gets the indicated source of the known network.
+     *
+     * @return Returns the network source as defined by IntDef {@link NetworkSource}.
+     */
+    @NonNull
+    @NetworkSource
+    public int getNetworkSource() {
+        return mNetworkSource;
+    }
+
+    /**
+     * Gets the SSID of the known network.
+     *
+     * @return Returns the SSID of the known network. Surrounded by double quotes if UTF-8.
+     */
+    @NonNull
+    public String getSsid() {
+        return mSsid;
+    }
+
+    /**
+     * Gets the security types of the known network.
+     *
+     * @return Returns the array of security types supported by the known network.
+     */
+    @NonNull
+    @SecurityType
+    public int[] getSecurityTypes() {
+        return mSecurityTypes;
+    }
+
+    /**
+     * Gets the device information of the device providing connectivity.
+     *
+     * @return Returns the array of security types supported by the known network.
+     */
+    @NonNull
+    public DeviceInfo getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof KnownNetwork)) return false;
+        KnownNetwork other = (KnownNetwork) obj;
+        return mNetworkSource == other.getNetworkSource()
+                && Objects.equals(mSsid, other.getSsid())
+                && Arrays.equals(mSecurityTypes, other.getSecurityTypes())
+                && Objects.equals(mDeviceInfo, other.getDeviceInfo());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNetworkSource, mSsid, Arrays.hashCode(mSecurityTypes),
+                mDeviceInfo.hashCode());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mNetworkSource);
+        dest.writeString(mSsid);
+        dest.writeIntArray(mSecurityTypes);
+        dest.writeTypedObject(mDeviceInfo, 0);
+    }
+
+    @NonNull
+    public static final Creator<KnownNetwork> CREATOR = new Creator<>() {
+        @Override
+        public KnownNetwork createFromParcel(Parcel in) {
+            return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(),
+                    in.readTypedObject(DeviceInfo.CREATOR));
+        }
+
+        @Override
+        public KnownNetwork[] newArray(int size) {
+            return new KnownNetwork[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return new StringBuilder("KnownNetwork[")
+                .append("NetworkSource=").append(mNetworkSource)
+                .append(", ssid=").append(mSsid)
+                .append(", securityTypes=").append(Arrays.toString(mSecurityTypes))
+                .append(", deviceInfo=").append(mDeviceInfo.toString())
+                .append("]").toString();
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java
new file mode 100644
index 0000000..dcb5201
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+
+import java.util.List;
+
+/**
+ * Interface for clients of {@link SharedConnectivityManager} to register for changes in network
+ * status.
+ *
+ * @hide
+ */
+@SystemApi
+public interface SharedConnectivityClientCallback {
+    /**
+     * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+     * list of available Tether Networks.
+     * @param networks Updated Tether Network list.
+     */
+    void onTetherNetworksUpdated(@NonNull List<TetherNetwork> networks);
+
+    /**
+     * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+     * list of available Known Networks.
+     * @param networks Updated Known Network list.
+     */
+    void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks);
+
+    /**
+     * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+     * state of share connectivity settings.
+     * @param state The new state.
+     */
+    void onSharedConnectivitySettingsChanged(@NonNull SharedConnectivitySettingsState state);
+}
+
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
new file mode 100644
index 0000000..b43e4f7
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.Resources;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is the library used by consumers of Shared Connectivity data to bind to the service,
+ * receive callbacks from, and send user actions to the service.
+ *
+ * @hide
+ */
+@SystemApi
+public class SharedConnectivityManager {
+    private static final String TAG = SharedConnectivityManager.class.getSimpleName();
+    private static final boolean DEBUG = true;
+    private static final String SERVICE_PACKAGE_NAME = "sharedconnectivity_service_package";
+    private static final String SERVICE_CLASS_NAME = "sharedconnectivity_service_class";
+
+    private static final class SharedConnectivityCallbackProxy extends
+            ISharedConnectivityCallback.Stub {
+        private final Executor mExecutor;
+        private final SharedConnectivityClientCallback mCallback;
+
+        SharedConnectivityCallbackProxy(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull SharedConnectivityClientCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onTetherNetworksUpdated(@NonNull List<TetherNetwork> networks) {
+            if (mCallback != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onTetherNetworksUpdated(networks));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+
+        @Override
+        public void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks) {
+            if (mCallback != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onKnownNetworksUpdated(networks));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+
+        @Override
+        public void onSharedConnectivitySettingsChanged(
+                @NonNull SharedConnectivitySettingsState state) {
+            if (mCallback != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onSharedConnectivitySettingsChanged(state));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        };
+    }
+
+    private ISharedConnectivityService mService;
+    private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy>
+            mProxyMap = new HashMap<>();
+
+    /**
+     * Constructor for new instance of {@link SharedConnectivityManager}.
+     *
+     * Automatically binds to implementation of {@link SharedConnectivityService} specified in
+     * device overlay.
+     */
+    @SuppressLint("ManagerConstructor")
+    public SharedConnectivityManager(@NonNull Context context) {
+        ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                mService = ISharedConnectivityService.Stub.asInterface(service);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                if (DEBUG) Log.i(TAG, "onServiceDisconnected");
+                mService = null;
+                mProxyMap.clear();
+            }
+        };
+        bind(context, serviceConnection);
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public void setService(@Nullable IInterface service) {
+        mService = (ISharedConnectivityService) service;
+    }
+
+    private void bind(Context context, ServiceConnection serviceConnection) {
+        Resources resources = context.getResources();
+        int packageNameId = resources.getIdentifier(SERVICE_PACKAGE_NAME, "string",
+                context.getPackageName());
+        int classNameId = resources.getIdentifier(SERVICE_CLASS_NAME, "string",
+                context.getPackageName());
+        if (packageNameId == 0 || classNameId == 0) {
+            throw new Resources.NotFoundException("Package and class names for"
+                    + " shared connectivity service must be defined");
+        }
+
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(resources.getString(packageNameId),
+                resources.getString(classNameId)));
+        context.bindService(
+                intent,
+                serviceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    /**
+     * Registers a callback for receiving updates to the list of Tether Networks and Known Networks.
+     *
+     * @param executor The Executor used to invoke the callback.
+     * @param callback The callback of type {@link SharedConnectivityClientCallback} that is invoked
+     *                 when the service updates either the list of Tether Networks or Known
+     *                 Networks.
+     * @return Returns true if the registration was successful, false otherwise.
+     */
+    public boolean registerCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull SharedConnectivityClientCallback callback) {
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+        if (mService == null || mProxyMap.containsKey(callback)) return false;
+        try {
+            SharedConnectivityCallbackProxy proxy =
+                    new SharedConnectivityCallbackProxy(executor, callback);
+            mService.registerCallback(proxy);
+            mProxyMap.put(callback, proxy);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in registerCallback", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Unregisters a callback.
+     *
+     * @return Returns true if the callback was successfully unregistered, false otherwise.
+     */
+    public boolean unregisterCallback(
+            @NonNull SharedConnectivityClientCallback callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
+        if (mService == null || !mProxyMap.containsKey(callback)) return false;
+        try {
+            mService.unregisterCallback(mProxyMap.get(callback));
+            mProxyMap.remove(callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in unregisterCallback", e);
+            return false;
+        }
+        return true;
+    }
+
+     /**
+     * Send command to the implementation of {@link SharedConnectivityService} requesting connection
+     * to the specified Tether Network.
+     *
+     * @param network {@link TetherNetwork} object representing the network the user has requested
+     *                a connection to.
+     * @return Returns true if the service received the command. Does not guarantee that the
+     *         connection was successful.
+     */
+    public boolean connectTetherNetwork(@NonNull TetherNetwork network) {
+        if (mService == null) return false;
+        try {
+            mService.connectTetherNetwork(network);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in connectTetherNetwork", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Send command to the implementation of {@link SharedConnectivityService} requesting
+     * disconnection from the active Tether Network.
+     *
+     * @return Returns true if the service received the command. Does not guarantee that the
+     *         disconnection was successful.
+     */
+    public boolean disconnectTetherNetwork() {
+        if (mService == null) return false;
+        try {
+            mService.disconnectTetherNetwork();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in disconnectTetherNetwork", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Send command to the implementation of {@link SharedConnectivityService} requesting connection
+     * to the specified Known Network.
+     *
+     * @param network {@link KnownNetwork} object representing the network the user has requested
+     *                a connection to.
+     * @return Returns true if the service received the command. Does not guarantee that the
+     *         connection was successful.
+     */
+    public boolean connectKnownNetwork(@NonNull KnownNetwork network) {
+        if (mService == null) return false;
+        try {
+            mService.connectKnownNetwork(network);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in connectKnownNetwork", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Send command to the implementation of {@link SharedConnectivityService} requesting removal of
+     * the specified Known Network from the list of Known Networks.
+     *
+     * @return Returns true if the service received the command. Does not guarantee that the
+     *         forget action was successful.
+     */
+    public boolean forgetKnownNetwork(@NonNull KnownNetwork network) {
+        if (mService == null) return false;
+        try {
+            mService.forgetKnownNetwork(network);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in forgetKnownNetwork", e);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl
new file mode 100644
index 0000000..289afac
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+parcelable SharedConnectivitySettingsState;
\ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
new file mode 100644
index 0000000..dd2fa94
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+
+/**
+ * A data class representing the shared connectivity settings state.
+ *
+ * This class represents a snapshot of the settings and can be out of date if the settings changed
+ * after receiving an object of this class.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SharedConnectivitySettingsState implements Parcelable {
+
+    private final boolean mInstantTetherEnabled;
+    private final Bundle mExtras;
+
+    /**
+     * Builder class for {@link SharedConnectivitySettingsState}.
+     */
+    public static final class Builder {
+        private boolean mInstantTetherEnabled;
+        private Bundle mExtras;
+
+        public Builder() {}
+
+        /**
+         * Sets the state of Instant Tether in settings
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setInstantTetherEnabled(boolean instantTetherEnabled) {
+            mInstantTetherEnabled = instantTetherEnabled;
+            return this;
+        }
+
+        /**
+         * Sets the extras bundle
+         *
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds the {@link SharedConnectivitySettingsState} object.
+         *
+         * @return Returns the built {@link SharedConnectivitySettingsState} object.
+         */
+        @NonNull
+        public SharedConnectivitySettingsState build() {
+            return new SharedConnectivitySettingsState(mInstantTetherEnabled, mExtras);
+        }
+    }
+
+    private SharedConnectivitySettingsState(boolean instantTetherEnabled, Bundle extras) {
+        mInstantTetherEnabled = instantTetherEnabled;
+        mExtras = extras;
+    }
+
+    /**
+     * Gets the state of Instant Tether in settings
+     *
+     * @return Returns true for enabled, false otherwise.
+     */
+    @NonNull
+    public boolean isInstantTetherEnabled() {
+        return mInstantTetherEnabled;
+    }
+
+    /**
+     * Gets the extras Bundle.
+     *
+     * @return Returns a Bundle object.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SharedConnectivitySettingsState)) return false;
+        SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj;
+        return mInstantTetherEnabled == other.isInstantTetherEnabled();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mInstantTetherEnabled);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mInstantTetherEnabled);
+        dest.writeBundle(mExtras);
+    }
+
+    @NonNull
+    public static final Creator<SharedConnectivitySettingsState> CREATOR =
+            new Creator<SharedConnectivitySettingsState>() {
+                @Override
+                public SharedConnectivitySettingsState createFromParcel(Parcel in) {
+                    return new SharedConnectivitySettingsState(in.readBoolean(),
+                            in.readBundle(getClass().getClassLoader()));
+                }
+
+                @Override
+                public SharedConnectivitySettingsState[] newArray(int size) {
+                    return new SharedConnectivitySettingsState[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return new StringBuilder("SharedConnectivitySettingsState[")
+                .append("instantTetherEnabled=").append(mInstantTetherEnabled)
+                .append("extras=").append(mExtras.toString())
+                .append("]").toString();
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.aidl
new file mode 100644
index 0000000..6cc4cfe
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+parcelable TetherNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
new file mode 100644
index 0000000..bbdad53
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiAnnotations.SecurityType;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A data class representing an Instant Tether network.
+ * This class is used in IPC calls between the implementer of {@link SharedConnectivityService} and
+ * the consumers of {@link com.android.wifitrackerlib}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TetherNetwork implements Parcelable {
+    /**
+     * Remote device is connected to the internet via an unknown connection.
+     */
+    public static final int NETWORK_TYPE_UNKNOWN = 0;
+
+    /**
+     * Remote device is connected to the internet via a cellular connection.
+     */
+    public static final int NETWORK_TYPE_CELLULAR = 1;
+
+    /**
+     * Remote device is connected to the internet via a Wi-Fi connection.
+     */
+    public static final int NETWORK_TYPE_WIFI = 2;
+
+    /**
+     * Remote device is connected to the internet via an ethernet connection.
+     */
+    public static final int NETWORK_TYPE_ETHERNET = 3;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            NETWORK_TYPE_UNKNOWN,
+            NETWORK_TYPE_CELLULAR,
+            NETWORK_TYPE_WIFI,
+            NETWORK_TYPE_ETHERNET
+    })
+    public @interface NetworkType {}
+
+    private final long mDeviceId;
+    private final DeviceInfo mDeviceInfo;
+    @NetworkType private final int mNetworkType;
+    private final String mNetworkName;
+    @Nullable private final String mHotspotSsid;
+    @Nullable private final String mHotspotBssid;
+    @Nullable @SecurityType private final int[] mHotspotSecurityTypes;
+
+    /**
+     * Builder class for {@link TetherNetwork}.
+     */
+    public static final class Builder {
+        private long mDeviceId = -1;
+        private DeviceInfo mDeviceInfo;
+        @NetworkType private int mNetworkType;
+        private String mNetworkName;
+        @Nullable private String mHotspotSsid;
+        @Nullable private String mHotspotBssid;
+        @Nullable @SecurityType private int[] mHotspotSecurityTypes;
+
+        public Builder() {}
+
+        /**
+         * Set the remote device ID.
+         *
+         * @param deviceId Locally unique ID for this Instant Tether network.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setDeviceId(long deviceId) {
+            mDeviceId = deviceId;
+            return this;
+        }
+
+        /**
+         * Sets information about the device providing connectivity.
+         *
+         * @param deviceInfo The user configurable device name.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setDeviceInfo(@NonNull DeviceInfo deviceInfo) {
+            mDeviceInfo = deviceInfo;
+            return this;
+        }
+
+        /**
+         * Sets the network type that the remote device is connected to.
+         *
+         * @param networkType Network type as represented by IntDef {@link NetworkType}.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setNetworkType(@NetworkType int networkType) {
+            mNetworkType = networkType;
+            return this;
+        }
+
+        /**
+         * Sets the display name of the network the remote device is connected to.
+         *
+         * @param networkName Network display name. (e.g. "Google Fi", "Hotel WiFi", "Ethernet")
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setNetworkName(@NonNull String networkName) {
+            mNetworkName = networkName;
+            return this;
+        }
+
+        /**
+         * Sets the hotspot SSID being broadcast by the remote device, or null if hotspot is off.
+         *
+         * @param hotspotSsid The SSID of the hotspot. Surrounded by double quotes if UTF-8.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setHotspotSsid(@NonNull String hotspotSsid) {
+            mHotspotSsid = hotspotSsid;
+            return this;
+        }
+
+        /**
+         * Sets the hotspot BSSID being broadcast by the remote device, or null if hotspot is off.
+         *
+         * @param hotspotBssid The BSSID of the hotspot.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setHotspotBssid(@NonNull String hotspotBssid) {
+            mHotspotBssid = hotspotBssid;
+            return this;
+        }
+
+        /**
+         * Sets the hotspot security types supported by the remote device, or null if hotspot is
+         * off.
+         *
+         * @param hotspotSecurityTypes The array of security types supported by the hotspot.
+         * @return Returns the Builder object.
+         */
+        @NonNull
+        public Builder setHotspotSecurityTypes(@NonNull @SecurityType int[] hotspotSecurityTypes) {
+            mHotspotSecurityTypes = hotspotSecurityTypes;
+            return this;
+        }
+
+        /**
+         * Builds the {@link TetherNetwork} object.
+         *
+         * @return Returns the built {@link TetherNetwork} object.
+         */
+        @NonNull
+        public TetherNetwork build() {
+            return new TetherNetwork(
+                    mDeviceId,
+                    mDeviceInfo,
+                    mNetworkType,
+                    mNetworkName,
+                    mHotspotSsid,
+                    mHotspotBssid,
+                    mHotspotSecurityTypes);
+        }
+    }
+
+    private static void validate(long deviceId, int networkType, String networkName) {
+        if (deviceId < 0) {
+            throw new IllegalArgumentException("DeviceId must be set");
+        }
+        if (networkType != NETWORK_TYPE_CELLULAR && networkType != NETWORK_TYPE_WIFI
+                && networkType != NETWORK_TYPE_ETHERNET && networkType != NETWORK_TYPE_UNKNOWN) {
+            throw new IllegalArgumentException("Illegal network type");
+        }
+        if (Objects.isNull(networkName)) {
+            throw new IllegalArgumentException("NetworkName must be set");
+        }
+    }
+
+    private TetherNetwork(
+            long deviceId,
+            DeviceInfo deviceInfo,
+            @NetworkType int networkType,
+            @NonNull String networkName,
+            @Nullable String hotspotSsid,
+            @Nullable String hotspotBssid,
+            @Nullable @SecurityType int[] hotspotSecurityTypes) {
+        validate(deviceId,
+                networkType,
+                networkName);
+        mDeviceId = deviceId;
+        mDeviceInfo = deviceInfo;
+        mNetworkType = networkType;
+        mNetworkName = networkName;
+        mHotspotSsid = hotspotSsid;
+        mHotspotBssid = hotspotBssid;
+        mHotspotSecurityTypes = hotspotSecurityTypes;
+    }
+
+    /**
+     * Gets the remote device ID.
+     *
+     * @return Returns the locally unique ID for this Instant Tether network.
+     */
+    @NonNull
+    public long getDeviceId() {
+        return mDeviceId;
+    }
+
+    /**
+     * Gets information about the device providing connectivity.
+     *
+     * @return Returns the locally unique ID for this Instant Tether network.
+     */
+    @NonNull
+    public DeviceInfo getDeviceInfo() {
+        return mDeviceInfo;
+    }
+
+    /**
+     * Gets the network type that the remote device is connected to.
+     *
+     * @return Returns the network type as represented by IntDef {@link NetworkType}.
+     */
+    @NonNull
+    @NetworkType
+    public int getNetworkType() {
+        return mNetworkType;
+    }
+
+    /**
+     * Gets the display name of the network the remote device is connected to.
+     *
+     * @return Returns the network display name. (e.g. "Google Fi", "Hotel WiFi", "Ethernet")
+     */
+    @NonNull
+    public String getNetworkName() {
+        return mNetworkName;
+    }
+
+    /**
+     * Gets the hotspot SSID being broadcast by the remote device, or null if hotspot is off.
+     *
+     * @return Returns the SSID of the hotspot. Surrounded by double quotes if UTF-8.
+     */
+    @Nullable
+    public String getHotspotSsid() {
+        return mHotspotSsid;
+    }
+
+    /**
+     * Gets the hotspot BSSID being broadcast by the remote device, or null if hotspot is off.
+     *
+     * @return Returns the BSSID of the hotspot.
+     */
+    @Nullable
+    public String getHotspotBssid() {
+        return mHotspotBssid;
+    }
+
+    /**
+     * Gets the hotspot security types supported by the remote device.
+     *
+     * @return Returns the array of security types supported by the hotspot.
+     */
+    @Nullable
+    @SecurityType
+    public int[] getHotspotSecurityTypes() {
+        return mHotspotSecurityTypes;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof TetherNetwork)) return false;
+        TetherNetwork other = (TetherNetwork) obj;
+        return mDeviceId == other.getDeviceId()
+                && Objects.equals(mDeviceInfo, other.getDeviceInfo())
+                && mNetworkType == other.getNetworkType()
+                && Objects.equals(mNetworkName, other.getNetworkName())
+                && Objects.equals(mHotspotSsid, other.getHotspotSsid())
+                && Objects.equals(mHotspotBssid, other.getHotspotBssid())
+                && Arrays.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDeviceId, mDeviceInfo, mNetworkName, mHotspotSsid, mHotspotBssid,
+                Arrays.hashCode(mHotspotSecurityTypes));
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeLong(mDeviceId);
+        dest.writeTypedObject(mDeviceInfo, 0);
+        dest.writeInt(mNetworkType);
+        dest.writeString(mNetworkName);
+        dest.writeString(mHotspotSsid);
+        dest.writeString(mHotspotBssid);
+        dest.writeIntArray(mHotspotSecurityTypes);
+    }
+
+    @NonNull
+    public static final Creator<TetherNetwork> CREATOR = new Creator<>() {
+        @Override
+        public TetherNetwork createFromParcel(Parcel in) {
+            return new TetherNetwork(in.readLong(), in.readTypedObject(
+                    android.net.wifi.sharedconnectivity.app.DeviceInfo.CREATOR),
+                    in.readInt(), in.readString(), in.readString(), in.readString(),
+                    in.createIntArray());
+        }
+
+        @Override
+        public TetherNetwork[] newArray(int size) {
+            return new TetherNetwork[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return new StringBuilder("TetherNetwork[")
+                .append("deviceId=").append(mDeviceId)
+                .append(", networkType=").append(mNetworkType)
+                .append(", deviceInfo=").append(mDeviceInfo.toString())
+                .append(", networkName=").append(mNetworkName)
+                .append(", hotspotSsid=").append(mHotspotSsid)
+                .append(", hotspotBssid=").append(mHotspotBssid)
+                .append(", hotspotSecurityTypes=").append(Arrays.toString(mHotspotSecurityTypes))
+                .append("]").toString();
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
new file mode 100644
index 0000000..6e56138
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.service;
+
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.net.wifi.sharedconnectivity.app.TetherNetwork;
+
+/*
+ * @hide
+ */
+interface ISharedConnectivityCallback {
+    oneway void onTetherNetworksUpdated(in List<TetherNetwork> networks);
+    oneway void onKnownNetworksUpdated(in List<KnownNetwork> networks);
+    oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
new file mode 100644
index 0000000..5d79405
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.service;
+
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.TetherNetwork;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback;
+
+/*
+ * @hide
+ */
+interface ISharedConnectivityService {
+    void registerCallback(in ISharedConnectivityCallback callback);
+    void unregisterCallback(in ISharedConnectivityCallback callback);
+    void connectTetherNetwork(in TetherNetwork network);
+    void disconnectTetherNetwork();
+    void connectKnownNetwork(in KnownNetwork network);
+    void forgetKnownNetwork(in KnownNetwork network);
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
new file mode 100644
index 0000000..234319a
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.service;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.net.wifi.sharedconnectivity.app.TetherNetwork;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * This class is the partly implemented service for injecting Shared Connectivity networks into the
+ * Wi-Fi Pickers and other relevant UI surfaces.
+ *
+ * Implementing application should extend this service and override the indicated methods.
+ * Callers to the service should use {@link SharedConnectivityManager} to bind to the implemented
+ * service as specified in the configuration overlay.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SharedConnectivityService extends Service {
+    private static final String TAG = SharedConnectivityService.class.getSimpleName();
+    private static final boolean DEBUG = true;
+
+    private  final Handler mHandler;
+    private final List<ISharedConnectivityCallback> mCallbacks = new ArrayList<>();
+    // Used to find DeathRecipient when unregistering a callback to call unlinkToDeath.
+    private final Map<ISharedConnectivityCallback, DeathRecipient> mDeathRecipientMap =
+            new HashMap<>();
+
+    private List<TetherNetwork> mTetherNetworks = Collections.emptyList();
+    private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
+    private SharedConnectivitySettingsState mSettingsState;
+
+    public SharedConnectivityService() {
+        mHandler = new Handler(getMainLooper());
+    }
+
+    public SharedConnectivityService(@NonNull Handler handler) {
+        mHandler = handler;
+    }
+
+    private final class DeathRecipient implements IBinder.DeathRecipient {
+        ISharedConnectivityCallback mCallback;
+
+        DeathRecipient(ISharedConnectivityCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void binderDied() {
+            mCallbacks.remove(mCallback);
+            mDeathRecipientMap.remove(mCallback);
+        }
+    }
+
+    @Override
+    @Nullable
+    public IBinder onBind(@NonNull Intent intent) {
+        if (DEBUG) Log.i(TAG, "onBind intent=" + intent);
+        return new ISharedConnectivityService.Stub() {
+            @Override
+            public void registerCallback(ISharedConnectivityCallback callback) {
+                checkPermissions();
+                mHandler.post(() -> registerCallback(callback));
+            }
+
+            @Override
+            public void unregisterCallback(ISharedConnectivityCallback callback) {
+                checkPermissions();
+                mHandler.post(() -> unregisterCallback(callback));
+            }
+
+            @Override
+            public void connectTetherNetwork(TetherNetwork network) {
+                checkPermissions();
+                mHandler.post(() -> onConnectTetherNetwork(network));
+            }
+
+            @Override
+            public void disconnectTetherNetwork() {
+                checkPermissions();
+                mHandler.post(() -> onDisconnectTetherNetwork());
+            }
+
+            @Override
+            public void connectKnownNetwork(KnownNetwork network) {
+                checkPermissions();
+                mHandler.post(() -> onConnectKnownNetwork(network));
+            }
+
+            @Override
+            public void forgetKnownNetwork(KnownNetwork network) {
+                checkPermissions();
+                mHandler.post(() -> onForgetKnownNetwork(network));
+            }
+
+            @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+                    android.Manifest.permission.NETWORK_SETUP_WIZARD})
+            private void checkPermissions() {
+                if (checkCallingPermission(NETWORK_SETTINGS) != PackageManager.PERMISSION_GRANTED
+                        && checkCallingPermission(NETWORK_SETUP_WIZARD)
+                                != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Calling process must have NETWORK_SETTINGS or"
+                            + " NETWORK_SETUP_WIZARD permission");
+                }
+            }
+        };
+    }
+
+    private void registerCallback(ISharedConnectivityCallback callback) {
+        // Listener gets triggered on first register using cashed data
+        if (!notifyTetherNetworkUpdate(callback) || !notifyKnownNetworkUpdate(callback)) {
+            if (DEBUG) Log.w(TAG, "Failed to notify client");
+            return;
+        }
+
+        DeathRecipient deathRecipient = new DeathRecipient(callback);
+        try {
+            callback.asBinder().linkToDeath(deathRecipient, 0);
+            mCallbacks.add(callback);
+            mDeathRecipientMap.put(callback, deathRecipient);
+        } catch (RemoteException e) {
+            if (DEBUG) Log.w(TAG, "Exception in registerCallback", e);
+        }
+    }
+
+    private void unregisterCallback(ISharedConnectivityCallback callback) {
+        DeathRecipient deathRecipient = mDeathRecipientMap.get(callback);
+        if (deathRecipient != null) {
+            callback.asBinder().unlinkToDeath(deathRecipient, 0);
+            mDeathRecipientMap.remove(callback);
+        }
+        mCallbacks.remove(callback);
+    }
+
+    private boolean notifyTetherNetworkUpdate(ISharedConnectivityCallback callback) {
+        try {
+            callback.onTetherNetworksUpdated(mTetherNetworks);
+        } catch (RemoteException e) {
+            if (DEBUG) Log.w(TAG, "Exception in notifyTetherNetworkUpdate", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean notifyKnownNetworkUpdate(ISharedConnectivityCallback callback) {
+        try {
+            callback.onKnownNetworksUpdated(mKnownNetworks);
+        } catch (RemoteException e) {
+            if (DEBUG) Log.w(TAG, "Exception in notifyKnownNetworkUpdate", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean notifySettingsStateUpdate(ISharedConnectivityCallback callback) {
+        try {
+            callback.onSharedConnectivitySettingsChanged(mSettingsState);
+        } catch (RemoteException e) {
+            if (DEBUG) Log.w(TAG, "Exception in notifySettingsStateUpdate", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Implementing application should call this method to provide an up-to-date list of Tether
+     * Networks to be displayed to the user.
+     *
+     * This method updates the cached list and notifies all registered callbacks. Any callbacks that
+     * are inaccessible will be unregistered.
+     *
+     * @param networks The updated list of {@link TetherNetwork} objects.
+     */
+    public final void setTetherNetworks(@NonNull List<TetherNetwork> networks) {
+        mTetherNetworks = networks;
+
+        for (ISharedConnectivityCallback callback:mCallbacks) {
+            notifyTetherNetworkUpdate(callback);
+        }
+    }
+
+    /**
+     * Implementing application should call this method to provide an up-to-date list of Known
+     * Networks to be displayed to the user.
+     *
+     * This method updates the cached list and notifies all registered callbacks. Any callbacks that
+     * are inaccessible will be unregistered.
+     *
+     * @param networks The updated list of {@link KnownNetwork} objects.
+     */
+    public final void setKnownNetworks(@NonNull List<KnownNetwork> networks) {
+        mKnownNetworks = networks;
+
+        for (ISharedConnectivityCallback callback:mCallbacks) {
+            notifyKnownNetworkUpdate(callback);
+        }
+    }
+
+    /**
+     * Implementing application should call this method to provide an up-to-date state of Shared
+     * connectivity settings state.
+     *
+     * This method updates the cached state and notifies all registered callbacks. Any callbacks
+     * that are inaccessible will be unregistered.
+     *
+     * @param settingsState The updated state {@link SharedConnectivitySettingsState}
+     *                 objects.
+     */
+    public final void setSettingsState(@NonNull SharedConnectivitySettingsState settingsState) {
+        mSettingsState = settingsState;
+
+        for (ISharedConnectivityCallback callback:mCallbacks) {
+            notifySettingsStateUpdate(callback);
+        }
+    }
+
+    /**
+     * Implementing application should implement this method.
+     *
+     * Implementation should initiate a connection to the Tether Network indicated.
+     *
+     * @param network Object identifying the Tether Network the user has requested a connection to.
+     */
+    public abstract void onConnectTetherNetwork(@NonNull TetherNetwork network);
+
+    /**
+     * Implementing application should implement this method.
+     *
+     * Implementation should initiate a disconnection from the active Tether Network.
+     */
+    public abstract void onDisconnectTetherNetwork();
+
+    /**
+     * Implementing application should implement this method.
+     *
+     * Implementation should initiate a connection to the Known Network indicated.
+     *
+     * @param network Object identifying the Known Network the user has requested a connection to.
+     */
+    public abstract void onConnectKnownNetwork(@NonNull KnownNetwork network);
+
+    /**
+     * Implementing application should implement this method.
+     *
+     * Implementation should remove the Known Network indicated from the synced list of networks.
+     *
+     * @param network Object identifying the Known Network the user has requested to forget.
+     */
+    public abstract void onForgetKnownNetwork(@NonNull KnownNetwork network);
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
new file mode 100644
index 0000000..f8f0700
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_LAPTOP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.app.sharedconnectivity.DeviceInfo}.
+ */
+@SmallTest
+public class DeviceInfoTest {
+
+    private static final int DEVICE_TYPE = DEVICE_TYPE_PHONE;
+    private static final String DEVICE_NAME = "TEST_NAME";
+    private static final String DEVICE_MODEL = "TEST_MODEL";
+    private static final int BATTERY_PERCENTAGE = 50;
+    private static final int CONNECTION_STRENGTH = 2;
+
+    private static final int DEVICE_TYPE_1 = DEVICE_TYPE_LAPTOP;
+    private static final String DEVICE_NAME_1 = "TEST_NAME1";
+    private static final String DEVICE_MODEL_1 = "TEST_MODEL1";
+    private static final int BATTERY_PERCENTAGE_1 = 30;
+    private static final int CONNECTION_STRENGTH_1 = 1;
+
+    /**
+     * Verifies parcel serialization/deserialization.
+     */
+    @Test
+    public void testParcelOperation() {
+        DeviceInfo info = buildDeviceInfoBuilder().build();
+
+        Parcel parcelW = Parcel.obtain();
+        info.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(info, fromParcel);
+        assertEquals(info.hashCode(), fromParcel.hashCode());
+    }
+
+    /**
+     * Verifies the Equals operation
+     */
+    @Test
+    public void testEqualsOperation() {
+        DeviceInfo info1 = buildDeviceInfoBuilder().build();
+        DeviceInfo info2 = buildDeviceInfoBuilder().build();
+        assertEquals(info1, info2);
+
+        DeviceInfo.Builder builder = buildDeviceInfoBuilder().setDeviceType(DEVICE_TYPE_1);
+        assertNotEquals(info1, builder.build());
+
+        builder = buildDeviceInfoBuilder().setDeviceName(DEVICE_NAME_1);
+        assertNotEquals(info1, builder.build());
+
+        builder = buildDeviceInfoBuilder().setModelName(DEVICE_MODEL_1);
+        assertNotEquals(info1, builder.build());
+
+        builder = buildDeviceInfoBuilder()
+                .setBatteryPercentage(BATTERY_PERCENTAGE_1);
+        assertNotEquals(info1, builder.build());
+
+        builder = buildDeviceInfoBuilder()
+                .setConnectionStrength(CONNECTION_STRENGTH_1);
+        assertNotEquals(info1, builder.build());
+    }
+
+    /**
+     * Verifies the get methods return the expected data.
+     */
+    @Test
+    public void testGetMethods() {
+        DeviceInfo info = buildDeviceInfoBuilder().build();
+        assertEquals(info.getDeviceType(), DEVICE_TYPE);
+        assertEquals(info.getDeviceName(), DEVICE_NAME);
+        assertEquals(info.getModelName(), DEVICE_MODEL);
+        assertEquals(info.getBatteryPercentage(), BATTERY_PERCENTAGE);
+        assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH);
+        assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH);
+    }
+
+    private DeviceInfo.Builder buildDeviceInfoBuilder() {
+        return new DeviceInfo.Builder().setDeviceType(DEVICE_TYPE).setDeviceName(DEVICE_NAME)
+                .setModelName(DEVICE_MODEL).setBatteryPercentage(BATTERY_PERCENTAGE)
+                .setConnectionStrength(CONNECTION_STRENGTH);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
new file mode 100644
index 0000000..266afcc
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET;
+import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_CLOUD_SELF;
+import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.app.sharedconnectivity.KnownNetwork}.
+ */
+@SmallTest
+public class KnownNetworkTest {
+
+    private static final int NETWORK_SOURCE = NETWORK_SOURCE_NEARBY_SELF;
+    private static final String SSID = "TEST_SSID";
+    private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP};
+    private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
+            .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+            .setConnectionStrength(2).setBatteryPercentage(50).build();
+    private static final int NETWORK_SOURCE_1 = NETWORK_SOURCE_CLOUD_SELF;
+    private static final String SSID_1 = "TEST_SSID1";
+    private static final int[] SECURITY_TYPES_1 = {SECURITY_TYPE_PSK};
+    private static final DeviceInfo DEVICE_INFO_1 = new DeviceInfo.Builder()
+            .setDeviceType(DEVICE_TYPE_PHONE).setDeviceName("TEST_NAME_1")
+            .setModelName("TEST_MODEL_1").setConnectionStrength(3).setBatteryPercentage(33).build();
+
+    /**
+     * Verifies parcel serialization/deserialization.
+     */
+    @Test
+    public void testParcelOperation() {
+        KnownNetwork network = buildKnownNetworkBuilder().build();
+
+        Parcel parcelW = Parcel.obtain();
+        network.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        KnownNetwork fromParcel = KnownNetwork.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(network, fromParcel);
+        assertEquals(network.hashCode(), fromParcel.hashCode());
+    }
+
+    /**
+     * Verifies the Equals operation
+     */
+    @Test
+    public void testEqualsOperation() {
+        KnownNetwork network1 = buildKnownNetworkBuilder().build();
+        KnownNetwork network2 = buildKnownNetworkBuilder().build();
+        assertEquals(network1, network2);
+
+        KnownNetwork.Builder builder = buildKnownNetworkBuilder()
+                .setNetworkSource(NETWORK_SOURCE_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildKnownNetworkBuilder().setSsid(SSID_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildKnownNetworkBuilder().setSecurityTypes(SECURITY_TYPES_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildKnownNetworkBuilder().setDeviceInfo(DEVICE_INFO_1);
+        assertNotEquals(network1, builder.build());
+    }
+
+    /**
+     * Verifies the get methods return the expected data.
+     */
+    @Test
+    public void testGetMethods() {
+        KnownNetwork network = buildKnownNetworkBuilder().build();
+        assertEquals(network.getNetworkSource(), NETWORK_SOURCE);
+        assertEquals(network.getSsid(), SSID);
+        assertArrayEquals(network.getSecurityTypes(), SECURITY_TYPES);
+        assertEquals(network.getDeviceInfo(), DEVICE_INFO);
+    }
+
+    private KnownNetwork.Builder buildKnownNetworkBuilder() {
+        return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
+                .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
new file mode 100644
index 0000000..9aeccac
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET;
+import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
+import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for {@link SharedConnectivityManager}.
+ */
+@SmallTest
+public class SharedConnectivityManagerTest {
+    private static final long DEVICE_ID = 11L;
+    private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
+            .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+            .setConnectionStrength(2).setBatteryPercentage(50).build();
+    private static final int NETWORK_TYPE = NETWORK_TYPE_CELLULAR;
+    private static final String NETWORK_NAME = "TEST_NETWORK";
+    private static final String HOTSPOT_SSID = "TEST_SSID";
+    private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
+
+    private static final int NETWORK_SOURCE = NETWORK_SOURCE_NEARBY_SELF;
+    private static final String SSID = "TEST_SSID";
+    private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP};
+
+    private static final int SERVICE_PACKAGE_ID = 1;
+    private static final int SERVICE_CLASS_ID = 2;
+
+    private static final String SERVICE_PACKAGE_NAME = "TEST_PACKAGE";
+    private static final String SERVICE_CLASS_NAME = "TEST_CLASS";
+    private static final String PACKAGE_NAME = "TEST_PACKAGE";
+
+    @Mock Context mContext;
+    @Mock
+    ISharedConnectivityService mService;
+    @Mock Executor mExecutor;
+    @Mock
+    SharedConnectivityClientCallback mClientCallback;
+    @Mock Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        setResources(mContext);
+    }
+
+    /**
+     * Verifies constructor is binding to service.
+     */
+    @Test
+    public void testBindingToService() {
+        new SharedConnectivityManager(mContext);
+        verify(mContext).bindService(any(), any(), anyInt());
+    }
+
+    /**
+     * Verifies callback is registered in the service only once and only when service is not null.
+     */
+    @Test
+    public void testRegisterCallback() throws Exception {
+        SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+        manager.setService(null);
+        assertFalse(manager.registerCallback(mExecutor, mClientCallback));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        assertTrue(manager.registerCallback(mExecutor, mClientCallback));
+        verify(mService).registerCallback(any());
+
+        // Registering the same callback twice should fail.
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.registerCallback(mExecutor, mClientCallback);
+        assertFalse(manager.registerCallback(mExecutor, mClientCallback));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).registerCallback(any());
+        assertFalse(manager.registerCallback(mExecutor, mClientCallback));
+    }
+
+    /**
+     * Verifies callback is unregistered from the service if it was registered before and only when
+     * service is not null.
+     */
+    @Test
+    public void testUnregisterCallback() throws Exception {
+        SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+        manager.setService(null);
+        assertFalse(manager.unregisterCallback(mClientCallback));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.registerCallback(mExecutor, mClientCallback);
+        assertTrue(manager.unregisterCallback(mClientCallback));
+        verify(mService).unregisterCallback(any());
+
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.registerCallback(mExecutor, mClientCallback);
+        manager.unregisterCallback(mClientCallback);
+        assertFalse(manager.unregisterCallback(mClientCallback));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        doThrow(new RemoteException()).when(mService).unregisterCallback(any());
+        assertFalse(manager.unregisterCallback(mClientCallback));
+    }
+
+    /**
+     * Verifies service is called when not null and exceptions are handles when calling
+     * connectTetherNetwork.
+     */
+    @Test
+    public void testConnectTetherNetwork() throws RemoteException {
+        TetherNetwork network = buildTetherNetwork();
+
+        SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+        manager.setService(null);
+        assertFalse(manager.connectTetherNetwork(network));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.connectTetherNetwork(network);
+        verify(mService).connectTetherNetwork(network);
+
+        doThrow(new RemoteException()).when(mService).connectTetherNetwork(network);
+        assertFalse(manager.connectTetherNetwork(network));
+    }
+
+    /**
+     * Verifies service is called when not null and exceptions are handles when calling
+     * disconnectTetherNetwork.
+     */
+    @Test
+    public void testDisconnectTetherNetwork() throws RemoteException {
+        SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+        manager.setService(null);
+        assertFalse(manager.disconnectTetherNetwork());
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.disconnectTetherNetwork();
+        verify(mService).disconnectTetherNetwork();
+
+        doThrow(new RemoteException()).when(mService).disconnectTetherNetwork();
+        assertFalse(manager.disconnectTetherNetwork());
+    }
+
+    /**
+     * Verifies service is called when not null and exceptions are handles when calling
+     * connectKnownNetwork.
+     */
+    @Test
+    public void testConnectKnownNetwork() throws RemoteException {
+        KnownNetwork network = buildKnownNetwork();
+
+        SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+        manager.setService(null);
+        assertFalse(manager.connectKnownNetwork(network));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.connectKnownNetwork(network);
+        verify(mService).connectKnownNetwork(network);
+
+        doThrow(new RemoteException()).when(mService).connectKnownNetwork(network);
+        assertFalse(manager.connectKnownNetwork(network));
+    }
+
+    /**
+     * Verifies service is called when not null and exceptions are handles when calling
+     * forgetKnownNetwork.
+     */
+    @Test
+    public void testForgetKnownNetwork() throws RemoteException {
+        KnownNetwork network = buildKnownNetwork();
+
+        SharedConnectivityManager manager = new SharedConnectivityManager(mContext);
+        manager.setService(null);
+        assertFalse(manager.forgetKnownNetwork(network));
+
+        manager = new SharedConnectivityManager(mContext);
+        manager.setService(mService);
+        manager.forgetKnownNetwork(network);
+        verify(mService).forgetKnownNetwork(network);
+
+        doThrow(new RemoteException()).when(mService).forgetKnownNetwork(network);
+        assertFalse(manager.forgetKnownNetwork(network));
+    }
+
+    private void setResources(@Mock Context context) {
+        when(context.getResources()).thenReturn(mResources);
+        when(context.getPackageName()).thenReturn(PACKAGE_NAME);
+        when(mResources.getIdentifier(anyString(), anyString(), anyString()))
+                .thenReturn(SERVICE_PACKAGE_ID, SERVICE_CLASS_ID);
+        when(mResources.getString(SERVICE_PACKAGE_ID)).thenReturn(SERVICE_PACKAGE_NAME);
+        when(mResources.getString(SERVICE_CLASS_ID)).thenReturn(SERVICE_CLASS_NAME);
+    }
+
+    private TetherNetwork buildTetherNetwork() {
+        return new TetherNetwork.Builder()
+                .setDeviceId(DEVICE_ID)
+                .setDeviceInfo(DEVICE_INFO)
+                .setNetworkType(NETWORK_TYPE)
+                .setNetworkName(NETWORK_NAME)
+                .setHotspotSsid(HOTSPOT_SSID)
+                .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES)
+                .build();
+    }
+
+    private KnownNetwork buildKnownNetwork() {
+        return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID)
+                .setSecurityTypes(SECURITY_TYPES).build();
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
new file mode 100644
index 0000000..3137c72
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState}.
+ */
+@SmallTest
+public class SharedConnectivitySettingsStateTest {
+    private static final boolean INSTANT_TETHER_STATE = true;
+
+    private static final boolean INSTANT_TETHER_STATE_1 = false;
+    /**
+     * Verifies parcel serialization/deserialization.
+     */
+    @Test
+    public void testParcelOperation() {
+        SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
+
+        Parcel parcelW = Parcel.obtain();
+        state.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        SharedConnectivitySettingsState fromParcel =
+                SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(state, fromParcel);
+        assertEquals(state.hashCode(), fromParcel.hashCode());
+    }
+
+    /**
+     * Verifies the Equals operation
+     */
+    @Test
+    public void testEqualsOperation() {
+        SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build();
+        SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build();
+        assertEquals(state1, state2);
+
+        SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder()
+                .setInstantTetherEnabled(INSTANT_TETHER_STATE_1);
+        assertNotEquals(state1, builder.build());
+    }
+
+    /**
+     * Verifies the get methods return the expected data.
+     */
+    @Test
+    public void testGetMethods() {
+        SharedConnectivitySettingsState state = buildSettingsStateBuilder().build();
+        assertEquals(state.isInstantTetherEnabled(), INSTANT_TETHER_STATE);
+    }
+
+    private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() {
+        return new SharedConnectivitySettingsState.Builder()
+                .setInstantTetherEnabled(INSTANT_TETHER_STATE);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
new file mode 100644
index 0000000..b01aec4
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.app;
+
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE;
+import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET;
+import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR;
+import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_WIFI;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetwork}.
+ */
+@SmallTest
+public class TetherNetworkTest {
+    private static final long DEVICE_ID = 11L;
+    private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder()
+            .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+            .setConnectionStrength(2).setBatteryPercentage(50).build();
+    private static final int NETWORK_TYPE = NETWORK_TYPE_CELLULAR;
+    private static final String NETWORK_NAME = "TEST_NETWORK";
+    private static final String HOTSPOT_SSID = "TEST_SSID";
+    private static final String HOTSPOT_BSSID = "TEST _BSSID";
+    private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP};
+
+    private static final long DEVICE_ID_1 = 111L;
+    private static final DeviceInfo DEVICE_INFO_1 = new DeviceInfo.Builder()
+            .setDeviceType(DEVICE_TYPE_PHONE).setDeviceName("TEST_NAME").setModelName("TEST_MODEL")
+            .setConnectionStrength(2).setBatteryPercentage(50).build();
+    private static final int NETWORK_TYPE_1 = NETWORK_TYPE_WIFI;
+    private static final String NETWORK_NAME_1 = "TEST_NETWORK1";
+    private static final String HOTSPOT_SSID_1 = "TEST_SSID1";
+    private static final String HOTSPOT_BSSID_1 = "TEST _BSSID1";
+    private static final int[] HOTSPOT_SECURITY_TYPES_1 = {SECURITY_TYPE_PSK, SECURITY_TYPE_EAP};
+
+    /**
+     * Verifies parcel serialization/deserialization.
+     */
+    @Test
+    public void testParcelOperation() {
+        TetherNetwork network = buildTetherNetworkBuilder().build();
+
+        Parcel parcelW = Parcel.obtain();
+        network.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        TetherNetwork fromParcel = TetherNetwork.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(network, fromParcel);
+        assertEquals(network.hashCode(), fromParcel.hashCode());
+    }
+
+    /**
+     * Verifies the Equals operation
+     */
+    @Test
+    public void testEqualsOperation() {
+        TetherNetwork network1 = buildTetherNetworkBuilder().build();
+        TetherNetwork network2 = buildTetherNetworkBuilder().build();
+        assertEquals(network1, network2);
+
+        TetherNetwork.Builder builder = buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildTetherNetworkBuilder().setDeviceInfo(DEVICE_INFO_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildTetherNetworkBuilder().setNetworkType(NETWORK_TYPE_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildTetherNetworkBuilder().setNetworkName(NETWORK_NAME_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildTetherNetworkBuilder().setHotspotSsid(HOTSPOT_SSID_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildTetherNetworkBuilder().setHotspotBssid(HOTSPOT_BSSID_1);
+        assertNotEquals(network1, builder.build());
+
+        builder = buildTetherNetworkBuilder().setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES_1);
+        assertNotEquals(network1, builder.build());
+    }
+
+    /**
+     * Verifies the get methods return the expected data.
+     */
+    @Test
+    public void testGetMethods() {
+        TetherNetwork network = buildTetherNetworkBuilder().build();
+        assertEquals(network.getDeviceId(), DEVICE_ID);
+        assertEquals(network.getDeviceInfo(), DEVICE_INFO);
+        assertEquals(network.getNetworkType(), NETWORK_TYPE);
+        assertEquals(network.getNetworkName(), NETWORK_NAME);
+        assertEquals(network.getHotspotSsid(), HOTSPOT_SSID);
+        assertEquals(network.getHotspotBssid(), HOTSPOT_BSSID);
+        assertArrayEquals(network.getHotspotSecurityTypes(), HOTSPOT_SECURITY_TYPES);
+    }
+
+    private TetherNetwork.Builder buildTetherNetworkBuilder() {
+        return new TetherNetwork.Builder()
+                .setDeviceId(DEVICE_ID)
+                .setDeviceInfo(DEVICE_INFO)
+                .setNetworkType(NETWORK_TYPE)
+                .setNetworkName(NETWORK_NAME)
+                .setHotspotSsid(HOTSPOT_SSID)
+                .setHotspotBssid(HOTSPOT_BSSID)
+                .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
new file mode 100644
index 0000000..e15be8b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.sharedconnectivity.service;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Intent;
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.TetherNetwork;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}.
+ */
+@SmallTest
+public class SharedConnectivityServiceTest {
+
+    /**
+     * Verifies service returns
+     */
+    @Test
+    public void testOnBind() {
+        SharedConnectivityService service = createService();
+        assertNotNull(service.onBind(new Intent()));
+    }
+
+    @Test
+    public void testCallbacks() {
+        SharedConnectivityService service = createService();
+        ISharedConnectivityService.Stub binder =
+                (ISharedConnectivityService.Stub) service.onBind(new Intent());
+    }
+
+    private SharedConnectivityService createService() {
+        return new SharedConnectivityService(new Handler(new TestLooper().getLooper())) {
+            @Override
+            public void onConnectTetherNetwork(TetherNetwork network) {}
+
+            @Override
+            public void onDisconnectTetherNetwork() {}
+
+            @Override
+            public void onConnectKnownNetwork(KnownNetwork network) {}
+
+            @Override
+            public void onForgetKnownNetwork(KnownNetwork network) {}
+        };
+    }
+}