Merge changes I771230c1,I460dfc11 into main

* changes:
  Support media on splitshade with constraints
  Fix NSSL flicker when going to/from OCCLUDED
diff --git a/Android.bp b/Android.bp
index bb93048..13b1703 100644
--- a/Android.bp
+++ b/Android.bp
@@ -174,6 +174,9 @@
         // and remove this line.
         "//frameworks/base/tools/hoststubgen:__subpackages__",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // AIDL files under these paths are mixture of public and private ones.
@@ -264,6 +267,9 @@
     ],
     sdk_version: "core_platform",
     installable: false,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -432,6 +438,9 @@
     ],
     sdk_version: "core_platform",
     installable: false,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -475,6 +484,9 @@
     ],
     compile_dex: false,
     headers_only: true,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -502,6 +514,9 @@
             "-Xep:AndroidFrameworkUid:ERROR",
         ],
     },
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -516,6 +531,7 @@
     },
     lint: {
         enabled: false,
+        baseline_filename: "lint-baseline.xml",
     },
 }
 
@@ -540,6 +556,9 @@
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -555,6 +574,9 @@
         "calendar-provider-compat-config",
         "contacts-provider-platform-compat-config",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 platform_compat_config {
@@ -609,6 +631,9 @@
         "rappor",
     ],
     dxflags: ["--core-library"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // utility classes statically linked into framework-wifi and dynamically linked
@@ -634,6 +659,9 @@
         "//frameworks/base/services/net",
         "//packages/modules/Wifi/framework",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 filegroup {
diff --git a/core/api/current.txt b/core/api/current.txt
index 43ff0c9..df073dd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12932,6 +12932,7 @@
     field public static final String FEATURE_MIDI = "android.software.midi";
     field public static final String FEATURE_NFC = "android.hardware.nfc";
     field public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+    field @FlaggedApi("android.nfc.enable_nfc_charging") public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
     field public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
     field public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
     field public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
@@ -28813,6 +28814,7 @@
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
     method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+    method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
     method public boolean isEnabled();
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
@@ -28820,6 +28822,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
     method public boolean isSecureNfcEnabled();
     method public boolean isSecureNfcSupported();
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28905,6 +28908,20 @@
     ctor public TagLostException(String);
   }
 
+  @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
+    ctor public WlcLDeviceInfo(double, double, double, int);
+    method public int describeContents();
+    method public double getBatteryLevel();
+    method public double getProductId();
+    method public int getState();
+    method public double getTemperature();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONNECTED_CHARGING = 2; // 0x2
+    field public static final int CONNECTED_DISCHARGING = 3; // 0x3
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
+    field public static final int DISCONNECTED = 1; // 0x1
+  }
+
 }
 
 package android.nfc.cardemulation {
@@ -45682,6 +45699,7 @@
     field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED";
     field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED";
     field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
+    field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
     field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
     field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE";
     field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 51e61e6..0d4169f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3357,7 +3357,7 @@
 
   @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
     method public int describeContents();
-    method @StringRes public int getDisplayNameStringRes();
+    method @NonNull public String getName();
     method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
@@ -3367,7 +3367,7 @@
     ctor public VirtualCameraConfig.Builder();
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
-    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+    method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
     method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
   }
 
@@ -9884,17 +9884,20 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
     method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
     field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
     field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
@@ -9909,6 +9912,10 @@
     method public boolean onUnlockAttempted(android.nfc.Tag);
   }
 
+  @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+    method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+  }
+
 }
 
 package android.nfc.cardemulation {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6ddb36a..6df0f6b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -407,7 +407,7 @@
 
     private int mLastSessionId;
     // Holds the value of the last reported device ID value from the server for the top activity.
-    int mLastReportedDeviceId;
+    int mLastReportedDeviceId = Context.DEVICE_ID_DEFAULT;
     final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
     @UnsupportedAppUsage
     final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -4856,10 +4856,13 @@
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
             if (!service.isUiContext()) { // WindowProviderService is a UI Context.
-                VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
-                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
-                        || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT) {
                     service.updateDeviceId(mLastReportedDeviceId);
+                } else {
+                    VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+                    if (vdm != null && vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+                        service.updateDeviceId(mLastReportedDeviceId);
+                    }
                 }
             }
             service.onCreate();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b7db5f5..c3adbc3 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -222,7 +222,7 @@
     boolean removeAutomaticZenRule(String id, boolean fromUser);
     boolean removeAutomaticZenRules(String packageName, boolean fromUser);
     int getRuleInstanceCount(in ComponentName owner);
-    void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
+    void setAutomaticZenRuleState(String id, in Condition condition);
 
     byte[] getBackupPayload(int user);
     void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f76a45b..0b6e24c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1391,20 +1391,9 @@
      * @param condition The new state of this rule
      */
     public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
-        if (Flags.modesApi()) {
-            setAutomaticZenRuleState(id, condition,
-                    /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
-        } else {
-            setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
-        }
-    }
-
-    /** @hide */
-    public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
-            boolean fromUser) {
         INotificationManager service = getService();
         try {
-            service.setAutomaticZenRuleState(id, condition, fromUser);
+            service.setAutomaticZenRuleState(id, condition);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 7418c06..9f97f6f 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,7 +16,7 @@
 
 package android.app.servertransaction;
 
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import static java.util.Objects.requireNonNull;
 
@@ -67,7 +67,7 @@
      * window configuration.
      */
     public void onDisplayChanged(int displayId) {
-        if (!isSyncWindowConfigUpdateFlagEnabled()) {
+        if (!isBundleClientTransactionFlagEnabled()) {
             return;
         }
         if (ActivityThread.isSystem()) {
@@ -77,9 +77,9 @@
         mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
     }
 
-    /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */
-    public boolean isSyncWindowConfigUpdateFlagEnabled() {
+    /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
+    public boolean isBundleClientTransactionFlagEnabled() {
         // Can't read flag from isolated process.
-        return !Process.isIsolated() && syncWindowConfigUpdateFlag();
+        return !Process.isIsolated() && bundleClientTransactionFlag();
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 9f5e0dc..ee48e43 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -32,7 +32,7 @@
 import static android.app.servertransaction.TransactionExecutorHelper.tId;
 import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
 
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import android.annotation.NonNull;
 import android.app.ActivityThread.ActivityClientRecord;
@@ -183,9 +183,9 @@
         }
 
         // Can't read flag from isolated process.
-        final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
-                && syncWindowConfigUpdateFlag();
-        final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+        final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated()
+                && bundleClientTransactionFlag();
+        final Context configUpdatedContext = isBundleClientTransactionFlagEnabled
                 ? item.getContextToUpdate(mTransactionHandler)
                 : null;
         final Configuration preExecutedConfig = configUpdatedContext != null
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index a939251..59fe9a1 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -20,11 +20,9 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.StringRes;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.companion.virtual.flags.Flags;
-import android.content.res.Resources;
 import android.graphics.ImageFormat;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -45,16 +43,16 @@
 @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
 public final class VirtualCameraConfig implements Parcelable {
 
-    private final @StringRes int mNameStringRes;
+    private final String mName;
     private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
     private final IVirtualCameraCallback mCallback;
 
     private VirtualCameraConfig(
-            int displayNameStringRes,
+            @NonNull String name,
             @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
             @NonNull Executor executor,
             @NonNull VirtualCameraCallback callback) {
-        mNameStringRes = displayNameStringRes;
+        mName = requireNonNull(name, "Missing name");
         mStreamConfigurations =
                 Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
         if (mStreamConfigurations.isEmpty()) {
@@ -68,7 +66,7 @@
     }
 
     private VirtualCameraConfig(@NonNull Parcel in) {
-        mNameStringRes = in.readInt();
+        mName = in.readString8();
         mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
         mStreamConfigurations =
                 Set.of(
@@ -84,18 +82,18 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mNameStringRes);
+        dest.writeString8(mName);
         dest.writeStrongInterface(mCallback);
         dest.writeParcelableArray(
                 mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
     }
 
     /**
-     * @return The display name of this VirtualCamera
+     * @return The name of this VirtualCamera
      */
-    @StringRes
-    public int getDisplayNameStringRes() {
-        return mNameStringRes;
+    @NonNull
+    public String getName() {
+        return mName;
     }
 
     /**
@@ -126,30 +124,22 @@
      * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
      * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
      *     VirtualCameraCallback)}
-     * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
+     * <li>A camera name must be set with {@link #setName(String)}
      */
     @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
     public static final class Builder {
 
-        private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+        private String mName;
         private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
         private Executor mCallbackExecutor;
         private VirtualCameraCallback mCallback;
 
         /**
-         * Set the visible name of this camera for the user.
-         *
-         * <p>Sets the resource to a string representing a user readable name for this virtual
-         * camera.
-         *
-         * @throws IllegalArgumentException if an invalid resource id is passed.
+         * Set the name of the virtual camera instance.
          */
         @NonNull
-        public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
-            if (displayNameStringRes <= 0) {
-                throw new IllegalArgumentException("Invalid resource passed for display name");
-            }
-            mDisplayNameStringRes = displayNameStringRes;
+        public Builder setName(@NonNull String name) {
+            mName = requireNonNull(name, "Display name cannot be null");
             return this;
         }
 
@@ -203,7 +193,7 @@
         @NonNull
         public VirtualCameraConfig build() {
             return new VirtualCameraConfig(
-                    mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
+                    mName, mStreamConfigurations, mCallbackExecutor, mCallback);
         }
     }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a863870..8151a91 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
@@ -55,7 +57,6 @@
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -91,6 +92,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageInfoCommonUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DataClass;
 
@@ -817,6 +822,8 @@
             GET_DISABLED_UNTIL_USED_COMPONENTS,
             GET_UNINSTALLED_PACKAGES,
             MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
+            MATCH_DIRECT_BOOT_AWARE,
+            MATCH_DIRECT_BOOT_UNAWARE,
             GET_ATTRIBUTIONS_LONG,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -2518,6 +2525,7 @@
             USER_MIN_ASPECT_RATIO_16_9,
             USER_MIN_ASPECT_RATIO_3_2,
             USER_MIN_ASPECT_RATIO_FULLSCREEN,
+            USER_MIN_ASPECT_RATIO_APP_DEFAULT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserMinAspectRatio {}
@@ -2571,6 +2579,16 @@
      */
     public static final int USER_MIN_ASPECT_RATIO_FULLSCREEN = 6;
 
+    /**
+     * Aspect ratio override code: user sets to app's default aspect ratio.
+     * This resets both the user-forced aspect ratio, and the device manufacturer
+     * per-app override {@link ActivityInfo#OVERRIDE_ANY_ORIENTATION_TO_USER}.
+     * It is different from {@link #USER_MIN_ASPECT_RATIO_UNSET} as the latter may
+     * apply the device manufacturer per-app orientation override if any,
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_APP_DEFAULT = 7;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
@@ -3295,6 +3313,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports NFC charging.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_NFC_CHARGING)
+    public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The Beam API is enabled on the device.
      */
     @SdkConstant(SdkConstantType.FEATURE)
@@ -3304,7 +3330,7 @@
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports any
      * one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION},
-     * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features.
+     * {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF}, or {@link #FEATURE_NFC_CHARGING} features.
      *
      * @hide
      */
@@ -8609,28 +8635,56 @@
     @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
             @NonNull PackageInfoFlags flags) {
-        long flagsBits = flags.getValue();
-        final PackageParser parser = new PackageParser();
-        parser.setCallback(new PackageParser.CallbackImpl(this));
         final File apkFile = new File(archiveFilePath);
-        try {
-            if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
-                // Caller expressed an explicit opinion about what encryption
-                // aware/unaware components they want to see, so fall through and
-                // give them what they want
-            } else {
-                // Caller expressed no opinion, so match everything
-                flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
-            }
 
-            PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
-            if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
-                PackageParser.collectCertificates(pkg, false /* skipVerify */);
-            }
-            return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
-                    FrameworkPackageUserState.DEFAULT);
-        } catch (PackageParser.PackageParserException e) {
-            Log.w(TAG, "Failure to parse package archive", e);
+        @PackageInfoFlagsBits long flagsBits = flags.getValue();
+        if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
+            // Caller expressed an explicit opinion about what encryption
+            // aware/unaware components they want to see, so fall through and
+            // give them what they want
+        } else {
+            // Caller expressed no opinion, so match everything
+            flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+        }
+
+        int parserFlags = 0;
+        if ((flagsBits & (GET_SIGNATURES | GET_SIGNING_CERTIFICATES)) != 0) {
+            parserFlags |= PARSE_COLLECT_CERTIFICATES;
+        }
+
+        final PackageParser2 parser2 = new PackageParser2(/*separateProcesses*/ null,
+                /*displayMetrics*/ null,/*cacher*/ null,
+                new PackageParser2.Callback() {
+                    @Override
+                    public boolean hasFeature(String feature) {
+                        return PackageManager.this.hasSystemFeature(feature);
+                    }
+
+                    @NonNull
+                    @Override
+                    public Set<String> getHiddenApiWhitelistedApps() {
+                        return Collections.emptySet();
+                    }
+
+                    @NonNull
+                    @Override
+                    public Set<String> getInstallConstraintsAllowlist() {
+                        return Collections.emptySet();
+                    }
+
+                    @Override
+                    public boolean isChangeEnabled(long changeId,
+                            @androidx.annotation.NonNull ApplicationInfo appInfo) {
+                        return false;
+                    }
+                });
+
+        try {
+            ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false);
+
+            return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId());
+        } catch (PackageParserException e) {
+            Log.w(TAG, "Failure to parse package archive apkFile= " +apkFile);
             return null;
         }
     }
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9a1796f..c7797c7 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -64,3 +64,10 @@
     bug: "296829976"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "allow_resolver_sheet_for_private_space"
+    namespace: "profile_experiences"
+    description: "Add support for Private Space in resolver sheet"
+    bug: "307515485"
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03db..60ad8e8 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@
     private static native long nativeCreate(String opPackageName);
     private static native boolean nativeGetSensorAtIndex(long nativeInstance,
             Sensor sensor, int index);
+    private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+            Sensor sensor, int index);
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
     private static native void nativeGetRuntimeSensors(
             long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@
         // initialize the sensor list
         for (int index = 0;; ++index) {
             Sensor sensor = new Sensor();
-            if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+            if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+                if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+            } else {
+                if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+            }
             mFullSensorsList.add(sensor);
             mHandleToSensor.put(sensor.getHandle(), sensor);
         }
-
     }
 
     /** @hide */
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889d..665d8d2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@
      * on a particular SessionConfiguration.</p>
      *
      * @return List of CameraCharacteristic keys containing characterisitics specific to a session
-     * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+     * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+     * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
      */
     @NonNull
     @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
     public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
         if (mAvailableSessionCharacteristicsKeys == null) {
-            mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+            mAvailableSessionCharacteristicsKeys =
+                    Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
         }
         return mAvailableSessionCharacteristicsKeys;
     }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212..64a62a9 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@
          */
         boolean blockScreenOn(Runnable unblocker);
 
+        /**
+         * Get the brightness levels used to determine automatic brightness based on lux levels.
+         * @param mode The auto-brightness mode
+         *             (AutomaticBrightnessController.AutomaticBrightnessMode)
+         * @return The brightness levels for the specified mode. The values are between
+         * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+         */
+        float[] getAutoBrightnessLevels(int mode);
+
+        /**
+         * Get the lux levels used to determine automatic brightness.
+         * @param mode The auto-brightness mode
+         *             (AutomaticBrightnessController.AutomaticBrightnessMode)
+         * @return The lux levels for the specified mode
+         */
+        float[] getAutoBrightnessLuxLevels(int mode);
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index f6beec1..967a0cc 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -30,8 +30,10 @@
 import android.nfc.INfcUnlockHandler;
 import android.nfc.ITagRemovedCallback;
 import android.nfc.INfcDta;
+import android.nfc.INfcWlcStateListener;
 import android.nfc.NfcAntennaInfo;
 import android.os.Bundle;
+import android.nfc.WlcLDeviceInfo;
 
 /**
  * @hide
@@ -86,4 +88,11 @@
     boolean enableReaderOption(boolean enable);
     boolean isObserveModeSupported();
     boolean setObserveMode(boolean enabled);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+    boolean enableWlc(boolean enable);
+    boolean isWlcEnabled();
+    void registerWlcStateListener(in INfcWlcStateListener listener);
+    void unregisterWlcStateListener(in INfcWlcStateListener listener);
+    WlcLDeviceInfo getWlcLDeviceInfo();
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java b/core/java/android/nfc/INfcWlcStateListener.aidl
similarity index 62%
rename from packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
rename to core/java/android/nfc/INfcWlcStateListener.aidl
index a979cf8..c2b7075 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
+++ b/core/java/android/nfc/INfcWlcStateListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 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.
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.packageinstaller.v2.model.installstagedata;
+package android.nfc;
 
-public class InstallStaging extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_STAGING;
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
+import android.nfc.WlcLDeviceInfo;
+/**
+ * @hide
+ */
+oneway interface INfcWlcStateListener {
+  /**
+   * Called whenever NFC WLC state changes
+   *
+   * @param wlcLDeviceInfo NFC wlc listener information
+   */
+  void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f407fb7..21e23ae 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -75,6 +75,7 @@
     static final String TAG = "NFC";
 
     private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+    private final NfcWlcStateListener mNfcWlcStateListener;
 
     /**
      * Intent to start an activity when a tag with NDEF payload is discovered.
@@ -440,6 +441,7 @@
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
     static boolean sHasCeFeature;
+    static boolean sHasNfcWlcFeature;
 
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -650,8 +652,9 @@
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
                     || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
+            sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
             /* is this device meant to have NFC */
-            if (!sHasNfcFeature && !sHasCeFeature) {
+            if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
                 Log.v(TAG, "this device does not have NFC support");
                 throw new UnsupportedOperationException();
             }
@@ -776,6 +779,7 @@
         mTagRemovedListener = null;
         mLock = new Object();
         mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+        mNfcWlcStateListener = new NfcWlcStateListener(getService());
     }
 
     /**
@@ -944,7 +948,8 @@
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
         }
-        return serviceState && (isTagReadingEnabled() || isCardEmulationEnabled());
+        return serviceState
+                && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
     }
 
     /**
@@ -2587,4 +2592,159 @@
             return false;
         }
     }
+
+    /**
+     * Sets NFC charging feature.
+     * <p>This API is for the Settings application.
+     * @return True if successful
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean enableWlc(boolean enable) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.enableWlc(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.enableWlc(enable);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks NFC charging feature is enabled.
+     *
+     * @return True if NFC charging is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public boolean isWlcEnabled() {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isWlcEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return false;
+            }
+            try {
+                return sService.isWlcEnabled();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * A listener to be invoked when NFC controller always on state changes.
+     * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+     * NfcAdapter#registerWlcStateListener} and disable it with {@link
+     * NfcAdapter#unregisterWlcStateListenerListener}.
+     * @see #registerWlcStateListener
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public interface WlcStateListener {
+        /**
+         * Called on NFC WLC state changes
+         */
+        void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+    }
+
+    /**
+     * Register a {@link WlcStateListener} to listen for NFC WLC state changes
+     * <p>The provided listener will be invoked by the given {@link Executor}.
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link WlcStateListener}
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public void registerWlcStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WlcStateListener listener) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        mNfcWlcStateListener.register(executor, listener);
+    }
+
+    /**
+     * Unregister the specified {@link WlcStateListener}
+     * <p>The same {@link WlcStateListener} object used when calling
+     * {@link #registerWlcStateListener(Executor, WlcStateListener)}
+     * must be used.
+     *
+     * <p>Listeners are automatically unregistered when application process goes away
+     *
+     * @param listener user implementation of the {@link WlcStateListener}a
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    public void unregisterWlcStateListener(
+            @NonNull WlcStateListener listener) {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        mNfcWlcStateListener.unregister(listener);
+    }
+
+    /**
+     * Returns information on the NFC charging listener device
+     *
+     * @return Information on the NFC charging listener device
+     * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+     * is unavailable
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+    @Nullable
+    public WlcLDeviceInfo getWlcLDeviceInfo() {
+        if (!sHasNfcWlcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.getWlcLDeviceInfo();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            // Try one more time
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+                return null;
+            }
+            try {
+                return sService.getWlcLDeviceInfo();
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover NFC Service.");
+            }
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/nfc/NfcWlcStateListener.java b/core/java/android/nfc/NfcWlcStateListener.java
new file mode 100644
index 0000000..8d79310
--- /dev/null
+++ b/core/java/android/nfc/NfcWlcStateListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.WlcStateListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
+    private static final String TAG = NfcWlcStateListener.class.getSimpleName();
+
+    private final INfcAdapter mAdapter;
+
+    private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
+
+    private WlcLDeviceInfo mCurrentState = null;
+    private boolean mIsRegistered = false;
+
+    public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Register a {@link WlcStateListener} with this
+     * {@link WlcStateListener}
+     *
+     * @param executor an {@link Executor} to execute given listener
+     * @param listener user implementation of the {@link WlcStateListener}
+     */
+    public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
+        synchronized (this) {
+            if (mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.put(listener, executor);
+
+            if (!mIsRegistered) {
+                try {
+                    mAdapter.registerWlcStateListener(this);
+                    mIsRegistered = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to register");
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregister the specified {@link WlcStateListener}
+     *
+     * @param listener user implementation of the {@link WlcStateListener}
+     */
+    public void unregister(@NonNull WlcStateListener listener) {
+        synchronized (this) {
+            if (!mListenerMap.containsKey(listener)) {
+                return;
+            }
+
+            mListenerMap.remove(listener);
+
+            if (mListenerMap.isEmpty() && mIsRegistered) {
+                try {
+                    mAdapter.unregisterWlcStateListener(this);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to unregister");
+                }
+                mIsRegistered = false;
+            }
+        }
+    }
+
+    private void sendCurrentState(@NonNull WlcStateListener listener) {
+        synchronized (this) {
+            Executor executor = mListenerMap.get(listener);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() -> listener.onWlcStateChanged(
+                        mCurrentState));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
+        synchronized (this) {
+            mCurrentState = wlcLDeviceInfo;
+
+            for (WlcStateListener cb : mListenerMap.keySet()) {
+                sendCurrentState(cb);
+            }
+        }
+    }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/core/java/android/nfc/WlcLDeviceInfo.aidl
similarity index 71%
copy from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
copy to core/java/android/nfc/WlcLDeviceInfo.aidl
index b8a9355..33143fe 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/core/java/android/nfc/WlcLDeviceInfo.aidl
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      https://www.apache.org/licenses/LICENSE-2.0
+ *      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,
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.packageinstaller.v2.ui;
+package android.nfc;
 
-public interface UninstallActionListener {
-
-    void onPositiveResponse(boolean keepData);
-
-    void onNegativeResponse();
-}
+parcelable WlcLDeviceInfo;
diff --git a/core/java/android/nfc/WlcLDeviceInfo.java b/core/java/android/nfc/WlcLDeviceInfo.java
new file mode 100644
index 0000000..016431e
--- /dev/null
+++ b/core/java/android/nfc/WlcLDeviceInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcLDeviceInfo implements Parcelable {
+    public static final int DISCONNECTED = 1;
+
+    public static final int CONNECTED_CHARGING = 2;
+
+    public static final int CONNECTED_DISCHARGING = 3;
+
+    private double mProductId;
+    private double mTemperature;
+    private double mBatteryLevel;
+    private int mState;
+
+    public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
+        this.mProductId = productId;
+        this.mTemperature = temperature;
+        this.mBatteryLevel = batteryLevel;
+        this.mState = state;
+    }
+
+    /**
+     * ProductId of the WLC listener device.
+     */
+    public double getProductId() {
+        return mProductId;
+    }
+
+    /**
+     * Temperature of the WLC listener device.
+     */
+    public double getTemperature() {
+        return mTemperature;
+    }
+
+    /**
+     * BatteryLevel of the WLC listener device.
+     */
+    public double getBatteryLevel() {
+        return mBatteryLevel;
+    }
+
+    /**
+     * State of the WLC listener device.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    private WlcLDeviceInfo(Parcel in) {
+        this.mProductId = in.readDouble();
+        this.mTemperature = in.readDouble();
+        this.mBatteryLevel = in.readDouble();
+        this.mState = in.readInt();
+    }
+
+    public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
+            new Parcelable.Creator<WlcLDeviceInfo>() {
+                @Override
+                public WlcLDeviceInfo createFromParcel(Parcel in) {
+                    return new WlcLDeviceInfo(in);
+                }
+
+                @Override
+                public WlcLDeviceInfo[] newArray(int size) {
+                    return new WlcLDeviceInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeDouble(mProductId);
+        dest.writeDouble(mTemperature);
+        dest.writeDouble(mBatteryLevel);
+        dest.writeDouble(mState);
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f9..41dee3a 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
 package android.nfc.cardemulation;
 
 import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@
         // Set uid
         mUid = si.applicationInfo.uid;
 
-        mCategoryOtherServiceEnabled = false;    // support other category
+        mCategoryOtherServiceEnabled = true;    // support other category
 
     }
 
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e0427..ce4f777 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,17 @@
     description: "Enable NFC Polling Loop Notifications ST shim"
     bug: "294217286"
 }
+
+flag {
+    name: "enable_tag_detection_broadcasts"
+    namespace: "nfc"
+    description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+    bug: "306203494"
+}
+
+flag {
+    name: "enable_nfc_charging"
+    namespace: "nfc"
+    description: "Flag for NFC charging changes"
+    bug: "292143899"
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523e..80ec458 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -757,7 +757,6 @@
                                                   @Nullable String invokeWith,
                                                   @Nullable String packageName,
                                                   @Nullable long[] disabledCompatChanges,
-                                                  boolean bindMountSyspropOverrides,
                                                   @Nullable String[] zygoteArgs) {
         // Webview zygote can't access app private data files, so doesn't need to know its data
         // info.
@@ -767,7 +766,7 @@
                     /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
                 disabledCompatChanges, /* pkgDataInfoMap */ null,
                 /* whitelistedDataInfoMap */ null, /* bindMountAppsData */ false,
-                /* bindMountAppStorageDirs */ false, bindMountSyspropOverrides, zygoteArgs);
+                /* bindMountAppStorageDirs */ false, /* bindMountSyspropOverrides */ false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 49ce40b..df503e8 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 /**
  * An exception specific to a service.
@@ -33,6 +34,7 @@
  * @hide
  */
 @SystemApi
+@RavenwoodKeepWholeClass
 public class ServiceSpecificException extends RuntimeException {
     public final int errorCode;
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 942ce971..54cc5f4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15021,6 +15021,16 @@
                 "foreground_service_starts_logging_enabled";
 
         /**
+         * Describes whether AM's AppProfiler should collect PSS even if RSS is the default. This
+         * can be set by a user in developer settings.
+         * Default: 0
+         * @hide
+         */
+        @Readable
+        public static final String FORCE_ENABLE_PSS_PROFILING =
+                "force_enable_pss_profiling";
+
+        /**
          * @hide
          * @see com.android.server.appbinding.AppBindingConstants
          */
@@ -19628,6 +19638,15 @@
              */
             public static final String WEAR_POWER_ANOMALY_SERVICE_ENABLED =
                     "wear_power_anomaly_service_enabled";
+
+            /**
+             * A boolean that tracks whether Wrist Detection Auto-Locking is enabled.
+             *
+             * @hide
+             */
+            @Readable
+            public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
+                    "wear_wrist_detection_auto_locking_enabled";
         }
     }
 
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3..30524a1 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@
     description: "Collect sepolicy hash from sysfs"
     bug: "308471499"
 }
+
+flag {
+    name: "frp_enforcement"
+    namespace: "android_hw_security"
+    description: "This flag controls whether PDB enforces FRP"
+    bug: "290312729"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ac9ad2d..feccc6b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2320,6 +2320,15 @@
      * @hide
      */
     public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+        final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
+        synchronized (mH) {
+            if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+                // Fail early if the view is not focused and not served
+                // to avoid logging many erroneous calls.
+                return false;
+            }
+        }
+
         final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
         final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
                 null /* component */, Process.myUid(),
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 933cc49..59d7b0e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -2,13 +2,6 @@
 
 # Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
 
-flag {
-    namespace: "windowing_sdk"
-    name: "sync_window_config_update_flag"
-    description: "Whether the feature to sync different window-related config updates is enabled"
-    bug: "260873529"
-}
-
 # Using a fixed read only flag because there are ClientTransaction scheduling before
 # WindowManagerService creation.
 flag {
@@ -35,13 +28,6 @@
 
 flag {
     namespace: "windowing_sdk"
-    name: "window_state_resize_item_flag"
-    description: "Whether to dispatch window resize through ClientTransaction is enabled"
-    bug: "301870955"
-}
-
-flag {
-    namespace: "windowing_sdk"
     name: "fullscreen_dim_flag"
     description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
     bug: "253533308"
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7534d29..7dcbbea 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -249,6 +249,7 @@
 
     private UserHandle mCloneProfileUserHandle;
     private UserHandle mTabOwnerUserHandleForLaunch;
+    private UserHandle mPrivateProfileUserHandle;
 
     protected final LatencyTracker mLatencyTracker = getLatencyTracker();
 
@@ -441,6 +442,7 @@
         mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
         mWorkProfileUserHandle = fetchWorkProfileUserProfile();
         mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+        mPrivateProfileUserHandle = fetchPrivateProfileUserHandle();
         mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
 
         // The last argument of createResolverListAdapter is whether to do special handling
@@ -648,7 +650,8 @@
                 initialIntents,
                 rList,
                 filterLastUsed,
-                /* userHandle */ getPersonalProfileUserHandle());
+                getPersonalProfileUserHandle());
+
         QuietModeManager quietModeManager = createQuietModeManager();
         return new ResolverMultiProfilePagerAdapter(
                 /* context */ this,
@@ -747,6 +750,9 @@
     }
 
     protected UserHandle getPersonalProfileUserHandle() {
+        if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
+            return mPrivateProfileUserHandle;
+        }
         return mPersonalProfileUserHandle;
     }
     protected @Nullable UserHandle getWorkProfileUserHandle() {
@@ -761,6 +767,10 @@
         return mTabOwnerUserHandleForLaunch;
     }
 
+    protected UserHandle getPrivateProfileUserHandle() {
+        return mPrivateProfileUserHandle;
+    }
+
     protected UserHandle fetchPersonalProfileUserHandle() {
         // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
         // profile is active, we always make the personal tab from the foreground user.
@@ -795,12 +805,28 @@
         return mCloneProfileUserHandle;
     }
 
+    protected @Nullable UserHandle fetchPrivateProfileUserHandle() {
+        mPrivateProfileUserHandle = null;
+        UserManager userManager = getSystemService(UserManager.class);
+        for (final UserInfo userInfo :
+                userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+            if (userInfo.isPrivateProfile()) {
+                mPrivateProfileUserHandle = userInfo.getUserHandle();
+                break;
+            }
+        }
+        return mPrivateProfileUserHandle;
+    }
+
     private UserHandle fetchTabOwnerUserHandleForLaunch() {
-        // If we are in work profile's process, return WorkProfile user as owner, otherwise we
-        // always return PersonalProfile user as owner
-        return UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())
-                ? getWorkProfileUserHandle()
-                : getPersonalProfileUserHandle();
+        // If we are in work or private profile's process, return WorkProfile/PrivateProfile user
+        // as owner, otherwise we always return PersonalProfile user as owner
+        if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
+            return getWorkProfileUserHandle();
+        } else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+            return getPrivateProfileUserHandle();
+        }
+        return getPersonalProfileUserHandle();
     }
 
     private boolean hasWorkProfile() {
@@ -816,7 +842,15 @@
                 && (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
     }
 
+    protected final boolean isLaunchedAsPrivateProfile() {
+        return getPrivateProfileUserHandle() != null
+                && (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
+    }
+
     protected boolean shouldShowTabs() {
+        if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+            return false;
+        }
         return hasWorkProfile() && ENABLE_TABBED_VIEW;
     }
 
@@ -2619,6 +2653,11 @@
         return resolveInfo.userHandle;
     }
 
+    private boolean privateSpaceEnabled() {
+        return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+    }
+
     /**
      * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
      * invisible target in the list.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4..5705b7e 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 
+import java.lang.ref.WeakReference;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -63,6 +64,8 @@
 
     PackageMonitorCallback mPackageMonitorCallback;
 
+    private Executor mExecutor;
+
     @UnsupportedAppUsage
     public PackageMonitor() {
         final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@
         if (mPackageMonitorCallback == null) {
             PackageManager pm = mRegisteredContext.getPackageManager();
             if (pm != null) {
-                mPackageMonitorCallback = new PackageMonitorCallback(this,
-                        new HandlerExecutor(mRegisteredHandler));
+                mExecutor = new HandlerExecutor(mRegisteredHandler);
+                mPackageMonitorCallback = new PackageMonitorCallback(this);
                 int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
                 pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
             }
@@ -131,6 +134,7 @@
         }
         mPackageMonitorCallback = null;
         mRegisteredContext = null;
+        mExecutor = null;
     }
 
     public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@
         doHandlePackageEvent(intent);
     }
 
+
+    private void postHandlePackageEvent(Intent intent) {
+        if (mExecutor != null) {
+            mExecutor.execute(() -> doHandlePackageEvent(intent));
+        }
+    }
+
     /**
      * Handle the package related event
      * @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@
     }
 
     private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+        private final WeakReference<PackageMonitor> mMonitorWeakReference;
 
-        private final PackageMonitor mPackageMonitor;
-        private final Executor mExecutor;
-
-        PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
-            mPackageMonitor = monitor;
-            mExecutor = executor;
+        PackageMonitorCallback(PackageMonitor monitor) {
+            mMonitorWeakReference = new WeakReference<>(monitor);
         }
 
         @Override
@@ -537,7 +545,10 @@
                 Log.w(TAG, "No intent is set for PackageMonitorCallback");
                 return;
             }
-            mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+            PackageMonitor monitor = mMonitorWeakReference.get();
+            if (monitor != null) {
+                monitor.postHandlePackageEvent(intent);
+            }
         }
     }
 }
diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
new file mode 100644
index 0000000..f05d9cb
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
@@ -0,0 +1,652 @@
+/*
+ * 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.internal.pm.parsing;
+
+import static com.android.internal.pm.pkg.SEInfoUtil.COMPLETE_STR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FallbackCategoryProvider;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.os.Debug;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.pkg.component.ComponentParseUtils;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import java.util.List;
+
+/**
+ * Method that use a {@link AndroidPackage} to generate a {@link PackageInfo} though
+ * the given {@link PackageManager.PackageInfoFlags}
+ * @hide
+ **/
+// TODO(b/317215254): refactor coped code from PackageInfoUtils
+public class PackageInfoCommonUtils {
+
+    private static final String TAG = ParsingUtils.TAG;
+    private static final boolean DEBUG = false;
+
+    /**
+     * Generates a {@link PackageInfo} from the given {@link AndroidPackage}
+     */
+    @Nullable
+    public static PackageInfo generate(@Nullable AndroidPackage pkg,
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+        if (pkg == null) {
+            return null;
+        }
+        ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, userId);
+
+        PackageInfo info = new PackageInfo();
+        info.packageName = pkg.getPackageName();
+        info.splitNames = pkg.getSplitNames();
+        info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
+        info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
+        info.baseRevisionCode = pkg.getBaseRevisionCode();
+        info.splitRevisionCodes = pkg.getSplitRevisionCodes();
+        info.versionName = pkg.getVersionName();
+        info.sharedUserId = pkg.getSharedUserId();
+        info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+        info.applicationInfo = applicationInfo;
+        info.installLocation = pkg.getInstallLocation();
+        if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                || (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+            info.requiredForAllUsers = pkg.isRequiredForAllUsers();
+        }
+        info.restrictedAccountType = pkg.getRestrictedAccountType();
+        info.requiredAccountType = pkg.getRequiredAccountType();
+        info.overlayTarget = pkg.getOverlayTarget();
+        info.targetOverlayableName = pkg.getOverlayTargetOverlayableName();
+        info.overlayCategory = pkg.getOverlayCategory();
+        info.overlayPriority = pkg.getOverlayPriority();
+        info.mOverlayIsStatic = pkg.isOverlayIsStatic();
+        info.compileSdkVersion = pkg.getCompileSdkVersion();
+        info.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName();
+        info.isStub = pkg.isStub();
+        info.coreApp = pkg.isCoreApp();
+        info.isApex = pkg.isApex();
+
+        if ((flags & PackageManager.GET_CONFIGURATIONS) != 0) {
+            int size = pkg.getConfigPreferences().size();
+            if (size > 0) {
+                info.configPreferences = new ConfigurationInfo[size];
+                pkg.getConfigPreferences().toArray(info.configPreferences);
+            }
+            size = pkg.getRequestedFeatures().size();
+            if (size > 0) {
+                info.reqFeatures = new FeatureInfo[size];
+                pkg.getRequestedFeatures().toArray(info.reqFeatures);
+            }
+            size = pkg.getFeatureGroups().size();
+            if (size > 0) {
+                info.featureGroups = new FeatureGroupInfo[size];
+                pkg.getFeatureGroups().toArray(info.featureGroups);
+            }
+        }
+        if ((flags & PackageManager.GET_PERMISSIONS) != 0) {
+            int size = ArrayUtils.size(pkg.getPermissions());
+            if (size > 0) {
+                info.permissions = new PermissionInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final var permission = pkg.getPermissions().get(i);
+                    final var permissionInfo = generatePermissionInfo(permission, flags);
+                    info.permissions[i] = permissionInfo;
+                }
+            }
+            final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+            size = usesPermissions.size();
+            if (size > 0) {
+                info.requestedPermissions = new String[size];
+                info.requestedPermissionsFlags = new int[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+                    info.requestedPermissions[i] = usesPermission.getName();
+                    // The notion of required permissions is deprecated but for compatibility.
+                    info.requestedPermissionsFlags[i] |=
+                            PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+                    if ((usesPermission.getUsesPermissionFlags()
+                            & ParsedUsesPermission.FLAG_NEVER_FOR_LOCATION) != 0) {
+                        info.requestedPermissionsFlags[i] |=
+                                PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+                    }
+                    if (pkg.getImplicitPermissions().contains(info.requestedPermissions[i])) {
+                        info.requestedPermissionsFlags[i] |=
+                                PackageInfo.REQUESTED_PERMISSION_IMPLICIT;
+                    }
+                }
+            }
+        }
+        if ((flags & PackageManager.GET_ATTRIBUTIONS_LONG) != 0) {
+            int size = ArrayUtils.size(pkg.getAttributions());
+            if (size > 0) {
+                info.attributions = new Attribution[size];
+                for (int i = 0; i < size; i++) {
+                    ParsedAttribution parsedAttribution = pkg.getAttributions().get(i);
+                    if (parsedAttribution != null) {
+                        info.attributions[i] = new Attribution(parsedAttribution.getTag(),
+                                parsedAttribution.getLabel());
+                    }
+                }
+            }
+            if (pkg.isAttributionsUserVisible()) {
+                info.applicationInfo.privateFlagsExt
+                        |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+            } else {
+                info.applicationInfo.privateFlagsExt
+                        &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+            }
+        } else {
+            info.applicationInfo.privateFlagsExt
+                    &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+        }
+
+        final SigningDetails signingDetails = pkg.getSigningDetails();
+        // deprecated method of getting signing certificates
+        if ((flags & PackageManager.GET_SIGNATURES) != 0) {
+            if (signingDetails.hasPastSigningCertificates()) {
+                // Package has included signing certificate rotation information.  Return the oldest
+                // cert so that programmatic checks keep working even if unaware of key rotation.
+                info.signatures = new Signature[1];
+                info.signatures[0] = signingDetails.getPastSigningCertificates()[0];
+            } else if (signingDetails.hasSignatures()) {
+                // otherwise keep old behavior
+                int numberOfSigs = signingDetails.getSignatures().length;
+                info.signatures = new Signature[numberOfSigs];
+                System.arraycopy(signingDetails.getSignatures(), 0, info.signatures, 0,
+                        numberOfSigs);
+            }
+        }
+
+        // replacement for GET_SIGNATURES
+        if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+            if (signingDetails != SigningDetails.UNKNOWN) {
+                // only return a valid SigningInfo if there is signing information to report
+                info.signingInfo = new SigningInfo(signingDetails);
+            } else {
+                info.signingInfo = null;
+            }
+        }
+
+        if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+            final int size = pkg.getActivities().size();
+            if (size > 0) {
+                int num = 0;
+                final ActivityInfo[] res = new ActivityInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedActivity a = pkg.getActivities().get(i);
+                    if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+                        if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
+                                a.getName())) {
+                            continue;
+                        }
+                        res[num++] = generateActivityInfo(a, flags, applicationInfo);
+                    }
+                }
+                info.activities = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+            final int size = pkg.getReceivers().size();
+            if (size > 0) {
+                int num = 0;
+                final ActivityInfo[] res = new ActivityInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedActivity a = pkg.getReceivers().get(i);
+                    if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+                        res[num++] = generateActivityInfo(a, flags, applicationInfo);
+                    }
+                }
+                info.receivers = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_SERVICES) != 0) {
+            final int size = pkg.getServices().size();
+            if (size > 0) {
+                int num = 0;
+                final ServiceInfo[] res = new ServiceInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedService s = pkg.getServices().get(i);
+                    if (isMatch(pkg, s.isDirectBootAware(), flags)) {
+                        res[num++] = generateServiceInfo(s, flags, applicationInfo);
+                    }
+                }
+                info.services = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+            final int size = pkg.getProviders().size();
+            if (size > 0) {
+                int num = 0;
+                final ProviderInfo[] res = new ProviderInfo[size];
+                for (int i = 0; i < size; i++) {
+                    final ParsedProvider pr = pkg.getProviders().get(i);
+                    if (isMatch(pkg, pr.isDirectBootAware(), flags)) {
+                        res[num++] = generateProviderInfo(pkg, pr, flags, applicationInfo, userId);
+                    }
+                }
+                info.providers = ArrayUtils.trimToSize(res, num);
+            }
+        }
+        if ((flags & PackageManager.GET_INSTRUMENTATION) != 0) {
+            final int size = pkg.getInstrumentations().size();
+            if (size > 0) {
+                info.instrumentation = new InstrumentationInfo[size];
+                for (int i = 0; i < size; i++) {
+                    info.instrumentation[i] = generateInstrumentationInfo(
+                            pkg.getInstrumentations().get(i), pkg, flags, userId);
+                }
+            }
+        }
+
+        return info;
+    }
+
+    private static void updateApplicationInfo(ApplicationInfo ai, long flags) {
+        if ((flags & PackageManager.GET_META_DATA) == 0) {
+            ai.metaData = null;
+        }
+        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) == 0) {
+            ai.sharedLibraryFiles = null;
+            ai.sharedLibraryInfos = null;
+        }
+
+        // CompatibilityMode is global state.
+        if (!ParsingPackageUtils.sCompatibilityModeEnabled) {
+            ai.disableCompatibilityMode();
+        }
+
+        if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+            ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
+        }
+        ai.seInfoUser = COMPLETE_STR;
+    }
+
+    @Nullable
+    private static ApplicationInfo generateApplicationInfo(@NonNull AndroidPackage pkg,
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId) {
+
+        // Make shallow copy so we can store the metadata/libraries safely
+        ApplicationInfo info = ((AndroidPackageHidden) pkg).toAppInfoWithoutState();
+
+        updateApplicationInfo(info, flags);
+
+        initForUser(info, pkg, userId);
+
+        info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+        info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            info.metaData = pkg.getMetaData();
+        }
+        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+            List<String> usesLibraryFiles = pkg.getUsesLibraries();
+
+            info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+                    ? null : usesLibraryFiles.toArray(new String[0]);
+        }
+
+        return info;
+    }
+
+    @Nullable
+    private static ActivityInfo generateActivityInfo(ParsedActivity a,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull ApplicationInfo applicationInfo) {
+        if (a == null) return null;
+
+        // Make shallow copies so we can store the metadata safely
+        ActivityInfo ai = new ActivityInfo();
+        ai.targetActivity = a.getTargetActivity();
+        ai.processName = a.getProcessName();
+        ai.exported = a.isExported();
+        ai.theme = a.getTheme();
+        ai.uiOptions = a.getUiOptions();
+        ai.parentActivityName = a.getParentActivityName();
+        ai.permission = a.getPermission();
+        ai.taskAffinity = a.getTaskAffinity();
+        ai.flags = a.getFlags();
+        ai.privateFlags = a.getPrivateFlags();
+        ai.launchMode = a.getLaunchMode();
+        ai.documentLaunchMode = a.getDocumentLaunchMode();
+        ai.maxRecents = a.getMaxRecents();
+        ai.configChanges = a.getConfigChanges();
+        ai.softInputMode = a.getSoftInputMode();
+        ai.persistableMode = a.getPersistableMode();
+        ai.lockTaskLaunchMode = a.getLockTaskLaunchMode();
+        ai.screenOrientation = a.getScreenOrientation();
+        ai.resizeMode = a.getResizeMode();
+        ai.setMaxAspectRatio(a.getMaxAspectRatio());
+        ai.setMinAspectRatio(a.getMinAspectRatio());
+        ai.supportsSizeChanges = a.isSupportsSizeChanges();
+        ai.requestedVrComponent = a.getRequestedVrComponent();
+        ai.rotationAnimation = a.getRotationAnimation();
+        ai.colorMode = a.getColorMode();
+        ai.windowLayout = a.getWindowLayout();
+        ai.attributionTags = a.getAttributionTags();
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            var metaData = a.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            ai.metaData = metaData.isEmpty() ? null : metaData;
+        } else {
+            ai.metaData = null;
+        }
+        ai.applicationInfo = applicationInfo;
+        ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+        ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
+        assignFieldsComponentInfoParsedMainComponent(ai, a);
+        return ai;
+    }
+
+    @Nullable
+    private static ServiceInfo generateServiceInfo(ParsedService s,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull ApplicationInfo applicationInfo) {
+        if (s == null) return null;
+
+        // Make shallow copies so we can store the metadata safely
+        ServiceInfo si = new ServiceInfo();
+        si.exported = s.isExported();
+        si.flags = s.getFlags();
+        si.permission = s.getPermission();
+        si.processName = s.getProcessName();
+        si.mForegroundServiceType = s.getForegroundServiceType();
+        si.applicationInfo = applicationInfo;
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            var metaData = s.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            si.metaData = metaData.isEmpty() ? null : metaData;
+        }
+        assignFieldsComponentInfoParsedMainComponent(si, s);
+        return si;
+    }
+
+    @Nullable
+    private static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull ApplicationInfo applicationInfo, int userId) {
+        if (p == null) return null;
+
+        if (!pkg.getPackageName().equals(applicationInfo.packageName)) {
+            Slog.wtf(TAG, "AppInfo's package name is different. Expected=" + pkg.getPackageName()
+                    + " actual=" + applicationInfo.packageName);
+            applicationInfo = generateApplicationInfo(pkg, flags, userId);
+        }
+
+        // Make shallow copies so we can store the metadata safely
+        ProviderInfo pi = new ProviderInfo();
+        pi.exported = p.isExported();
+        pi.flags = p.getFlags();
+        pi.processName = p.getProcessName();
+        pi.authority = p.getAuthority();
+        pi.isSyncable = p.isSyncable();
+        pi.readPermission = p.getReadPermission();
+        pi.writePermission = p.getWritePermission();
+        pi.grantUriPermissions = p.isGrantUriPermissions();
+        pi.forceUriPermissions = p.isForceUriPermissions();
+        pi.multiprocess = p.isMultiProcess();
+        pi.initOrder = p.getInitOrder();
+        pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+        pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
+        if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+            pi.uriPermissionPatterns = null;
+        }
+        if ((flags & PackageManager.GET_META_DATA) != 0) {
+            var metaData = p.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            pi.metaData = metaData.isEmpty() ? null : metaData;
+        }
+        pi.applicationInfo = applicationInfo;
+        assignFieldsComponentInfoParsedMainComponent(pi, p);
+        return pi;
+    }
+
+    @Nullable
+    private static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
+            AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
+        if (i == null) return null;
+
+        InstrumentationInfo info = new InstrumentationInfo();
+        info.targetPackage = i.getTargetPackage();
+        info.targetProcesses = i.getTargetProcesses();
+        info.handleProfiling = i.isHandleProfiling();
+        info.functionalTest = i.isFunctionalTest();
+
+        info.sourceDir = pkg.getBaseApkPath();
+        info.publicSourceDir = pkg.getBaseApkPath();
+        info.splitNames = pkg.getSplitNames();
+        info.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+        info.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+                ? null : pkg.getSplitCodePaths();
+        info.splitDependencies = pkg.getSplitDependencies().size() == 0
+                ? null : pkg.getSplitDependencies();
+
+        initForUser(info, pkg, userId);
+
+        info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+        info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+        info.nativeLibraryDir = pkg.getNativeLibraryDir();
+        info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
+
+        assignFieldsPackageItemInfoParsedComponent(info, i);
+
+        if ((flags & PackageManager.GET_META_DATA) == 0) {
+            info.metaData = null;
+        } else {
+            var metaData = i.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            info.metaData = metaData.isEmpty() ? null : metaData;
+        }
+
+        return info;
+    }
+
+    @Nullable
+    private static PermissionInfo generatePermissionInfo(ParsedPermission p,
+            @PackageManager.ComponentInfoFlagsBits long flags) {
+        // TODO(b/135203078): Remove null checks and make all usages @NonNull
+        if (p == null) return null;
+
+        PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission());
+
+        assignFieldsPackageItemInfoParsedComponent(pi, p);
+
+        pi.group = p.getGroup();
+        pi.requestRes = p.getRequestRes();
+        pi.protectionLevel = p.getProtectionLevel();
+        pi.descriptionRes = p.getDescriptionRes();
+        pi.flags = p.getFlags();
+        pi.knownCerts = p.getKnownCerts();
+
+        if ((flags & PackageManager.GET_META_DATA) == 0) {
+            pi.metaData = null;
+        } else {
+            var metaData = p.getMetaData();
+            // Backwards compatibility, coerce to null if empty
+            pi.metaData = metaData.isEmpty() ? null : metaData;
+        }
+        return pi;
+    }
+
+    private static void assignFieldsComponentInfoParsedMainComponent(
+            @NonNull ComponentInfo info, @NonNull ParsedMainComponent component) {
+        assignFieldsPackageItemInfoParsedComponent(info, component);
+        info.descriptionRes = component.getDescriptionRes();
+        info.directBootAware = component.isDirectBootAware();
+        info.enabled = component.isEnabled();
+        info.splitName = component.getSplitName();
+        info.attributionTags = component.getAttributionTags();
+        info.nonLocalizedLabel = component.getNonLocalizedLabel();
+        info.icon = component.getIcon();
+    }
+
+    private static void assignFieldsPackageItemInfoParsedComponent(
+            @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component) {
+        packageItemInfo.nonLocalizedLabel = ComponentParseUtils.getNonLocalizedLabel(component);
+        packageItemInfo.icon = ComponentParseUtils.getIcon(component);
+        packageItemInfo.banner = component.getBanner();
+        packageItemInfo.labelRes = component.getLabelRes();
+        packageItemInfo.logo = component.getLogo();
+        packageItemInfo.name = component.getName();
+        packageItemInfo.packageName = component.getPackageName();
+    }
+
+    private static void initForUser(ApplicationInfo output, AndroidPackage input,
+            @UserIdInt int userId) {
+        PackageImpl pkg = ((PackageImpl) input);
+        String packageName = input.getPackageName();
+        output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
+
+        // For performance reasons, all these paths are built as strings
+        final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+        final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+        if (credentialDir !=  null && deviceDir != null) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                output.credentialProtectedDataDir = credentialDir + packageName;
+                output.deviceProtectedDataDir = deviceDir + packageName;
+            } else {
+                // Convert /data/user/0/ -> /data/user/1/com.example.app
+                String userIdString = String.valueOf(userId);
+                int credentialLength = credentialDir.length();
+                output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+                        .replace(credentialLength - 2, credentialLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+                int deviceLength = deviceDir.length();
+                output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+                        .replace(deviceLength - 2, deviceLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+            }
+        }
+
+        if (input.isDefaultToDeviceProtectedStorage()
+                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+            output.dataDir = output.deviceProtectedDataDir;
+        } else {
+            output.dataDir = output.credentialProtectedDataDir;
+        }
+    }
+
+    // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
+    // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
+    private static void initForUser(InstrumentationInfo output, AndroidPackage input,
+            @UserIdInt int userId) {
+        PackageImpl pkg = ((PackageImpl) input);
+        String packageName = input.getPackageName();
+
+        // For performance reasons, all these paths are built as strings
+        final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+        final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+        if (credentialDir !=  null && deviceDir != null) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                output.credentialProtectedDataDir = credentialDir + packageName;
+                output.deviceProtectedDataDir = deviceDir + packageName;
+            } else {
+                // Convert /data/user/0/ -> /data/user/1/com.example.app
+                String userIdString = String.valueOf(userId);
+                int credentialLength = credentialDir.length();
+                output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+                        .replace(credentialLength - 2, credentialLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+                int deviceLength = deviceDir.length();
+                output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+                        .replace(deviceLength - 2, deviceLength - 1, userIdString)
+                        .append(packageName)
+                        .toString();
+            }
+        }
+
+        if (input.isDefaultToDeviceProtectedStorage()
+                && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+            output.dataDir = output.deviceProtectedDataDir;
+        } else {
+            output.dataDir = output.credentialProtectedDataDir;
+        }
+    }
+
+    /**
+     * Test if the given component is considered system, enabled and a match for the given
+     * flags.
+     *
+     * <p>
+     * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link
+     * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
+     * </p>
+     */
+    private static boolean isMatch(AndroidPackage pkg,
+            boolean isComponentDirectBootAware, long flags) {
+        final boolean isSystem = ((AndroidPackageHidden) pkg).isSystem();
+        if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+            if (!isSystem) {
+                return reportIfDebug(false, flags);
+            }
+        }
+
+        final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0)
+                && !isComponentDirectBootAware;
+        final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0)
+                && isComponentDirectBootAware;
+        return reportIfDebug(matchesUnaware || matchesAware, flags);
+    }
+
+    private static boolean reportIfDebug(boolean result, long flags) {
+        if (DEBUG && !result) {
+            Slog.i(TAG, "No match!; flags: "
+                    + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+                    + Debug.getCaller());
+        }
+        return result;
+    }
+}
diff --git a/core/java/com/android/internal/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
index e413293..2c54672 100644
--- a/core/java/com/android/internal/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.app.Application;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseInput;
@@ -40,6 +41,7 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -78,10 +80,19 @@
             displayMetrics.setToDefaults();
         }
 
-        PermissionManager permissionManager = ActivityThread.currentApplication()
-                .getSystemService(PermissionManager.class);
-        List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
-                .getSplitPermissions();
+        List<PermissionManager.SplitPermissionInfo> splitPermissions = null;
+
+        final Application application = ActivityThread.currentApplication();
+        if (application != null) {
+            final PermissionManager permissionManager =
+                    application.getSystemService(PermissionManager.class);
+            if (permissionManager != null) {
+                splitPermissions = permissionManager.getSplitPermissions();
+            }
+        }
+        if (splitPermissions == null) {
+            splitPermissions = new ArrayList<>();
+        }
 
         mCacher = cacher;
 
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d1..56ea52d 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@
     return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
 }
 
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+                                                    jobject sensor, jint index) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+    Vector<Sensor> sensorList;
+    ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+    if (ssize_t(index) >= count) {
+        return false;
+    }
+
+    return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
 static void
 nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
 
@@ -539,6 +552,9 @@
         {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
          (void *)nativeGetSensorAtIndex},
 
+        {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+         (void *)nativeGetDefaultDeviceSensorAtIndex},
+
         {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
 
         {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 930b1a4..95d5049 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -65,7 +65,7 @@
         mHandler = getInstrumentation().getContext().getMainThreadHandler();
         mController = spy(ClientTransactionListenerController.createInstanceForTesting(
                 mDisplayManager));
-        doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled();
+        doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 8308e7c..1617eda 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -20,12 +20,15 @@
 import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.CheckFlagsRule
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.SparseArray
 import androidx.core.util.forEach
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
 import org.junit.Rule
 import kotlin.math.ceil
 import kotlin.math.floor
@@ -45,6 +48,19 @@
     @get:Rule
     val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
+    private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+
+    @Before
+    fun setup() {
+        defaultLookupTables = FontScaleConverterFactory.sLookupTables.clone()
+    }
+
+    @After
+    fun teardown() {
+        // Restore the default tables (since some tests will have added extras to the cache)
+        FontScaleConverterFactory.sLookupTables = defaultLookupTables
+    }
+
     @Test
     fun scale200IsTwiceAtSmallSizes() {
         val table = FontScaleConverterFactory.forScale(2F)!!
@@ -245,7 +261,7 @@
     }
 
     companion object {
-        private const val CONVERSION_TOLERANCE = 0.05f
+        private const val CONVERSION_TOLERANCE = 0.18f
     }
 }
 
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a5bbeb5..9292f66 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -16,7 +16,6 @@
 
 package android.window.flags;
 
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
 import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
 
 import android.platform.test.annotations.Presubmit;
@@ -39,12 +38,6 @@
 public class WindowFlagsTest {
 
     @Test
-    public void testSyncWindowConfigUpdateFlag() {
-        // No crash when accessing the flag.
-        syncWindowConfigUpdateFlag();
-    }
-
-    @Test
     public void testTaskFragmentSystemOrganizerFlag() {
         // No crash when accessing the flag.
         taskFragmentSystemOrganizerFlag();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b6813ff..b209c7c 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,7 @@
 import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
 import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import static org.hamcrest.CoreMatchers.allOf;
@@ -46,6 +47,7 @@
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.RelativeLayout;
@@ -88,7 +90,8 @@
     public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
             new ActivityTestRule<>(ResolverWrapperActivity.class, false,
                     false);
-
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Before
     public void cleanOverrideData() {
         sOverrides.reset();
@@ -1156,6 +1159,97 @@
                 sOverrides.cloneProfileUserHandle)));
     }
 
+    @Test
+    public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> privateResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+        setupResolverControllers(privateResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+        onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+        for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    sOverrides.privateProfileUserHandle);
+        }
+    }
+
+    @Test
+    public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        ResolverActivity.ENABLE_TABBED_VIEW = false;
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> privateResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+        setupResolverControllers(privateResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+        onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+        assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+        for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    sOverrides.privateProfileUserHandle);
+        }
+    }
+
+    @Test
+    public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+        for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+            assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+                    activity.getPersonalProfileUserHandle());
+        }
+    }
+
+    @Test
+    public void testPrivateProfile_triggerFromWorkProfile(){
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+        markPrivateProfileUserAvailable();
+        markWorkProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getWorkListAdapter().getCount(), is(4));
+        onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+        for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+            assertTrue(resolvedInfo.getResolveInfoAt(0).userHandle.equals(
+                    activity.getPersonalProfileUserHandle()) || resolvedInfo.getResolveInfoAt(
+                    0).userHandle.equals(activity.getWorkProfileUserHandle()));
+        }
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -1237,6 +1331,10 @@
         ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
     }
 
+    private void markPrivateProfileUserAvailable() {
+        ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+    }
+
     private void setupResolverControllers(
             List<ResolvedComponentInfo> personalResolvedComponentInfos,
             List<ResolvedComponentInfo> workResolvedComponentInfos) {
@@ -1256,4 +1354,13 @@
                 eq(UserHandle.SYSTEM)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
     }
+
+    private void setupResolverControllers(
+            List<ResolvedComponentInfo> resolvedComponentInfos) {
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(resolvedComponentInfos));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index e193de0..862cbd5 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -88,6 +88,10 @@
         return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
     }
 
+    int getMultiProfilePagerAdapterCount(){
+        return mMultiProfilePagerAdapter.getCount();
+    }
+
     @Override
     public boolean isVoiceInteraction() {
         if (sOverrides.isVoiceInteraction != null) {
@@ -144,6 +148,11 @@
     }
 
     @Override
+    protected UserHandle getPrivateProfileUserHandle() {
+        return sOverrides.privateProfileUserHandle;
+    }
+
+    @Override
     protected UserHandle getTabOwnerUserHandleForLaunch() {
         if (sOverrides.tabOwnerUserHandleForLaunch == null) {
             return super.getTabOwnerUserHandleForLaunch();
@@ -176,6 +185,7 @@
         public Boolean isVoiceInteraction;
         public UserHandle workProfileUserHandle;
         public UserHandle cloneProfileUserHandle;
+        public UserHandle privateProfileUserHandle;
         public UserHandle tabOwnerUserHandleForLaunch;
         public Integer myUserId;
         public boolean hasCrossProfileIntents;
@@ -191,6 +201,7 @@
             workResolverListController = mock(ResolverListController.class);
             workProfileUserHandle = null;
             cloneProfileUserHandle = null;
+            privateProfileUserHandle = null;
             tabOwnerUserHandleForLaunch = null;
             myUserId = null;
             hasCrossProfileIntents = true;
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87..0000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d939..0000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187e..0000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 0000000..bb7d63e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_NonPlatformSignatureOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+    ],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    manifest: "static/AndroidManifest.xml",
+    aaptflags: [
+        "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+    ],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_PlatformSignatureOverlay",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.signature_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde9..0000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 0000000..ee0c0e5
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_UpdateOverlay",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    static_libs: ["androidx.test.rules"],
+    aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_FrameworkOverlayV1",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.framework_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+    resource_dirs: ["framework/v1/res"],
+    manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_FrameworkOverlayV2",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.framework_overlay_v2",
+        "--version-code",
+        "2",
+        "--version-name",
+        "v2",
+    ],
+    resource_dirs: ["framework/v2/res"],
+    manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_AppOverlayV1",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.app_overlay_v1",
+        "--version-code",
+        "1",
+        "--version-name",
+        "v1",
+    ],
+    resource_dirs: ["app/v1/res"],
+    manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "OverlayHostTests_AppOverlayV2",
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    aaptflags: [
+        "--custom-package",
+        "com.android.server.om.hosttest.app_overlay_v2",
+        "--version-code",
+        "2",
+        "--version-name",
+        "v2",
+    ],
+    resource_dirs: ["app/v2/res"],
+    manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887..0000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f6..13d38d2 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
     <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
     <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
 
-    <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
-        otherwise, it may not be able to enforce provision for managed devices. -->
+    <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+         when app in background; otherwise, it may not be able to enforce provision for managed
+         devices. -->
     <allow-in-power-save package="com.android.devicelockcontroller" />
+    <allow-in-data-usage-save package="com.android.devicelockcontroller" />
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f..b9efe65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@
         <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
         <permission name="android.permission.BIND_WALLPAPER"/>
         <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+        <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.dynsystem">
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1..e4f793c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -30,8 +30,8 @@
         android:orientation="horizontal"
         android:clickable="true"
         android:focusable="true"
-        android:paddingStart="16dp">
-
+        android:paddingStart="6dp"
+        android:paddingEnd="8dp">
         <ImageView
             android:id="@+id/application_icon"
             android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -43,7 +43,7 @@
             android:id="@+id/application_name"
             android:layout_width="0dp"
             android:layout_height="20dp"
-            android:minWidth="80dp"
+            android:maxWidth="86dp"
             android:textAppearance="@android:style/TextAppearance.Material.Title"
             android:textSize="14sp"
             android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de61..0a40cea 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,25 @@
     <!-- Height of desktop mode caption for fullscreen tasks. -->
     <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
 
+    <!-- Required empty space to be visible for partially offscreen tasks. -->
+    <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+    <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+    <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+    <!-- 32dp width back button + 10dp margin -->
+    <dimen name="caption_left_buttons_width">32dp</dimen>
+
+    <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+    <dimen name="caption_right_buttons_width">126dp</dimen>
+
+    <!-- 2 buttons * 48dp button size. -->
+    <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+
+    <!-- 22dp padding + 24dp app icon + 16dp expand button.
+         Text varies in size, we will calculate that width separately. -->
+    <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
     <!-- The width of the maximize menu in desktop mode. -->
     <dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba..af31f5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.transition;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 
 import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -35,7 +36,8 @@
 
 /**
  * The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
  */
 public class HomeTransitionObserver implements TransitionObserver,
         RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@
         for (TransitionInfo.Change change : info.getChanges()) {
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
             if (taskInfo == null
+                    || taskInfo.displayId != DEFAULT_DISPLAY
                     || taskInfo.taskId == -1
                     || !taskInfo.isRunning) {
                 continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c6..72fba3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@
 import android.window.TransitionFilter;
 
 /**
- *  Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
  */
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
 
     /**
-     * Called when a transition changes the visibility of the home activity.
+     * Called when a transition changes the visibility of the home activity on the default display.
      */
     void onHomeVisibilityChanged(in boolean isVisible);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b..6e7d11d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
@@ -34,6 +35,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 /**
@@ -84,6 +86,69 @@
         mDragPositioningCallback = dragPositioningCallback;
     }
 
+    @Override
+    Rect calculateValidDragArea() {
+        final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.caption_left_buttons_width);
+
+        // On a smaller screen, don't require as much empty space on screen, as offscreen
+        // drags will be restricted too much.
+        final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+                .getResources().getConfiguration().smallestScreenWidthDp >= 600
+                ? R.dimen.freeform_required_visible_empty_space_in_header :
+                R.dimen.small_screen_required_visible_empty_space_in_header;
+        final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+                requiredEmptySpaceId);
+
+        final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.caption_right_buttons_width);
+        final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+        final int displayWidth = layout.width();
+        final Rect stableBounds = new Rect();
+        layout.getStableBounds(stableBounds);
+        return new Rect(
+                determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+                        taskWidth),
+                stableBounds.top,
+                determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+                        displayWidth),
+                determineMaxY(requiredEmptySpace, stableBounds));
+    }
+
+
+    /**
+     * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth) {
+        // Do not let apps with < 48dp empty header space go off the left edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return 0;
+        }
+        return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+    }
+
+    /**
+     * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth, int displayWidth) {
+        // Do not let apps with < 48dp empty header space go off the right edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return displayWidth - taskWidth;
+        }
+        return displayWidth - requiredEmptySpace - leftButtonsWidth;
+    }
+
+    /**
+     * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+        return stableBounds.bottom - requiredEmptySpace;
+    }
+
+
     void setDragDetector(DragDetector dragDetector) {
         mDragDetector = dragDetector;
         mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6ec91e0..3b6be8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -443,6 +443,66 @@
     }
 
     /**
+     * Determine valid drag area for this task based on elements in the app chip.
+     */
+    @Override
+    Rect calculateValidDragArea() {
+        final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+                mWindowDecorViewHolder).getAppNameTextWidth();
+        final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+        final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.freeform_required_visible_empty_space_in_header);
+        final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+                R.dimen.desktop_mode_right_edge_buttons_width);
+        final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+        final int displayWidth = layout.width();
+        final Rect stableBounds = new Rect();
+        layout.getStableBounds(stableBounds);
+        return new Rect(
+                determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+                        taskWidth),
+                stableBounds.top,
+                determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+                        taskWidth, displayWidth),
+                determineMaxY(requiredEmptySpace, stableBounds));
+    }
+
+
+    /**
+     * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth) {
+        // Do not let apps with < 48dp empty header space go off the left edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return 0;
+        }
+        return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+    }
+
+    /**
+     * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+            int taskWidth, int displayWidth) {
+        // Do not let apps with < 48dp empty header space go off the right edge at all.
+        if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+            return displayWidth - taskWidth;
+        }
+        return displayWidth - requiredEmptySpace - leftButtonsWidth;
+    }
+
+    /**
+     * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+     */
+    private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+        return stableBounds.bottom - requiredEmptySpace;
+    }
+
+
+    /**
      * Create and display maximize menu window
      */
     void createMaximizeMenu() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c7..677c7f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -162,18 +162,29 @@
 
     /**
      * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
-     * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
-     * stable bounds.
+     * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+     * valid drag area.
      */
-    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
-            PointF repositionStartPoint, float x, float y)  {
+    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+            PointF repositionStartPoint, float x, float y, Rect validDragArea) {
         updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
                 x, y);
+        snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
+    }
 
-        // If task is outside of stable bounds (in the status bar area), shift the task down.
-        if (stableBounds.top > repositionTaskBounds.top) {
-            final int yShift =  stableBounds.top - repositionTaskBounds.top;
-            repositionTaskBounds.offset(0, yShift);
+    private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+        // If we were never supplied a valid drag area, do not restrict movement.
+        // Otherwise, we restrict deltas to keep task position inside the Rect.
+        if (validDragArea.width() == 0) return;
+        if (repositionTaskBounds.left < validDragArea.left) {
+            repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+        } else if (repositionTaskBounds.left > validDragArea.right) {
+            repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+        }
+        if (repositionTaskBounds.top < validDragArea.top) {
+            repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+        } else if (repositionTaskBounds.top > validDragArea.bottom) {
+            repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e..5d006fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -136,7 +136,8 @@
                 y)) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+                    mWindowDecoration.calculateValidDragArea());
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTaskOrganizer.applyTransaction(wct);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0c..4363558 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -152,7 +152,8 @@
                 mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
                 y)) {
             DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+                    mDesktopWindowDecoration.calculateValidDragArea());
             DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
                     mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b755..ee0e31e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@
 import static android.view.WindowInsets.Type.statusBars;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
@@ -178,6 +179,13 @@
      */
     abstract void relayout(RunningTaskInfo taskInfo);
 
+    /**
+     * Used by the {@link DragPositioningCallback} associated with the implementing class to
+     * enforce drags ending in a valid position. A null result means no restriction.
+     */
+    @Nullable
+    abstract Rect calculateValidDragArea();
+
     void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
             RelayoutResult<T> outResult) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a813..144373f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -42,6 +42,8 @@
     private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
     private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
     private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+    val appNameTextWidth: Int
+        get() = appNameTextView.width
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04a..e60be71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@
     }
 
     @Test
+    fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+        val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+            STABLE_BOUNDS.top,
+            DISPLAY_BOUNDS.right - 100,
+            DISPLAY_BOUNDS.bottom - 100)
+
+        DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+            startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+            validDragArea)
+        assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+        assertThat(repositionTaskBounds.right)
+            .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+        assertThat(repositionTaskBounds.bottom)
+            .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+    }
+
+    @Test
     fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
         var hasMoved = false
         val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2..2ce49cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -103,6 +103,7 @@
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
             configuration.windowConfiguration.displayRotation = ROTATION_90
         }
+        `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
 
@@ -660,6 +661,38 @@
     }
 
     @Test
+    fun testDragResize_drag_taskPositionedInValidDragArea() {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED, // drag
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = VALID_DRAG_AREA.left - 500f
+        val newY = VALID_DRAG_AREA.bottom + 500f
+        taskPositioner.onDragPositioningMove(
+            newX,
+            newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+            newX,
+            newY
+        )
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        VALID_DRAG_AREA.bottom &&
+                        change.configuration.windowConfiguration.bounds.left ==
+                        VALID_DRAG_AREA.left
+            }
+        })
+    }
+
+    @Test
     fun testDragResize_drag_updatesStableBoundsOnRotate() {
         // Test landscape stable bounds
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -761,5 +794,11 @@
             DISPLAY_BOUNDS.bottom,
             DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
         )
+        private val VALID_DRAG_AREA = Rect(
+            DISPLAY_BOUNDS.left - 100,
+            STABLE_BOUNDS_LANDSCAPE.top,
+            DISPLAY_BOUNDS.right - 100,
+            DISPLAY_BOUNDS.bottom - 100
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf1..a759b53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -118,6 +118,7 @@
             configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
             configuration.windowConfiguration.displayRotation = ROTATION_90
         }
+        `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
 
@@ -379,6 +380,38 @@
     }
 
     @Test
+    fun testDragResize_drag_taskPositionedInValidDragArea() {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED, // drag
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = VALID_DRAG_AREA.left - 500f
+        val newY = VALID_DRAG_AREA.bottom + 500f
+        taskPositioner.onDragPositioningMove(
+            newX,
+            newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+            newX,
+            newY
+        )
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        VALID_DRAG_AREA.bottom &&
+                        change.configuration.windowConfiguration.bounds.left ==
+                        VALID_DRAG_AREA.left
+            }
+        })
+    }
+
+    @Test
     fun testDragResize_drag_updatesStableBoundsOnRotate() {
         // Test landscape stable bounds
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -470,5 +503,11 @@
             DISPLAY_BOUNDS.bottom,
             DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
         )
+        private val VALID_DRAG_AREA = Rect(
+            DISPLAY_BOUNDS.left - 100,
+            STABLE_BOUNDS_LANDSCAPE.top,
+            DISPLAY_BOUNDS.right - 100,
+            DISPLAY_BOUNDS.bottom - 100
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e42f74..fe508e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -702,6 +702,11 @@
             relayout(taskInfo, false /* applyStartTransactionOnDraw */);
         }
 
+        @Override
+        Rect calculateValidDragArea() {
+            return null;
+        }
+
         void relayout(ActivityManager.RunningTaskInfo taskInfo,
                 boolean applyStartTransactionOnDraw) {
             mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000..67f4775 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.location.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -37,6 +40,23 @@
  * @hide
  */
 public final class LocationResult implements Parcelable {
+    private static final String TAG = "LocationResult";
+
+    // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+    // could likely be lower, but we only want to throw out really absurd values.
+    private static final float MAX_ACCURACY_M = 1000000;
+
+    // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+    // current fastest private jet). Higher speed than the value is considered as a malfunction
+    // than a correct reading.
+    private static final float MAX_SPEED_MPS = 343;
+
+    /** Exception representing an invalid location within a {@link LocationResult}. */
+    public static class BadLocationException extends Exception {
+        public BadLocationException(String message) {
+            super(message);
+        }
+    }
 
     /**
      * Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@
      *
      * @hide
      */
-    public @NonNull LocationResult validate() {
+    public @NonNull LocationResult validate() throws BadLocationException {
         long prevElapsedRealtimeNs = 0;
         final int size = mLocations.size();
         for (int i = 0; i < size; ++i) {
             Location location = mLocations.get(i);
-            if (!location.isComplete()) {
-                throw new IllegalArgumentException(
-                        "incomplete location at index " + i + ": " + mLocations);
-            }
-            if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
-                throw new IllegalArgumentException(
-                        "incorrectly ordered location at index " + i + ": " + mLocations);
+            if (Flags.locationValidation()) {
+                if (location.getLatitude() < -90.0
+                        || location.getLatitude() > 90.0
+                        || location.getLongitude() < -180.0
+                        || location.getLongitude() > 180.0
+                        || Double.isNaN(location.getLatitude())
+                        || Double.isNaN(location.getLongitude())) {
+                    throw new BadLocationException("location must have valid lat/lng");
+                }
+                if (!location.hasAccuracy()) {
+                    throw new BadLocationException("location must have accuracy");
+                }
+                if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+                    throw new BadLocationException("location must have reasonable accuracy");
+                }
+                if (location.getTime() < 0) {
+                    throw new BadLocationException("location must have valid time");
+                }
+                if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+                    throw new BadLocationException(
+                            "location must have valid monotonically increasing realtime");
+                }
+                if (location.getElapsedRealtimeNanos()
+                        > SystemClock.elapsedRealtimeNanos()) {
+                    throw new BadLocationException("location must not have realtime in the future");
+                }
+                if (!location.isMock()) {
+                    if (location.getProvider() == null) {
+                        throw new BadLocationException("location must have valid provider");
+                    }
+                    if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                        throw new BadLocationException("location must not be at 0,0");
+                    }
+                }
+
+                if (location.hasSpeed() && (location.getSpeed() < 0
+                        || location.getSpeed() > MAX_SPEED_MPS)) {
+                    Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+                    location.removeSpeed();
+                }
+            } else {
+                if (!location.isComplete()) {
+                    throw new IllegalArgumentException(
+                            "incomplete location at index " + i + ": " + mLocations);
+                }
+                if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+                    throw new IllegalArgumentException(
+                            "incorrectly ordered location at index " + i + ": " + mLocations);
+                }
             }
             prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
         }
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index f4b1056..a8464d3 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -27,3 +27,10 @@
     description: "Flag for releasing SUPL connection on timeout"
     bug: "315024652"
 }
+
+flag {
+    name: "location_validation"
+    namespace: "location"
+    description: "Flag for location validation"
+    bug: "314328533"
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3da52cc..7f95886 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -77,3 +77,9 @@
      bug: "279555229"
 }
 
+flag {
+    name: "enable_notifying_activity_manager_with_media_session_status_change"
+    namespace: "media_solutions"
+    description: "Notify ActivityManager with the changes in playback state of the media session."
+    bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6..60497fe 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@
         return false;
     }
 
+    /**
+     * Returns whether the service holding the media session should run in the foreground when the
+     * media session has this playback state or not.
+     *
+     * @hide
+     */
+    public boolean shouldAllowServiceToRunInForeground() {
+        switch (mState) {
+            case PlaybackState.STATE_PLAYING:
+            case PlaybackState.STATE_FAST_FORWARDING:
+            case PlaybackState.STATE_REWINDING:
+            case PlaybackState.STATE_BUFFERING:
+            case PlaybackState.STATE_CONNECTING:
+            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+            case PlaybackState.STATE_SKIPPING_TO_NEXT:
+            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
             new Parcelable.Creator<PlaybackState>() {
         @Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 96e95fd..3fcb871 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@
                                            mpuSequenceNumber, isPesPrivateData, sc,
                                            audioDescriptor.get(), presentationsJObj.get()));
 
+    // Protect mFilterClient from being set to null.
+    android::Mutex::Autolock autoLock(mLock);
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
         (dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@
             }
         }
     }
-    ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
-    if (!env->IsSameObject(filter.get(), nullptr)) {
-        jmethodID methodID = gFields.onFilterEventID;
-        if (mSharedFilter) {
-            methodID = gFields.onSharedFilterEventID;
+
+    ScopedLocalRef<jobject> filter(env);
+    {
+        android::Mutex::Autolock autoLock(mLock);
+        if (env->IsSameObject(mFilterObj, nullptr)) {
+            ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+                  "Filter object has been freed. Ignoring callback.");
+            return;
+        } else {
+            filter.reset(env->NewLocalRef(mFilterObj));
         }
-        env->CallVoidMethod(filter.get(), methodID, array.get());
-    } else {
-        ALOGE("FilterClientCallbackImpl::onFilterEvent:"
-              "Filter object has been freed. Ignoring callback.");
     }
+
+    jmethodID methodID = gFields.onFilterEventID;
+    if (mSharedFilter) {
+        methodID = gFields.onSharedFilterEventID;
+    }
+    env->CallVoidMethod(filter.get(), methodID, array.get());
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
     ALOGV("FilterClientCallbackImpl::onFilterStatus");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
-    if (!env->IsSameObject(filter.get(), nullptr)) {
-        jmethodID methodID = gFields.onFilterStatusID;
-        if (mSharedFilter) {
-            methodID = gFields.onSharedFilterStatusID;
+    ScopedLocalRef<jobject> filter(env);
+    {
+        android::Mutex::Autolock autoLock(mLock);
+        if (env->IsSameObject(filter.get(), nullptr)) {
+            ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+                  "Filter object has been freed. Ignoring callback.");
+            return;
+        } else {
+            filter.reset(env->NewLocalRef(mFilterObj));
         }
-        env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
-    } else {
-        ALOGE("FilterClientCallbackImpl::onFilterStatus:"
-              "Filter object has been freed. Ignoring callback.");
     }
+
+    jmethodID methodID = gFields.onFilterStatusID;
+    if (mSharedFilter) {
+        methodID = gFields.onSharedFilterStatusID;
+    }
+    env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
 }
 
 void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
     ALOGV("FilterClientCallbackImpl::setFilter");
     // Java Object
+    android::Mutex::Autolock autoLock(mLock);
     mFilterObj = filterObj;
     mFilterClient = filterClient;
     mSharedFilter = false;
@@ -979,6 +995,7 @@
 void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
     ALOGV("FilterClientCallbackImpl::setFilter");
     // Java Object
+    android::Mutex::Autolock autoLock(mLock);
     mFilterObj = filterObj;
     mFilterClient = filterClient;
     mSharedFilter = true;
@@ -1047,11 +1064,14 @@
 
 FilterClientCallbackImpl::~FilterClientCallbackImpl() {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    if (mFilterObj != nullptr) {
-        env->DeleteWeakGlobalRef(mFilterObj);
-        mFilterObj = nullptr;
+    {
+        android::Mutex::Autolock autoLock(mLock);
+        if (mFilterObj != nullptr) {
+            env->DeleteWeakGlobalRef(mFilterObj);
+            mFilterObj = nullptr;
+        }
+        mFilterClient = nullptr;
     }
-    mFilterClient = nullptr;
     env->DeleteGlobalRef(mEventClass);
     env->DeleteGlobalRef(mSectionEventClass);
     env->DeleteGlobalRef(mMediaEventClass);
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998d..3de3ab9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@
 private:
     jweak mFilterObj;
     sp<FilterClient> mFilterClient;
+    android::Mutex mLock;
     jclass mEventClass;
     jclass mSectionEventClass;
     jclass mMediaEventClass;
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb4..1ac5db6 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    style="@style/ScrollViewStyle">
+    style="@style/ScrollViewStyle"
+    android:importantForAccessibility="no">
 
     <LinearLayout
-        android:id="@+id/data_transfer_confirmation"
-        style="@style/ContainerLayout">
-
-        <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
-
-        <ImageView
-            android:id="@+id/header_icon"
-            android:layout_width="match_parent"
-            android:layout_height="32dp"
-            android:gravity="center"
-            android:layout_marginTop="18dp"
-            android:src="@drawable/ic_warning"
-            android:contentDescription="@null" />
-
-        <LinearLayout style="@style/Description">
-
-            <TextView
-                android:id="@+id/title"
-                style="@style/DescriptionTitle" />
-
-            <TextView
-                android:id="@+id/summary"
-                style="@style/DescriptionSummary" />
-
-        </LinearLayout>
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:baselineAligned="false"
+        android:importantForAccessibility="no">
 
         <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:orientation="vertical"
-            android:layout_marginTop="12dp"
-            android:layout_marginBottom="18dp">
+            android:id="@+id/data_transfer_confirmation"
+            style="@style/ContainerLayout">
 
-            <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+            <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
 
-            <Button
-                android:id="@+id/btn_positive"
-                style="@style/PositiveButton"
-                android:text="@string/consent_yes" />
+            <ImageView
+                android:id="@+id/header_icon"
+                android:layout_width="match_parent"
+                android:layout_height="32dp"
+                android:gravity="center"
+                android:layout_marginTop="18dp"
+                android:src="@drawable/ic_warning"
+                android:contentDescription="@null" />
 
-            <Button
-                android:id="@+id/btn_negative"
-                style="@style/NegativeButton"
-                android:text="@string/consent_no" />
+            <LinearLayout style="@style/Description">
+
+                <TextView
+                    android:id="@+id/title"
+                    style="@style/DescriptionTitle" />
+
+                <TextView
+                    android:id="@+id/summary"
+                    style="@style/DescriptionSummary" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:orientation="vertical"
+                android:layout_marginTop="12dp"
+                android:layout_marginBottom="18dp">
+
+                <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+                <Button
+                    android:id="@+id/btn_positive"
+                    style="@style/PositiveButton"
+                    android:text="@string/consent_yes" />
+
+                <Button
+                    android:id="@+id/btn_negative"
+                    style="@style/NegativeButton"
+                    android:text="@string/consent_no" />
+
+            </LinearLayout>
 
         </LinearLayout>
 
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11..6a4bb21 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@
     @UsesReflection({
             // As the runtime class name is used to generate the returned name, and the returned
             // name may be used used with reflection, generate the necessary keep rules.
-            @KeepTarget(classConstant = LocalTransport.class),
-            @KeepTarget(extendsClassConstant = LocalTransport.class)
+            @KeepTarget(instanceOfClassConstant = LocalTransport.class)
     })
     @Override
     public String name() {
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 25ad9b8..98a5a67 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -35,7 +35,10 @@
     name: "PackageInstaller",
     defaults: ["platform_app_defaults"],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     certificate: "platform",
     privileged: true,
@@ -62,7 +65,10 @@
     name: "PackageInstaller_tablet",
     defaults: ["platform_app_defaults"],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     certificate: "platform",
     privileged: true,
@@ -91,7 +97,10 @@
     name: "PackageInstaller_tv",
     defaults: ["platform_app_defaults"],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     certificate: "platform",
     privileged: true,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
deleted file mode 100644
index c8175ad..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
+++ /dev/null
@@ -1,912 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
-import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
-import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_DONE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.InstallSourceInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.ApplicationInfoFlags;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.InstallEventReceiver;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallReady;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import java.io.File;
-import java.io.IOException;
-
-public class InstallRepository {
-
-    public static final String EXTRA_STAGED_SESSION_ID =
-        "com.android.packageinstaller.extra.STAGED_SESSION_ID";
-    private static final String SCHEME_PACKAGE = "package";
-    private static final String BROADCAST_ACTION =
-        "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
-    private static final String TAG = InstallRepository.class.getSimpleName();
-    private final Context mContext;
-    private final PackageManager mPackageManager;
-    private final PackageInstaller mPackageInstaller;
-    private final UserManager mUserManager;
-    private final DevicePolicyManager mDevicePolicyManager;
-    private final AppOpsManager mAppOpsManager;
-    private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
-    private final MutableLiveData<InstallStage> mInstallResult = new MutableLiveData<>();
-    private final boolean mLocalLOGV = false;
-    private Intent mIntent;
-    private boolean mIsSessionInstall;
-    private boolean mIsTrustedSource;
-    /**
-     * Session ID for a session created when caller uses PackageInstaller APIs
-     */
-    private int mSessionId;
-    /**
-     * Session ID for a session created by this app
-     */
-    private int mStagedSessionId = SessionInfo.INVALID_ID;
-    private int mCallingUid;
-    private String mCallingPackage;
-    private SessionStager mSessionStager;
-    private AppOpRequestInfo mAppOpRequestInfo;
-    private AppSnippet mAppSnippet;
-    /**
-     * PackageInfo of the app being installed on device.
-     */
-    private PackageInfo mNewPackageInfo;
-
-    public InstallRepository(Context context) {
-        mContext = context;
-        mPackageManager = context.getPackageManager();
-        mPackageInstaller = mPackageManager.getPackageInstaller();
-        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
-        mUserManager = context.getSystemService(UserManager.class);
-        mAppOpsManager = context.getSystemService(AppOpsManager.class);
-    }
-
-    /**
-     * Extracts information from the incoming install intent, checks caller's permission to install
-     * packages, verifies that the caller is the install session owner (in case of a session based
-     * install) and checks if the current user has restrictions set that prevent app installation,
-     *
-     * @param intent the incoming {@link Intent} object for installing a package
-     * @param callerInfo {@link CallerInfo} that holds the callingUid and callingPackageName
-     * @return <p>{@link InstallAborted} if there are errors while performing the checks</p>
-     *     <p>{@link InstallStaging} after successfully performing the checks</p>
-     */
-    public InstallStage performPreInstallChecks(Intent intent, CallerInfo callerInfo) {
-        mIntent = intent;
-
-        String callingAttributionTag = null;
-
-        mIsSessionInstall =
-            PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
-                || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
-
-        mSessionId = mIsSessionInstall
-            ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
-            : SessionInfo.INVALID_ID;
-
-        mStagedSessionId = mIntent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID);
-
-        mCallingPackage = callerInfo.getPackageName();
-
-        if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) {
-            PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(mSessionId);
-            mCallingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
-            callingAttributionTag =
-                (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
-        }
-
-        // Uid of the source package, coming from ActivityManager
-        mCallingUid = callerInfo.getUid();
-        if (mCallingUid == Process.INVALID_UID) {
-            Log.e(TAG, "Could not determine the launching uid.");
-        }
-        final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
-        // Uid of the source package, with a preference to uid from ApplicationInfo
-        final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
-        mAppOpRequestInfo = new AppOpRequestInfo(
-            getPackageNameForUid(mContext, originatingUid, mCallingPackage),
-            originatingUid, callingAttributionTag);
-
-        if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
-            // Caller's identity could not be determined. Abort the install
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        if ((mSessionId != SessionInfo.INVALID_ID
-            && !isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId))
-            || (mStagedSessionId != SessionInfo.INVALID_ID
-            && !isCallerSessionOwner(mPackageInstaller, Process.myUid(), mStagedSessionId))) {
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        mIsTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, mIntent, originatingUid);
-
-        if (!isInstallPermissionGrantedOrRequested(mContext, mCallingUid, originatingUid,
-            mIsTrustedSource)) {
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        String restriction = getDevicePolicyRestrictions();
-        if (restriction != null) {
-            InstallAborted.Builder abortedBuilder =
-                new InstallAborted.Builder(ABORT_REASON_POLICY).setMessage(restriction);
-            final Intent adminSupportDetailsIntent =
-                mDevicePolicyManager.createAdminSupportIntent(restriction);
-            if (adminSupportDetailsIntent != null) {
-                abortedBuilder.setResultIntent(adminSupportDetailsIntent);
-            }
-            return abortedBuilder.build();
-        }
-
-        maybeRemoveInvalidInstallerPackageName(callerInfo);
-
-        return new InstallStaging();
-    }
-
-    /**
-     * @return the ApplicationInfo for the installation source (the calling package), if available
-     */
-    @Nullable
-    private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
-        if (callingPackage == null) {
-            return null;
-        }
-        try {
-            return mPackageManager.getApplicationInfo(callingPackage, 0);
-        } catch (PackageManager.NameNotFoundException ignored) {
-            return null;
-        }
-    }
-
-    private boolean isInstallRequestFromTrustedSource(ApplicationInfo sourceInfo, Intent intent,
-        int originatingUid) {
-        boolean isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
-        return sourceInfo != null && sourceInfo.isPrivilegedApp()
-            && (isNotUnknownSource
-            || isPermissionGranted(mContext, Manifest.permission.INSTALL_PACKAGES, originatingUid));
-    }
-
-    private String getDevicePolicyRestrictions() {
-        final String[] restrictions = new String[]{
-            UserManager.DISALLOW_INSTALL_APPS,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
-        };
-
-        for (String restriction : restrictions) {
-            if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
-                continue;
-            }
-            return restriction;
-        }
-        return null;
-    }
-
-    private void maybeRemoveInvalidInstallerPackageName(CallerInfo callerInfo) {
-        final String installerPackageNameFromIntent =
-            mIntent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
-        if (installerPackageNameFromIntent == null) {
-            return;
-        }
-        if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.getPackageName())
-            && !isPermissionGranted(mPackageManager, Manifest.permission.INSTALL_PACKAGES,
-            callerInfo.getPackageName())) {
-            Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
-                + " is invalid. Remove it.");
-            EventLog.writeEvent(0x534e4554, "236687884", callerInfo.getUid(),
-                "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
-            mIntent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
-        }
-    }
-
-    public void stageForInstall() {
-        Uri uri = mIntent.getData();
-        if (mStagedSessionId != SessionInfo.INVALID_ID
-            || mIsSessionInstall
-            || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) {
-            // For a session based install or installing with a package:// URI, there is no file
-            // for us to stage.
-            mStagingResult.setValue(new InstallReady());
-            return;
-        }
-        if (uri != null
-            && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
-            && canPackageQuery(mContext, mCallingUid, uri)) {
-
-            if (mStagedSessionId > 0) {
-                final PackageInstaller.SessionInfo info =
-                    mPackageInstaller.getSessionInfo(mStagedSessionId);
-                if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
-                    Log.w(TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
-                    if (info != null) {
-                        cleanupStagingSession();
-                    }
-                    mStagedSessionId = 0;
-                }
-            }
-
-            // Session does not exist, or became invalid.
-            if (mStagedSessionId <= 0) {
-                // Create session here to be able to show error.
-                try (final AssetFileDescriptor afd =
-                    mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) {
-                    ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
-                    PackageInstaller.SessionParams params =
-                        createSessionParams(mIntent, pfd, uri.toString());
-                    mStagedSessionId = mPackageInstaller.createSession(params);
-                } catch (IOException e) {
-                    Log.w(TAG, "Failed to create a staging session", e);
-                    mStagingResult.setValue(
-                        new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                            .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                                PackageManager.INSTALL_FAILED_INVALID_APK))
-                            .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                            .build());
-                    return;
-                }
-            }
-
-            SessionStageListener listener = new SessionStageListener() {
-                @Override
-                public void onStagingSuccess(SessionInfo info) {
-                    //TODO: Verify if the returned sessionInfo should be used anywhere
-                    mStagingResult.setValue(new InstallReady());
-                }
-
-                @Override
-                public void onStagingFailure() {
-                    cleanupStagingSession();
-                    mStagingResult.setValue(
-                        new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                            .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                                PackageManager.INSTALL_FAILED_INVALID_APK))
-                            .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                            .build());
-                }
-            };
-            if (mSessionStager != null) {
-                mSessionStager.cancel(true);
-            }
-            mSessionStager = new SessionStager(mContext, uri, mStagedSessionId, listener);
-            mSessionStager.execute();
-        }
-    }
-
-    public int getStagedSessionId() {
-        return mStagedSessionId;
-    }
-
-    private void cleanupStagingSession() {
-        if (mStagedSessionId > 0) {
-            try {
-                mPackageInstaller.abandonSession(mStagedSessionId);
-            } catch (SecurityException ignored) {
-            }
-            mStagedSessionId = 0;
-        }
-    }
-
-    private PackageInstaller.SessionParams createSessionParams(@NonNull Intent intent,
-        @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri.class);
-        params.setPackageSource(
-            referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
-                : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
-        params.setInstallAsInstantApp(false);
-        params.setReferrerUri(referrerUri);
-        params.setOriginatingUri(
-            intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri.class));
-        params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-            Process.INVALID_UID));
-        params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME));
-        params.setInstallReason(PackageManager.INSTALL_REASON_USER);
-        // Disable full screen intent usage by for sideloads.
-        params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
-            PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
-
-        if (pfd != null) {
-            try {
-                final PackageInstaller.InstallInfo result = mPackageInstaller.readInstallInfo(pfd,
-                    debugPathName, 0);
-                params.setAppPackageName(result.getPackageName());
-                params.setInstallLocation(result.getInstallLocation());
-                params.setSize(result.calculateInstalledSize(params, pfd));
-            } catch (PackageInstaller.PackageParsingException e) {
-                Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
-                params.setSize(pfd.getStatSize());
-            } catch (IOException e) {
-                Log.e(TAG,
-                    "Cannot calculate installed size " + debugPathName
-                        + ". Try only apk size.", e);
-            }
-        } else {
-            Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
-        }
-        return params;
-    }
-
-    /**
-     * Processes Install session, file:// or package:// URI to generate data pertaining to user
-     * confirmation for an install. This method also checks if the source app has the AppOp granted
-     * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
-     * be reused once appOp has been granted
-     *
-     * @return <ul>
-     *     <li>InstallAborted </li>
-     *         <ul>
-     *             <li> If install session is invalid (not sealed or resolvedBaseApk path
-     *             is invalid) </li>
-     *             <li> Source app doesn't have visibility to target app </li>
-     *             <li> The APK is invalid </li>
-     *             <li> URI is invalid </li>
-     *             <li> Can't get ApplicationInfo for source app, to request AppOp </li>
-     *         </ul>
-     *    <li> InstallUserActionRequired</li>
-     *         <ul>
-     *             <li> If AppOP is granted and user action is required to proceed
-     *             with install </li>
-     *             <li> If AppOp grant is to be requested from the user</li>
-     *         </ul>
-     *  </ul>
-     */
-    public InstallStage requestUserConfirmation() {
-        if (mIsTrustedSource) {
-            if (mLocalLOGV) {
-                Log.i(TAG, "install allowed");
-            }
-            // Returns InstallUserActionRequired stage if install details could be successfully
-            // computed, else it returns InstallAborted.
-            return generateConfirmationSnippet();
-        } else {
-            InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
-            if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
-                // Source app already has appOp granted.
-                return generateConfirmationSnippet();
-            } else {
-                return unknownSourceStage;
-            }
-        }
-    }
-
-
-    private InstallStage generateConfirmationSnippet() {
-        final Object packageSource;
-        int pendingUserActionReason = -1;
-        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(mIntent.getAction())) {
-            final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
-            String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
-
-            if (info == null || !info.isSealed() || resolvedPath == null) {
-                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-            }
-            packageSource = Uri.fromFile(new File(resolvedPath));
-            // TODO: Not sure where is this used yet. PIA.java passes it to
-            //  InstallInstalling if not null
-            // mOriginatingURI = null;
-            // mReferrerURI = null;
-            pendingUserActionReason = info.getPendingUserActionReason();
-        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(mIntent.getAction())) {
-            final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
-
-            if (info == null || !info.isPreApprovalRequested()) {
-                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-            }
-            packageSource = info;
-            // mOriginatingURI = null;
-            // mReferrerURI = null;
-            pendingUserActionReason = info.getPendingUserActionReason();
-        } else {
-            // Two possible origins:
-            // 1. Installation with SCHEME_PACKAGE.
-            // 2. Installation with "file://" for session created by this app
-            if (mIntent.getData() != null && mIntent.getData().getScheme().equals(SCHEME_PACKAGE)) {
-                packageSource = mIntent.getData();
-            } else {
-                SessionInfo stagedSessionInfo = mPackageInstaller.getSessionInfo(mStagedSessionId);
-                packageSource = Uri.fromFile(new File(stagedSessionInfo.getResolvedBaseApkPath()));
-            }
-            // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
-            // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
-            pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;
-        }
-
-        // if there's nothing to do, quietly slip into the ether
-        if (packageSource == null) {
-            Log.w(TAG, "Unspecified source");
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                    PackageManager.INSTALL_FAILED_INVALID_URI))
-                .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                .build();
-        }
-
-        return processAppSnippet(packageSource, pendingUserActionReason);
-    }
-
-    /**
-     * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
-     * session) to set up the installer for this install.
-     *
-     * @param source The source of package URI or SessionInfo
-     * @return {@code true} iff the installer could be set up
-     */
-    private InstallStage processAppSnippet(Object source, int userActionReason) {
-        if (source instanceof Uri) {
-            return processPackageUri((Uri) source, userActionReason);
-        } else if (source instanceof SessionInfo) {
-            return processSessionInfo((SessionInfo) source, userActionReason);
-        }
-        return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-    }
-
-    /**
-     * Parse the Uri and set up the installer for this package.
-     *
-     * @param packageUri The URI to parse
-     * @return {@code true} iff the installer could be set up
-     */
-    private InstallStage processPackageUri(final Uri packageUri, int userActionReason) {
-        final String scheme = packageUri.getScheme();
-        final String packageName = packageUri.getSchemeSpecificPart();
-
-        if (scheme == null) {
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-
-        if (mLocalLOGV) {
-            Log.i(TAG, "processPackageUri(): uri = " + packageUri + ", scheme = " + scheme);
-        }
-
-        switch (scheme) {
-            case SCHEME_PACKAGE -> {
-                for (UserHandle handle : mUserManager.getUserHandles(true)) {
-                    PackageManager pmForUser = mContext.createContextAsUser(handle, 0)
-                        .getPackageManager();
-                    try {
-                        if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
-                            mNewPackageInfo = pmForUser.getPackageInfo(packageName,
-                                PackageManager.GET_PERMISSIONS
-                                    | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                        }
-                    } catch (NameNotFoundException ignored) {
-                    }
-                }
-                if (mNewPackageInfo == null) {
-                    Log.w(TAG, "Requested package " + packageUri.getSchemeSpecificPart()
-                        + " not available. Discontinuing installation");
-                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                        .setErrorDialogType(DLG_PACKAGE_ERROR)
-                        .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                            PackageManager.INSTALL_FAILED_INVALID_APK))
-                        .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                        .build();
-                }
-                mAppSnippet = getAppSnippet(mContext, mNewPackageInfo);
-                if (mLocalLOGV) {
-                    Log.i(TAG, "Created snippet for " + mAppSnippet.getLabel());
-                }
-            }
-            case ContentResolver.SCHEME_FILE -> {
-                File sourceFile = new File(packageUri.getPath());
-                mNewPackageInfo = getPackageInfo(mContext, sourceFile,
-                    PackageManager.GET_PERMISSIONS);
-
-                // Check for parse errors
-                if (mNewPackageInfo == null) {
-                    Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
-                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                        .setErrorDialogType(DLG_PACKAGE_ERROR)
-                        .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
-                            PackageManager.INSTALL_FAILED_INVALID_APK))
-                        .setActivityResultCode(Activity.RESULT_FIRST_USER)
-                        .build();
-                }
-                if (mLocalLOGV) {
-                    Log.i(TAG, "Creating snippet for local file " + sourceFile);
-                }
-                mAppSnippet = getAppSnippet(mContext, mNewPackageInfo.applicationInfo, sourceFile);
-            }
-            default -> {
-                Log.e(TAG, "Unexpected URI scheme " + packageUri);
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-            }
-        }
-
-        return new InstallUserActionRequired.Builder(
-            USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
-            .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
-            .setAppUpdating(isAppUpdating(mNewPackageInfo))
-            .build();
-    }
-
-    /**
-     * Use the SessionInfo and set up the installer for pre-commit install session.
-     *
-     * @param sessionInfo The SessionInfo to compose
-     * @return {@code true} iff the installer could be set up
-     */
-    private InstallStage processSessionInfo(@NonNull SessionInfo sessionInfo,
-        int userActionReason) {
-        mNewPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName());
-
-        mAppSnippet = getAppSnippet(mContext, sessionInfo);
-        return new InstallUserActionRequired.Builder(
-            USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
-            .setAppUpdating(isAppUpdating(mNewPackageInfo))
-            .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
-            .build();
-    }
-
-    private String getUpdateMessage(PackageInfo pkgInfo, int userActionReason) {
-        if (isAppUpdating(pkgInfo)) {
-            final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo);
-            final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
-
-            if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
-                && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
-                return mContext.getString(R.string.install_confirm_question_update_owner_reminder,
-                    requestedUpdateOwnerLabel, existingUpdateOwnerLabel);
-            }
-        }
-        return null;
-    }
-
-    private CharSequence getExistingUpdateOwnerLabel(PackageInfo pkgInfo) {
-        try {
-            final String packageName = pkgInfo.packageName;
-            final InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName);
-            final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
-            return getApplicationLabel(existingUpdateOwner);
-        } catch (NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    private CharSequence getApplicationLabel(String packageName) {
-        try {
-            final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
-                ApplicationInfoFlags.of(0));
-            return mPackageManager.getApplicationLabel(appInfo);
-        } catch (NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    private boolean isAppUpdating(PackageInfo newPkgInfo) {
-        String pkgName = newPkgInfo.packageName;
-        // Check if there is already a package on the device with this name
-        // but it has been renamed to something else.
-        String[] oldName = mPackageManager.canonicalToCurrentPackageNames(new String[]{pkgName});
-        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
-            pkgName = oldName[0];
-            newPkgInfo.packageName = pkgName;
-            newPkgInfo.applicationInfo.packageName = pkgName;
-        }
-        // Check if package is already installed. display confirmation dialog if replacing pkg
-        try {
-            // This is a little convoluted because we want to get all uninstalled
-            // apps, but this may include apps with just data, and if it is just
-            // data we still want to count it as "installed".
-            ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkgName,
-                PackageManager.MATCH_UNINSTALLED_PACKAGES);
-            if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                return false;
-            }
-        } catch (NameNotFoundException e) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Once the user returns from Settings related to installing from unknown sources, reattempt
-     * the installation if the source app is granted permission to install other apps. Abort the
-     * installation if the source app is still not granted installing permission.
-     * @return {@link InstallUserActionRequired} containing data required to ask user confirmation
-     * to proceed with the install.
-     * {@link InstallAborted} if there was an error while recomputing, or the source still
-     * doesn't have install permission.
-     */
-    public InstallStage reattemptInstall() {
-        InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
-        if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
-            // Source app now has appOp granted.
-            return generateConfirmationSnippet();
-        } else if (unknownSourceStage.getStageCode() == InstallStage.STAGE_ABORTED) {
-            // There was some error in determining the AppOp code for the source app.
-            // Abort installation
-            return unknownSourceStage;
-        } else {
-            // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
-            // unexpected while reattempting the install. Let's abort it.
-            Log.e(TAG, "AppOp still not granted.");
-            return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-    }
-
-    private InstallStage handleUnknownSources(AppOpRequestInfo requestInfo) {
-        if (requestInfo.getCallingPackage() == null) {
-            Log.i(TAG, "No source found for package " + mNewPackageInfo.packageName);
-            return new InstallUserActionRequired.Builder(
-                USER_ACTION_REASON_ANONYMOUS_SOURCE, null)
-                .build();
-        }
-        // Shouldn't use static constant directly, see b/65534401.
-        final String appOpStr =
-            AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
-        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr,
-            requestInfo.getOriginatingUid(),
-            requestInfo.getCallingPackage(), requestInfo.getAttributionTag(),
-            "Started package installation activity");
-
-        if (mLocalLOGV) {
-            Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
-        }
-        switch (appOpMode) {
-            case AppOpsManager.MODE_DEFAULT:
-                mAppOpsManager.setMode(appOpStr, requestInfo.getOriginatingUid(),
-                    requestInfo.getCallingPackage(), AppOpsManager.MODE_ERRORED);
-                // fall through
-            case AppOpsManager.MODE_ERRORED:
-                try {
-                    ApplicationInfo sourceInfo =
-                        mPackageManager.getApplicationInfo(requestInfo.getCallingPackage(), 0);
-                    AppSnippet sourceAppSnippet = getAppSnippet(mContext, sourceInfo);
-                    return new InstallUserActionRequired.Builder(
-                        USER_ACTION_REASON_UNKNOWN_SOURCE, sourceAppSnippet)
-                        .setDialogMessage(requestInfo.getCallingPackage())
-                        .build();
-                } catch (NameNotFoundException e) {
-                    Log.e(TAG, "Did not find appInfo for " + requestInfo.getCallingPackage());
-                    return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-                }
-            case AppOpsManager.MODE_ALLOWED:
-                return new InstallReady();
-            default:
-                Log.e(TAG, "Invalid app op mode " + appOpMode
-                    + " for OP_REQUEST_INSTALL_PACKAGES found for uid "
-                    + requestInfo.getOriginatingUid());
-                return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
-        }
-    }
-
-
-    /**
-     * Kick off the installation. Register a broadcast listener to get the result of the
-     * installation and commit the staged session here. If the installation was session based,
-     * signal the PackageInstaller that the user has granted permission to proceed with the install
-     */
-    public void initiateInstall() {
-        if (mSessionId > 0) {
-            mPackageInstaller.setPermissionsResult(mSessionId, true);
-            mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_DONE)
-                .setActivityResultCode(Activity.RESULT_OK).build());
-            return;
-        }
-
-        Uri uri = mIntent.getData();
-        if (uri != null && SCHEME_PACKAGE.equals(uri.getScheme())) {
-            try {
-                mPackageManager.installExistingPackage(mNewPackageInfo.packageName);
-                setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null, -1);
-            } catch (PackageManager.NameNotFoundException e) {
-                setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
-                    PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
-            }
-            return;
-        }
-
-        if (mStagedSessionId <= 0) {
-            // How did we even land here?
-            Log.e(TAG, "Invalid local session and caller initiated session");
-            mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
-                .build());
-            return;
-        }
-
-        int installId;
-        try {
-            mInstallResult.setValue(new InstallInstalling(mAppSnippet));
-            installId = InstallEventReceiver.addObserver(mContext,
-                EventResultPersister.GENERATE_NEW_ID, this::setStageBasedOnResult);
-        } catch (EventResultPersister.OutOfIdsException e) {
-            setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
-            return;
-        }
-
-        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
-        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        broadcastIntent.setPackage(mContext.getPackageName());
-        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId);
-
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(
-            mContext, installId, broadcastIntent,
-            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
-        try {
-            PackageInstaller.Session session = mPackageInstaller.openSession(mStagedSessionId);
-            session.commit(pendingIntent.getIntentSender());
-        } catch (Exception e) {
-            Log.e(TAG, "Session " + mStagedSessionId + " could not be opened.", e);
-            mPackageInstaller.abandonSession(mStagedSessionId);
-            setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
-        }
-    }
-
-    private void setStageBasedOnResult(int statusCode, int legacyStatus, String message,
-        int serviceId) {
-        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
-            boolean shouldReturnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-
-            InstallSuccess.Builder successBuilder = new InstallSuccess.Builder(mAppSnippet)
-                .setShouldReturnResult(shouldReturnResult);
-            Intent resultIntent;
-            if (shouldReturnResult) {
-                resultIntent = new Intent()
-                    .putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
-            } else {
-                resultIntent = mPackageManager
-                    .getLaunchIntentForPackage(mNewPackageInfo.packageName);
-            }
-            successBuilder.setResultIntent(resultIntent);
-
-            mInstallResult.setValue(successBuilder.build());
-        } else {
-            mInstallResult.setValue(
-                new InstallFailed(mAppSnippet, statusCode, legacyStatus, message));
-        }
-    }
-
-    public MutableLiveData<InstallStage> getInstallResult() {
-        return mInstallResult;
-    }
-
-    /**
-     * Cleanup the staged session. Also signal the packageinstaller that an install session is to
-     * be aborted
-     */
-    public void cleanupInstall() {
-        if (mSessionId > 0) {
-            mPackageInstaller.setPermissionsResult(mSessionId, false);
-        } else if (mStagedSessionId > 0) {
-            cleanupStagingSession();
-        }
-    }
-
-    /**
-     * When the identity of the install source could not be determined, user can skip checking the
-     * source and directly proceed with the install.
-     */
-    public InstallStage forcedSkipSourceCheck() {
-        return generateConfirmationSnippet();
-    }
-
-    public MutableLiveData<Integer> getStagingProgress() {
-        if (mSessionStager != null) {
-            return mSessionStager.getProgress();
-        }
-        return new MutableLiveData<>(0);
-    }
-
-    public MutableLiveData<InstallStage> getStagingResult() {
-        return mStagingResult;
-    }
-
-    public interface SessionStageListener {
-
-        void onStagingSuccess(SessionInfo info);
-
-        void onStagingFailure();
-    }
-
-    public static class CallerInfo {
-
-        private final String mPackageName;
-        private final int mUid;
-
-        public CallerInfo(String packageName, int uid) {
-            mPackageName = packageName;
-            mUid = uid;
-        }
-
-        public String getPackageName() {
-            return mPackageName;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-    }
-
-    public static class AppOpRequestInfo {
-
-        private String mCallingPackage;
-        private String mAttributionTag;
-        private int mOrginatingUid;
-
-        public AppOpRequestInfo(String callingPackage, int orginatingUid, String attributionTag) {
-            mCallingPackage = callingPackage;
-            mOrginatingUid = orginatingUid;
-            mAttributionTag = attributionTag;
-        }
-
-        public String getCallingPackage() {
-            return mCallingPackage;
-        }
-
-        public String getAttributionTag() {
-            return mAttributionTag;
-        }
-
-        public int getOriginatingUid() {
-            return mOrginatingUid;
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
new file mode 100644
index 0000000..326e533
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -0,0 +1,867 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionInfo
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import android.os.Process
+import android.os.UserManager
+import android.text.TextUtils
+import android.util.EventLog
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.InstallEventReceiver
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_UNKNOWN_SOURCE
+import com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery
+import com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner
+import com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import java.io.File
+import java.io.IOException
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+class InstallRepository(private val context: Context) {
+
+    private val packageManager: PackageManager = context.packageManager
+    private val packageInstaller: PackageInstaller = packageManager.packageInstaller
+    private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+    private val devicePolicyManager: DevicePolicyManager? =
+        context.getSystemService(DevicePolicyManager::class.java)
+    private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+    private val localLOGV = false
+    private var isSessionInstall = false
+    private var isTrustedSource = false
+    private val _stagingResult = MutableLiveData<InstallStage>()
+    val stagingResult: LiveData<InstallStage>
+        get() = _stagingResult
+    private val _installResult = MutableLiveData<InstallStage>()
+    val installResult: LiveData<InstallStage>
+        get() = _installResult
+
+    /**
+     * Session ID for a session created when caller uses PackageInstaller APIs
+     */
+    private var sessionId = SessionInfo.INVALID_ID
+
+    /**
+     * Session ID for a session created by this app
+     */
+    var stagedSessionId = SessionInfo.INVALID_ID
+        private set
+    private var callingUid = Process.INVALID_UID
+    private var callingPackage: String? = null
+    private var sessionStager: SessionStager? = null
+    private lateinit var intent: Intent
+    private lateinit var appOpRequestInfo: AppOpRequestInfo
+    private lateinit var appSnippet: PackageUtil.AppSnippet
+
+    /**
+     * PackageInfo of the app being installed on device.
+     */
+    private var newPackageInfo: PackageInfo? = null
+
+    /**
+     * Extracts information from the incoming install intent, checks caller's permission to install
+     * packages, verifies that the caller is the install session owner (in case of a session based
+     * install) and checks if the current user has restrictions set that prevent app installation,
+     *
+     * @param intent the incoming [Intent] object for installing a package
+     * @param callerInfo [CallerInfo] that holds the callingUid and callingPackageName
+     * @return
+     *  * [InstallAborted] if there are errors while performing the checks
+     *  * [InstallStaging] after successfully performing the checks
+     */
+    fun performPreInstallChecks(intent: Intent, callerInfo: CallerInfo): InstallStage {
+        this.intent = intent
+
+        var callingAttributionTag: String? = null
+
+        isSessionInstall =
+            PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action
+                || PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action
+
+        sessionId = if (isSessionInstall)
+            intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
+        else SessionInfo.INVALID_ID
+
+        stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
+
+        callingPackage = callerInfo.packageName
+
+        if (callingPackage == null && sessionId != SessionInfo.INVALID_ID) {
+            val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId)
+            callingPackage = sessionInfo?.getInstallerPackageName()
+            callingAttributionTag = sessionInfo?.getInstallerAttributionTag()
+        }
+
+        // Uid of the source package, coming from ActivityManager
+        callingUid = callerInfo.uid
+        if (callingUid == Process.INVALID_UID) {
+            Log.e(LOG_TAG, "Could not determine the launching uid.")
+        }
+        val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
+        // Uid of the source package, with a preference to uid from ApplicationInfo
+        val originatingUid = sourceInfo?.uid ?: callingUid
+        appOpRequestInfo = AppOpRequestInfo(
+            getPackageNameForUid(context, originatingUid, callingPackage),
+            originatingUid, callingAttributionTag
+        )
+
+        if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+            // Caller's identity could not be determined. Abort the install
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+
+        if ((sessionId != SessionInfo.INVALID_ID
+                && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+            || (stagedSessionId != SessionInfo.INVALID_ID
+                && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
+        ) {
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+
+        isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
+        if (!isInstallPermissionGrantedOrRequested(
+                context, callingUid, originatingUid, isTrustedSource
+            )
+        ) {
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+
+        val restriction = getDevicePolicyRestrictions()
+        if (restriction != null) {
+            val adminSupportDetailsIntent =
+                devicePolicyManager!!.createAdminSupportIntent(restriction)
+            return InstallAborted(
+                ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
+            )
+        }
+
+        maybeRemoveInvalidInstallerPackageName(callerInfo)
+
+        return InstallStaging()
+    }
+
+    /**
+     * @return the ApplicationInfo for the installation source (the calling package), if available
+     */
+    private fun getSourceInfo(callingPackage: String?): ApplicationInfo? {
+        return try {
+            callingPackage?.let { packageManager.getApplicationInfo(it, 0) }
+        } catch (ignored: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
+
+    private fun isInstallRequestFromTrustedSource(
+        sourceInfo: ApplicationInfo?,
+        intent: Intent,
+        originatingUid: Int,
+    ): Boolean {
+        val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
+        return (sourceInfo != null && sourceInfo.isPrivilegedApp
+            && (isNotUnknownSource
+            || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+    }
+
+    private fun getDevicePolicyRestrictions(): String? {
+        val restrictions = arrayOf(
+            UserManager.DISALLOW_INSTALL_APPS,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+        )
+        for (restriction in restrictions) {
+            if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+                continue
+            }
+            return restriction
+        }
+        return null
+    }
+
+    private fun maybeRemoveInvalidInstallerPackageName(callerInfo: CallerInfo) {
+        val installerPackageNameFromIntent =
+            intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME) ?: return
+
+        if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.packageName)
+            && callerInfo.packageName != null
+            && isPermissionGranted(
+                packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName
+            )
+        ) {
+            Log.e(
+                LOG_TAG, "The given installer package name $installerPackageNameFromIntent"
+                    + " is invalid. Remove it."
+            )
+            EventLog.writeEvent(
+                0x534e4554, "236687884", callerInfo.uid,
+                "Invalid EXTRA_INSTALLER_PACKAGE_NAME"
+            )
+            intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME)
+        }
+    }
+
+    @OptIn(DelicateCoroutinesApi::class)
+    fun stageForInstall() {
+        val uri = intent.data
+        if (stagedSessionId != SessionInfo.INVALID_ID
+            || isSessionInstall
+            || (uri != null && SCHEME_PACKAGE == uri.scheme)
+        ) {
+            // For a session based install or installing with a package:// URI, there is no file
+            // for us to stage.
+            _stagingResult.value = InstallReady()
+            return
+        }
+        if (uri != null
+            && ContentResolver.SCHEME_CONTENT == uri.scheme
+            && canPackageQuery(context, callingUid, uri)
+        ) {
+            if (stagedSessionId > 0) {
+                val info: SessionInfo? = packageInstaller.getSessionInfo(stagedSessionId)
+                if (info == null || !info.isActive || info.resolvedBaseApkPath == null) {
+                    Log.w(LOG_TAG, "Session $stagedSessionId in funky state; ignoring")
+                    if (info != null) {
+                        cleanupStagingSession()
+                    }
+                    stagedSessionId = 0
+                }
+            }
+
+            // Session does not exist, or became invalid.
+            if (stagedSessionId <= 0) {
+                // Create session here to be able to show error.
+                try {
+                    context.contentResolver.openAssetFileDescriptor(uri, "r").use { afd ->
+                        val pfd: ParcelFileDescriptor? = afd?.parcelFileDescriptor
+                        val params: SessionParams =
+                            createSessionParams(intent, pfd, uri.toString())
+                        stagedSessionId = packageInstaller.createSession(params)
+                    }
+                } catch (e: IOException) {
+                    Log.w(LOG_TAG, "Failed to create a staging session", e)
+                    _stagingResult.value = InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                    return
+                }
+            }
+
+            sessionStager = SessionStager(context, uri, stagedSessionId)
+            GlobalScope.launch(Dispatchers.Main) {
+                val wasFileStaged = sessionStager!!.execute()
+
+                if (wasFileStaged) {
+                    _stagingResult.value = InstallReady()
+                } else {
+                    cleanupStagingSession()
+                    _stagingResult.value = InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                }
+            }
+        }
+    }
+
+    private fun cleanupStagingSession() {
+        if (stagedSessionId > 0) {
+            try {
+                packageInstaller.abandonSession(stagedSessionId)
+            } catch (ignored: SecurityException) {
+            }
+            stagedSessionId = 0
+        }
+    }
+
+    private fun createSessionParams(
+        intent: Intent,
+        pfd: ParcelFileDescriptor?,
+        debugPathName: String,
+    ): SessionParams {
+        val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
+        val referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
+        params.setPackageSource(
+            if (referrerUri != null)
+                PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+            else PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+        )
+        params.setInstallAsInstantApp(false)
+        params.setReferrerUri(referrerUri)
+        params.setOriginatingUri(
+            intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri::class.java)
+        )
+        params.setOriginatingUid(
+            intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, Process.INVALID_UID)
+        )
+        params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME))
+        params.setInstallReason(PackageManager.INSTALL_REASON_USER)
+        // Disable full screen intent usage by for sideloads.
+        params.setPermissionState(
+            Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
+        )
+        if (pfd != null) {
+            try {
+                val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
+                params.setAppPackageName(installInfo.packageName)
+                params.setInstallLocation(installInfo.installLocation)
+                params.setSize(installInfo.calculateInstalledSize(params, pfd))
+            } catch (e: PackageInstaller.PackageParsingException) {
+                Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
+                params.setSize(pfd.statSize)
+            } catch (e: IOException) {
+                Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+                    "Try only apk size.", e
+                )
+            }
+        } else {
+            Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.")
+        }
+        return params
+    }
+
+    /**
+     * Processes Install session, file:// or package:// URI to generate data pertaining to user
+     * confirmation for an install. This method also checks if the source app has the AppOp granted
+     * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
+     * be reused once appOp has been granted
+     *
+     * @return
+     *  * [InstallAborted]
+     *      *  If install session is invalid (not sealed or resolvedBaseApk path is invalid)
+     *      *  Source app doesn't have visibility to target app
+     *      *  The APK is invalid
+     *      *  URI is invalid
+     *      *  Can't get ApplicationInfo for source app, to request AppOp
+     *
+     *  *  [InstallUserActionRequired]
+     *      * If AppOP is granted and user action is required to proceed with install
+     *      * If AppOp grant is to be requested from the user
+     */
+    fun requestUserConfirmation(): InstallStage {
+        return if (isTrustedSource) {
+            if (localLOGV) {
+                Log.i(LOG_TAG, "install allowed")
+            }
+            // Returns InstallUserActionRequired stage if install details could be successfully
+            // computed, else it returns InstallAborted.
+            generateConfirmationSnippet()
+        } else {
+            val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+            if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) {
+                // Source app already has appOp granted.
+                generateConfirmationSnippet()
+            } else {
+                unknownSourceStage
+            }
+        }
+    }
+
+    private fun generateConfirmationSnippet(): InstallStage {
+        val packageSource: Any?
+        val pendingUserActionReason: Int
+
+        if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
+            val info = packageInstaller.getSessionInfo(sessionId)
+            val resolvedPath = info?.resolvedBaseApkPath
+            if (info == null || !info.isSealed || resolvedPath == null) {
+                Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+                return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+            packageSource = Uri.fromFile(File(resolvedPath))
+            // TODO: Not sure where is this used yet. PIA.java passes it to
+            //  InstallInstalling if not null
+            // mOriginatingURI = null;
+            // mReferrerURI = null;
+            pendingUserActionReason = info.getPendingUserActionReason()
+        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action) {
+            val info = packageInstaller.getSessionInfo(sessionId)
+            if (info == null || !info.isPreApprovalRequested) {
+                Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+                return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+            packageSource = info
+            // mOriginatingURI = null;
+            // mReferrerURI = null;
+            pendingUserActionReason = info.getPendingUserActionReason()
+        } else {
+            // Two possible origins:
+            // 1. Installation with SCHEME_PACKAGE.
+            // 2. Installation with "file://" for session created by this app
+            packageSource =
+                if (intent.data?.scheme == SCHEME_PACKAGE) {
+                    intent.data
+                } else {
+                    val stagedSessionInfo = packageInstaller.getSessionInfo(stagedSessionId)
+                    Uri.fromFile(File(stagedSessionInfo?.resolvedBaseApkPath!!))
+                }
+            // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
+            // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
+            pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE
+        }
+
+        // if there's nothing to do, quietly slip into the ether
+        if (packageSource == null) {
+            Log.w(LOG_TAG, "Unspecified source")
+            return InstallAborted(
+                ABORT_REASON_INTERNAL_ERROR,
+                resultIntent = Intent().putExtra(
+                    Intent.EXTRA_INSTALL_RESULT,
+                    PackageManager.INSTALL_FAILED_INVALID_URI
+                ),
+                activityResultCode = Activity.RESULT_FIRST_USER
+            )
+        }
+        return processAppSnippet(packageSource, pendingUserActionReason)
+    }
+
+    /**
+     * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
+     * session) to set up the installer for this install.
+     *
+     * @param source The source of package URI or SessionInfo
+     * @return
+     *  * [InstallUserActionRequired] if source could be processed
+     *  * [InstallAborted] if source is invalid or there was an error is processing a source
+     */
+    private fun processAppSnippet(source: Any, userActionReason: Int): InstallStage {
+        return when (source) {
+            is Uri -> processPackageUri(source, userActionReason)
+            is SessionInfo -> processSessionInfo(source, userActionReason)
+            else -> InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+    }
+
+    /**
+     * Parse the Uri and set up the installer for this package.
+     *
+     * @param packageUri The URI to parse
+     * @return
+     *  * [InstallUserActionRequired] if source could be processed
+     *  * [InstallAborted] if source is invalid or there was an error is processing a source
+     */
+    private fun processPackageUri(packageUri: Uri, userActionReason: Int): InstallStage {
+        val scheme = packageUri.scheme
+        val packageName = packageUri.schemeSpecificPart
+        if (scheme == null) {
+            return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+        }
+        if (localLOGV) {
+            Log.i(LOG_TAG, "processPackageUri(): uri = $packageUri, scheme = $scheme")
+        }
+        when (scheme) {
+            SCHEME_PACKAGE -> {
+                for (handle in userManager!!.getUserHandles(true)) {
+                    val pmForUser = context.createContextAsUser(handle, 0).packageManager
+                    try {
+                        if (pmForUser.canPackageQuery(callingPackage!!, packageName)) {
+                            newPackageInfo = pmForUser.getPackageInfo(
+                                packageName,
+                                PackageManager.GET_PERMISSIONS
+                                    or PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            )
+                        }
+                    } catch (ignored: PackageManager.NameNotFoundException) {
+                    }
+                }
+                if (newPackageInfo == null) {
+                    Log.w(
+                        LOG_TAG, "Requested package " + packageUri.schemeSpecificPart
+                            + " not available. Discontinuing installation"
+                    )
+                    return InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        errorDialogType = DLG_PACKAGE_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                }
+                appSnippet = getAppSnippet(context, newPackageInfo!!)
+                if (localLOGV) {
+                    Log.i(LOG_TAG, "Created snippet for " + appSnippet.label)
+                }
+            }
+
+            ContentResolver.SCHEME_FILE -> {
+                val sourceFile = packageUri.path?.let { File(it) }
+                newPackageInfo = sourceFile?.let {
+                    getPackageInfo(context, it, PackageManager.GET_PERMISSIONS)
+                }
+
+                // Check for parse errors
+                if (newPackageInfo == null) {
+                    Log.w(
+                        LOG_TAG, "Parse error when parsing manifest. " +
+                            "Discontinuing installation"
+                    )
+                    return InstallAborted(
+                        ABORT_REASON_INTERNAL_ERROR,
+                        errorDialogType = DLG_PACKAGE_ERROR,
+                        resultIntent = Intent().putExtra(
+                            Intent.EXTRA_INSTALL_RESULT,
+                            PackageManager.INSTALL_FAILED_INVALID_APK
+                        ),
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                }
+                if (localLOGV) {
+                    Log.i(LOG_TAG, "Creating snippet for local file $sourceFile")
+                }
+                appSnippet = getAppSnippet(context, newPackageInfo!!, sourceFile!!)
+            }
+
+            else -> {
+                Log.e(LOG_TAG, "Unexpected URI scheme $packageUri")
+                return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+        }
+        return InstallUserActionRequired(
+            USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+            getUpdateMessage(newPackageInfo!!, userActionReason)
+        )
+    }
+
+    /**
+     * Use the SessionInfo and set up the installer for pre-commit install session.
+     *
+     * @param sessionInfo The SessionInfo to compose
+     * @return
+     *  * [InstallUserActionRequired] if source could be processed
+     *  * [InstallAborted] if source is invalid or there was an error is processing a source
+     */
+    private fun processSessionInfo(sessionInfo: SessionInfo, userActionReason: Int): InstallStage {
+        newPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName())
+        appSnippet = getAppSnippet(context, sessionInfo)
+
+        return InstallUserActionRequired(
+            USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+            getUpdateMessage(newPackageInfo!!, userActionReason)
+
+        )
+    }
+
+    private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? {
+        if (isAppUpdating(pkgInfo)) {
+            val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
+            val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage)
+            if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
+                && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
+            ) {
+                return context.getString(
+                    R.string.install_confirm_question_update_owner_reminder,
+                    requestedUpdateOwnerLabel, existingUpdateOwnerLabel
+                )
+            }
+        }
+        return null
+    }
+
+    private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? {
+        return try {
+            val packageName = pkgInfo.packageName
+            val sourceInfo = packageManager.getInstallSourceInfo(packageName)
+            val existingUpdateOwner = sourceInfo.updateOwnerPackageName
+            getApplicationLabel(existingUpdateOwner)
+        } catch (e: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
+
+    private fun getApplicationLabel(packageName: String?): CharSequence? {
+        return try {
+            val appInfo = packageName?.let {
+                packageManager.getApplicationInfo(
+                    it, PackageManager.ApplicationInfoFlags.of(0)
+                )
+            }
+            appInfo?.let { packageManager.getApplicationLabel(it) }
+        } catch (e: PackageManager.NameNotFoundException) {
+            null
+        }
+    }
+
+    private fun isAppUpdating(newPkgInfo: PackageInfo): Boolean {
+        var pkgName = newPkgInfo.packageName
+        // Check if there is already a package on the device with this name
+        // but it has been renamed to something else.
+        val oldName = packageManager.canonicalToCurrentPackageNames(arrayOf(pkgName))
+        if (oldName != null && oldName.isNotEmpty() && oldName[0] != null) {
+            pkgName = oldName[0]
+            newPkgInfo.packageName = pkgName
+            newPkgInfo.applicationInfo?.packageName = pkgName
+        }
+
+        // Check if package is already installed. display confirmation dialog if replacing pkg
+        try {
+            // This is a little convoluted because we want to get all uninstalled
+            // apps, but this may include apps with just data, and if it is just
+            // data we still want to count it as "installed".
+            val appInfo = packageManager.getApplicationInfo(
+                pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES
+            )
+            if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
+                return false
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            return false
+        }
+        return true
+    }
+
+    /**
+     * Once the user returns from Settings related to installing from unknown sources, reattempt
+     * the installation if the source app is granted permission to install other apps. Abort the
+     * installation if the source app is still not granted installing permission.
+     *
+     * @return
+     * * [InstallUserActionRequired] containing data required to ask user confirmation
+     * to proceed with the install.
+     * * [InstallAborted] if there was an error while recomputing, or the source still
+     * doesn't have install permission.
+     */
+    fun reattemptInstall(): InstallStage {
+        val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+        return when (unknownSourceStage.stageCode) {
+            InstallStage.STAGE_READY -> {
+                // Source app now has appOp granted.
+                generateConfirmationSnippet()
+            }
+
+            InstallStage.STAGE_ABORTED -> {
+                // There was some error in determining the AppOp code for the source app.
+                // Abort installation
+                unknownSourceStage
+            }
+
+            else -> {
+                // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
+                // unexpected while reattempting the install. Let's abort it.
+                Log.e(LOG_TAG, "AppOp still not granted.")
+                InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+        }
+    }
+
+    private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
+        if (requestInfo.callingPackage == null) {
+            Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
+            return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
+        }
+        // Shouldn't use static constant directly, see b/65534401.
+        val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
+        val appOpMode = appOpsManager!!.noteOpNoThrow(
+            appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+            requestInfo.attributionTag, "Started package installation activity"
+        )
+        if (localLOGV) {
+            Log.i(LOG_TAG, "handleUnknownSources(): appMode=$appOpMode")
+        }
+
+        return when (appOpMode) {
+            AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
+                if (appOpMode == AppOpsManager.MODE_DEFAULT) {
+                    appOpsManager.setMode(
+                        appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+                        AppOpsManager.MODE_ERRORED
+                    )
+                }
+                try {
+                    val sourceInfo =
+                        packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+                    val sourceAppSnippet = getAppSnippet(context, sourceInfo)
+                    InstallUserActionRequired(
+                        USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
+                        dialogMessage = requestInfo.callingPackage
+                    )
+                } catch (e: PackageManager.NameNotFoundException) {
+                    Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+                    InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+                }
+            }
+
+            AppOpsManager.MODE_ALLOWED -> InstallReady()
+
+            else -> {
+                Log.e(
+                    LOG_TAG, "Invalid app op mode $appOpMode for " +
+                        "OP_REQUEST_INSTALL_PACKAGES found for uid $requestInfo.originatingUid"
+                )
+                InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            }
+        }
+    }
+
+    /**
+     * Kick off the installation. Register a broadcast listener to get the result of the
+     * installation and commit the staged session here. If the installation was session based,
+     * signal the PackageInstaller that the user has granted permission to proceed with the install
+     */
+    fun initiateInstall() {
+        if (sessionId > 0) {
+            packageInstaller.setPermissionsResult(sessionId, true)
+            _installResult.value = InstallAborted(
+                ABORT_REASON_DONE, activityResultCode = Activity.RESULT_OK
+            )
+            return
+        }
+        val uri = intent.data
+        if (SCHEME_PACKAGE == uri?.scheme) {
+            try {
+                packageManager.installExistingPackage(
+                    newPackageInfo!!.packageName, PackageManager.INSTALL_REASON_USER
+                )
+                setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
+            } catch (e: PackageManager.NameNotFoundException) {
+                setStageBasedOnResult(
+                    PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+                    null)
+            }
+            return
+        }
+        if (stagedSessionId <= 0) {
+            // How did we even land here?
+            Log.e(LOG_TAG, "Invalid local session and caller initiated session")
+            _installResult.value = InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+            return
+        }
+        val installId: Int
+        try {
+            _installResult.value = InstallInstalling(appSnippet)
+            installId = InstallEventReceiver.addObserver(
+                context, EventResultPersister.GENERATE_NEW_ID
+            ) { statusCode: Int, legacyStatus: Int, message: String?, serviceId: Int ->
+                setStageBasedOnResult(statusCode, legacyStatus, message)
+            }
+        } catch (e: OutOfIdsException) {
+            setStageBasedOnResult(
+                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+            return
+        }
+        val broadcastIntent = Intent(BROADCAST_ACTION)
+        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+        broadcastIntent.setPackage(context.packageName)
+        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId)
+        val pendingIntent = PendingIntent.getBroadcast(
+            context, installId, broadcastIntent,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+        )
+        try {
+            val session = packageInstaller.openSession(stagedSessionId)
+            session.commit(pendingIntent.intentSender)
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
+            packageInstaller.abandonSession(stagedSessionId)
+            setStageBasedOnResult(
+                PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+        }
+    }
+
+    private fun setStageBasedOnResult(
+        statusCode: Int,
+        legacyStatus: Int,
+        message: String?
+    ) {
+        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
+            val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+            val resultIntent = if (shouldReturnResult) {
+                Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
+            } else {
+                packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName)
+            }
+            _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
+        } else {
+            _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+        }
+    }
+
+    /**
+     * Cleanup the staged session. Also signal the packageinstaller that an install session is to
+     * be aborted
+     */
+    fun cleanupInstall() {
+        if (sessionId > 0) {
+            packageInstaller.setPermissionsResult(sessionId, false)
+        } else if (stagedSessionId > 0) {
+            cleanupStagingSession()
+        }
+    }
+
+    /**
+     * When the identity of the install source could not be determined, user can skip checking the
+     * source and directly proceed with the install.
+     */
+    fun forcedSkipSourceCheck(): InstallStage {
+        return generateConfirmationSnippet()
+    }
+
+    val stagingProgress: LiveData<Int>
+        get() = sessionStager?.progress ?: MutableLiveData(0)
+
+    companion object {
+        const val EXTRA_STAGED_SESSION_ID = "com.android.packageinstaller.extra.STAGED_SESSION_ID"
+        const val SCHEME_PACKAGE = "package"
+        const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT"
+        private val LOG_TAG = InstallRepository::class.java.simpleName
+    }
+
+    data class CallerInfo(val packageName: String?, val uid: Int)
+    data class AppOpRequestInfo(
+        val callingPackage: String?,
+        val originatingUid: Int,
+        val attributionTag: String?,
+    )
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
new file mode 100644
index 0000000..be49b39
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -0,0 +1,134 @@
+/*
+ * 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
+ *
+ *      https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+
+sealed class InstallStage(val stageCode: Int) {
+
+    companion object {
+        const val STAGE_DEFAULT = -1
+        const val STAGE_ABORTED = 0
+        const val STAGE_STAGING = 1
+        const val STAGE_READY = 2
+        const val STAGE_USER_ACTION_REQUIRED = 3
+        const val STAGE_INSTALLING = 4
+        const val STAGE_SUCCESS = 5
+        const val STAGE_FAILED = 6
+    }
+}
+
+class InstallStaging : InstallStage(STAGE_STAGING)
+
+class InstallReady : InstallStage(STAGE_READY)
+
+data class InstallUserActionRequired(
+    val actionReason: Int,
+    private val appSnippet: PackageUtil.AppSnippet? = null,
+    val isAppUpdating: Boolean = false,
+    val dialogMessage: String? = null,
+) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
+
+    val appIcon: Drawable?
+        get() = appSnippet?.icon
+
+    val appLabel: String?
+        get() = appSnippet?.let { appSnippet.label as String? }
+
+    companion object {
+        const val USER_ACTION_REASON_UNKNOWN_SOURCE = 0
+        const val USER_ACTION_REASON_ANONYMOUS_SOURCE = 1
+        const val USER_ACTION_REASON_INSTALL_CONFIRMATION = 2
+    }
+}
+
+data class InstallInstalling(private val appSnippet: PackageUtil.AppSnippet) :
+    InstallStage(STAGE_INSTALLING) {
+
+    val appIcon: Drawable?
+        get() = appSnippet.icon
+
+    val appLabel: String?
+        get() = appSnippet.label as String?
+}
+
+data class InstallSuccess(
+    private val appSnippet: PackageUtil.AppSnippet,
+    val shouldReturnResult: Boolean = false,
+    /**
+     *
+     * * If the caller is requesting a result back, this will hold the Intent with
+     * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED] which is sent
+     * back to the caller.
+     *
+     * * If the caller doesn't want the result back, this will hold the Intent that launches
+     * the newly installed / updated app if a launchable activity exists.
+     */
+    val resultIntent: Intent? = null,
+) : InstallStage(STAGE_SUCCESS) {
+
+    val appIcon: Drawable?
+        get() = appSnippet.icon
+
+    val appLabel: String?
+        get() = appSnippet.label as String?
+}
+
+data class InstallFailed(
+    private val appSnippet: PackageUtil.AppSnippet,
+    val legacyCode: Int,
+    val statusCode: Int,
+    val message: String?,
+) : InstallStage(STAGE_FAILED) {
+
+    val appIcon: Drawable?
+        get() = appSnippet.icon
+
+    val appLabel: String?
+        get() = appSnippet.label as String?
+}
+
+data class InstallAborted(
+    val abortReason: Int,
+    /**
+     * It will hold the restriction name, when the restriction was enforced by the system, and not
+     * a device admin.
+     */
+    val message: String? = null,
+    /**
+     * * If abort reason is [ABORT_REASON_POLICY], then this will hold the Intent
+     * to display a support dialog when a feature was disabled by an admin. It will be
+     * `null` if the feature is disabled by the system. In this case, the restriction name
+     * will be set in [message]
+     * * If the abort reason is [ABORT_REASON_INTERNAL_ERROR], it **may** hold an
+     * intent to be sent as a result to the calling activity.
+     */
+    val resultIntent: Intent? = null,
+    val activityResultCode: Int = Activity.RESULT_CANCELED,
+    val errorDialogType: Int? = 0,
+) : InstallStage(STAGE_ABORTED) {
+
+    companion object {
+        const val ABORT_REASON_INTERNAL_ERROR = 0
+        const val ABORT_REASON_POLICY = 1
+        const val ABORT_REASON_DONE = 2
+        const val DLG_PACKAGE_ERROR = 1
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
deleted file mode 100644
index fe05237..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import java.io.File;
-import java.util.Arrays;
-import java.util.Objects;
-
-public class PackageUtil {
-
-    private static final String TAG = InstallRepository.class.getSimpleName();
-    private static final String DOWNLOADS_AUTHORITY = "downloads";
-    private static final String SPLIT_BASE_APK_END_WITH = "base.apk";
-
-    /**
-     * Determines if the UID belongs to the system downloads provider and returns the
-     * {@link ApplicationInfo} of the provider
-     *
-     * @param uid UID of the caller
-     * @return {@link ApplicationInfo} of the provider if a downloads provider exists, it is a
-     *     system app, and its UID matches with the passed UID, null otherwise.
-     */
-    public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
-        final ProviderInfo providerInfo = pm.resolveContentProvider(
-            DOWNLOADS_AUTHORITY, 0);
-        if (providerInfo == null) {
-            // There seems to be no currently enabled downloads provider on the system.
-            return null;
-        }
-        ApplicationInfo appInfo = providerInfo.applicationInfo;
-        if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
-            return appInfo;
-        }
-        return null;
-    }
-
-    /**
-     * Get the maximum target sdk for a UID.
-     *
-     * @param context The context to use
-     * @param uid The UID requesting the install/uninstall
-     * @return The maximum target SDK or -1 if the uid does not match any packages.
-     */
-    public static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
-        PackageManager pm = context.getPackageManager();
-        final String[] packages = pm.getPackagesForUid(uid);
-        int targetSdkVersion = -1;
-        if (packages != null) {
-            for (String packageName : packages) {
-                try {
-                    ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-                    targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
-                } catch (PackageManager.NameNotFoundException e) {
-                    // Ignore and try the next package
-                }
-            }
-        }
-        return targetSdkVersion;
-    }
-
-    public static boolean canPackageQuery(Context context, int callingUid, Uri packageUri) {
-        PackageManager pm = context.getPackageManager();
-        ProviderInfo info = pm.resolveContentProvider(packageUri.getAuthority(),
-            PackageManager.ComponentInfoFlags.of(0));
-        if (info == null) {
-            return false;
-        }
-        String targetPackage = info.packageName;
-
-        String[] callingPackages = pm.getPackagesForUid(callingUid);
-        if (callingPackages == null) {
-            return false;
-        }
-        for (String callingPackage : callingPackages) {
-            try {
-                if (pm.canPackageQuery(callingPackage, targetPackage)) {
-                    return true;
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                // no-op
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @param context the {@link Context} object
-     * @param permission the permission name to check
-     * @param callingUid the UID of the caller who's permission is being checked
-     * @return {@code true} if the callingUid is granted the said permission
-     */
-    public static boolean isPermissionGranted(Context context, String permission, int callingUid) {
-        return context.checkPermission(permission, -1, callingUid)
-            == PackageManager.PERMISSION_GRANTED;
-    }
-
-    /**
-     * @param pm the {@link PackageManager} object
-     * @param permission the permission name to check
-     * @param packageName the name of the package who's permission is being checked
-     * @return {@code true} if the package is granted the said permission
-     */
-    public static boolean isPermissionGranted(PackageManager pm, String permission,
-        String packageName) {
-        return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    /**
-     * @param context the {@link Context} object
-     * @param callingUid the UID of the caller who's permission is being checked
-     * @param originatingUid the UID from where install is being originated. This could be same as
-     * callingUid or it will be the UID of the package performing a session based install
-     * @param isTrustedSource whether install request is coming from a privileged app or an app that
-     * has {@link Manifest.permission.INSTALL_PACKAGES} permission granted
-     * @return {@code true} if the package is granted the said permission
-     */
-    public static boolean isInstallPermissionGrantedOrRequested(Context context, int callingUid,
-        int originatingUid, boolean isTrustedSource) {
-        boolean isDocumentsManager =
-            isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid);
-        boolean isSystemDownloadsProvider =
-            getSystemDownloadsProviderInfo(context.getPackageManager(), callingUid) != null;
-
-        if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
-
-            final int targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid);
-            if (targetSdkVersion < 0) {
-                // Invalid originating uid supplied. Abort install.
-                Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
-                return false;
-            } else if (targetSdkVersion >= Build.VERSION_CODES.O
-                && !isUidRequestingPermission(context.getPackageManager(), originatingUid,
-                Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
-                Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
-                    + Manifest.permission.REQUEST_INSTALL_PACKAGES);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * @param pm the {@link PackageManager} object
-     * @param uid the UID of the caller who's permission is being checked
-     * @param permission the permission name to check
-     * @return {@code true} if the caller is requesting the said permission in its Manifest
-     */
-    public static boolean isUidRequestingPermission(PackageManager pm, int uid, String permission) {
-        final String[] packageNames = pm.getPackagesForUid(uid);
-        if (packageNames == null) {
-            return false;
-        }
-        for (final String packageName : packageNames) {
-            final PackageInfo packageInfo;
-            try {
-                packageInfo = pm.getPackageInfo(packageName,
-                    PackageManager.GET_PERMISSIONS);
-            } catch (PackageManager.NameNotFoundException e) {
-                // Ignore and try the next package
-                continue;
-            }
-            if (packageInfo.requestedPermissions != null
-                && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @param pi the {@link PackageInstaller} object to use
-     * @param originatingUid the UID of the package performing a session based install
-     * @param sessionId ID of the install session
-     * @return {@code true} if the caller is the session owner
-     */
-    public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid,
-        int sessionId) {
-        if (originatingUid == Process.ROOT_UID) {
-            return true;
-        }
-        PackageInstaller.SessionInfo sessionInfo = pi.getSessionInfo(sessionId);
-        if (sessionInfo == null) {
-            return false;
-        }
-        int installerUid = sessionInfo.getInstallerUid();
-        return originatingUid == installerUid;
-    }
-
-    /**
-     * Generates a stub {@link PackageInfo} object for the given packageName
-     */
-    public static PackageInfo generateStubPackageInfo(String packageName) {
-        final PackageInfo info = new PackageInfo();
-        final ApplicationInfo aInfo = new ApplicationInfo();
-        info.applicationInfo = aInfo;
-        info.packageName = info.applicationInfo.packageName = packageName;
-        return info;
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * {@link SessionInfo} object
-     */
-    public static AppSnippet getAppSnippet(Context context, SessionInfo info) {
-        PackageManager pm = context.getPackageManager();
-        CharSequence label = info.getAppLabel();
-        Drawable icon = info.getAppIcon() != null ?
-            new BitmapDrawable(context.getResources(), info.getAppIcon())
-            : pm.getDefaultActivityIcon();
-        return new AppSnippet(label, icon);
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * {@link PackageInfo} object
-     */
-    public static AppSnippet getAppSnippet(Context context, PackageInfo pkgInfo) {
-        return getAppSnippet(context, pkgInfo.applicationInfo);
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * {@link ApplicationInfo} object
-     */
-    public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo) {
-        PackageManager pm = context.getPackageManager();
-        CharSequence label = pm.getApplicationLabel(appInfo);
-        Drawable icon = pm.getApplicationIcon(appInfo);
-        return new AppSnippet(label, icon);
-    }
-
-    /**
-     * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
-     * supplied APK file
-     */
-    public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo,
-        File sourceFile) {
-        ApplicationInfo appInfoFromFile = processAppInfoForFile(appInfo, sourceFile);
-        CharSequence label = getAppLabelFromFile(context, appInfoFromFile);
-        Drawable icon = getAppIconFromFile(context, appInfoFromFile);
-        return new AppSnippet(label, icon);
-    }
-
-    /**
-     * Utility method to load application label
-     *
-     * @param context context of package that can load the resources
-     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
-     */
-    public static CharSequence getAppLabelFromFile(Context context, ApplicationInfo appInfo) {
-        PackageManager pm = context.getPackageManager();
-        CharSequence label = null;
-        // Try to load the label from the package's resources. If an app has not explicitly
-        // specified any label, just use the package name.
-        if (appInfo.labelRes != 0) {
-            try {
-                label = appInfo.loadLabel(pm);
-            } catch (Resources.NotFoundException e) {
-            }
-        }
-        if (label == null) {
-            label = (appInfo.nonLocalizedLabel != null) ?
-                appInfo.nonLocalizedLabel : appInfo.packageName;
-        }
-        return label;
-    }
-
-    /**
-     * Utility method to load application icon
-     *
-     * @param context context of package that can load the resources
-     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
-     */
-    public static Drawable getAppIconFromFile(Context context, ApplicationInfo appInfo) {
-        PackageManager pm = context.getPackageManager();
-        Drawable icon = null;
-        // Try to load the icon from the package's resources. If an app has not explicitly
-        // specified any resource, just use the default icon for now.
-        try {
-            if (appInfo.icon != 0) {
-                try {
-                    icon = appInfo.loadIcon(pm);
-                } catch (Resources.NotFoundException e) {
-                }
-            }
-            if (icon == null) {
-                icon = context.getPackageManager().getDefaultActivityIcon();
-            }
-        } catch (OutOfMemoryError e) {
-            Log.i(TAG, "Could not load app icon", e);
-        }
-        return icon;
-    }
-
-    private static ApplicationInfo processAppInfoForFile(ApplicationInfo appInfo, File sourceFile) {
-        final String archiveFilePath = sourceFile.getAbsolutePath();
-        appInfo.publicSourceDir = archiveFilePath;
-
-        if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
-            final File[] files = sourceFile.getParentFile().listFiles();
-            final String[] splits = Arrays.stream(appInfo.splitNames)
-                .map(i -> findFilePath(files, i + ".apk"))
-                .filter(Objects::nonNull)
-                .toArray(String[]::new);
-
-            appInfo.splitSourceDirs = splits;
-            appInfo.splitPublicSourceDirs = splits;
-        }
-        return appInfo;
-    }
-
-    private static String findFilePath(File[] files, String postfix) {
-        for (File file : files) {
-            final String path = file.getAbsolutePath();
-            if (path.endsWith(postfix)) {
-                return path;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * @return the packageName corresponding to a UID.
-     */
-    public static String getPackageNameForUid(Context context, int sourceUid,
-        String callingPackage) {
-        if (sourceUid == Process.INVALID_UID) {
-            return null;
-        }
-        // If the sourceUid belongs to the system downloads provider, we explicitly return the
-        // name of the Download Manager package. This is because its UID is shared with multiple
-        // packages, resulting in uncertainty about which package will end up first in the list
-        // of packages associated with this UID
-        PackageManager pm = context.getPackageManager();
-        ApplicationInfo systemDownloadProviderInfo = getSystemDownloadsProviderInfo(
-            pm, sourceUid);
-        if (systemDownloadProviderInfo != null) {
-            return systemDownloadProviderInfo.packageName;
-        }
-        String[] packagesForUid = pm.getPackagesForUid(sourceUid);
-        if (packagesForUid == null) {
-            return null;
-        }
-        if (packagesForUid.length > 1) {
-            if (callingPackage != null) {
-                for (String packageName : packagesForUid) {
-                    if (packageName.equals(callingPackage)) {
-                        return packageName;
-                    }
-                }
-            }
-            Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
-        }
-        return packagesForUid[0];
-    }
-
-    /**
-     * Utility method to get package information for a given {@link File}
-     */
-    public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
-        String filePath = sourceFile.getAbsolutePath();
-        if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
-            File dir = sourceFile.getParentFile();
-            if (dir.listFiles().length > 1) {
-                // split apks, use file directory to get archive info
-                filePath = dir.getPath();
-            }
-        }
-        try {
-            return context.getPackageManager().getPackageArchiveInfo(filePath, flags);
-        } catch (Exception ignored) {
-            return null;
-        }
-    }
-
-    /**
-     * Is a profile part of a user?
-     *
-     * @param userManager The user manager
-     * @param userHandle The handle of the user
-     * @param profileHandle The handle of the profile
-     *
-     * @return If the profile is part of the user or the profile parent of the user
-     */
-    public static boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
-        UserHandle profileHandle) {
-        if (userHandle.equals(profileHandle)) {
-            return true;
-        }
-        return userManager.getProfileParent(profileHandle) != null
-            && userManager.getProfileParent(profileHandle).equals(userHandle);
-    }
-
-    /**
-     * The class to hold an incoming package's icon and label.
-     * See {@link #getAppSnippet(Context, SessionInfo)},
-     * {@link #getAppSnippet(Context, PackageInfo)},
-     * {@link #getAppSnippet(Context, ApplicationInfo)},
-     * {@link #getAppSnippet(Context, ApplicationInfo, File)}
-     */
-    public static class AppSnippet {
-
-        private CharSequence mLabel;
-        private Drawable mIcon;
-
-        public AppSnippet(CharSequence label, Drawable icon) {
-            mLabel = label;
-            mIcon = icon;
-        }
-
-        public AppSnippet() {
-        }
-
-        public CharSequence getLabel() {
-            return mLabel;
-        }
-
-        public void setLabel(CharSequence mLabel) {
-            this.mLabel = mLabel;
-        }
-
-        public Drawable getIcon() {
-            return mIcon;
-        }
-
-        public void setIcon(Drawable mIcon) {
-            this.mIcon = mIcon;
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
new file mode 100644
index 0000000..8d8c2f1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -0,0 +1,440 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import java.io.File
+
+object PackageUtil {
+    private val LOG_TAG = InstallRepository::class.java.simpleName
+    private const val DOWNLOADS_AUTHORITY = "downloads"
+    private const val SPLIT_BASE_APK_END_WITH = "base.apk"
+
+    /**
+     * Determines if the UID belongs to the system downloads provider and returns the
+     * [ApplicationInfo] of the provider
+     *
+     * @param uid UID of the caller
+     * @return [ApplicationInfo] of the provider if a downloads provider exists, it is a
+     * system app, and its UID matches with the passed UID, null otherwise.
+     */
+    private fun getSystemDownloadsProviderInfo(pm: PackageManager, uid: Int): ApplicationInfo? {
+        // Check if there are currently enabled downloads provider on the system.
+        val providerInfo = pm.resolveContentProvider(DOWNLOADS_AUTHORITY, 0)
+            ?: return null
+        val appInfo = providerInfo.applicationInfo
+        return if ((appInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) && uid == appInfo.uid) {
+            appInfo
+        } else null
+    }
+
+    /**
+     * Get the maximum target sdk for a UID.
+     *
+     * @param context The context to use
+     * @param uid The UID requesting the install/uninstall
+     * @return The maximum target SDK or -1 if the uid does not match any packages.
+     */
+    @JvmStatic
+    fun getMaxTargetSdkVersionForUid(context: Context, uid: Int): Int {
+        val pm = context.packageManager
+        val packages = pm.getPackagesForUid(uid)
+        var targetSdkVersion = -1
+        if (packages != null) {
+            for (packageName in packages) {
+                try {
+                    val info = pm.getApplicationInfo(packageName!!, 0)
+                    targetSdkVersion = maxOf(targetSdkVersion, info.targetSdkVersion)
+                } catch (e: PackageManager.NameNotFoundException) {
+                    // Ignore and try the next package
+                }
+            }
+        }
+        return targetSdkVersion
+    }
+
+    @JvmStatic
+    fun canPackageQuery(context: Context, callingUid: Int, packageUri: Uri): Boolean {
+        val pm = context.packageManager
+        val info = pm.resolveContentProvider(
+            packageUri.authority!!,
+            PackageManager.ComponentInfoFlags.of(0)
+        ) ?: return false
+        val targetPackage = info.packageName
+        val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
+        for (callingPackage in callingPackages) {
+            try {
+                if (pm.canPackageQuery(callingPackage!!, targetPackage)) {
+                    return true
+                }
+            } catch (e: PackageManager.NameNotFoundException) {
+                // no-op
+            }
+        }
+        return false
+    }
+
+    /**
+     * @param context the [Context] object
+     * @param permission the permission name to check
+     * @param callingUid the UID of the caller who's permission is being checked
+     * @return `true` if the callingUid is granted the said permission
+     */
+    @JvmStatic
+    fun isPermissionGranted(context: Context, permission: String, callingUid: Int): Boolean {
+        return (context.checkPermission(permission, -1, callingUid)
+            == PackageManager.PERMISSION_GRANTED)
+    }
+
+    /**
+     * @param pm the [PackageManager] object
+     * @param permission the permission name to check
+     * @param packageName the name of the package who's permission is being checked
+     * @return `true` if the package is granted the said permission
+     */
+    @JvmStatic
+    fun isPermissionGranted(pm: PackageManager, permission: String, packageName: String): Boolean {
+        return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED
+    }
+
+    /**
+     * @param context the [Context] object
+     * @param callingUid the UID of the caller who's permission is being checked
+     * @param originatingUid the UID from where install is being originated. This could be same as
+     * callingUid or it will be the UID of the package performing a session based install
+     * @param isTrustedSource whether install request is coming from a privileged app or an app that
+     * has [Manifest.permission.INSTALL_PACKAGES] permission granted
+     * @return `true` if the package is granted the said permission
+     */
+    @JvmStatic
+    fun isInstallPermissionGrantedOrRequested(
+        context: Context,
+        callingUid: Int,
+        originatingUid: Int,
+        isTrustedSource: Boolean,
+    ): Boolean {
+        val isDocumentsManager =
+            isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid)
+        val isSystemDownloadsProvider =
+            getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
+
+        if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
+            val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+            if (targetSdkVersion < 0) {
+                // Invalid originating uid supplied. Abort install.
+                Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+                return false
+            } else if (targetSdkVersion >= Build.VERSION_CODES.O
+                && !isUidRequestingPermission(
+                    context.packageManager, originatingUid,
+                    Manifest.permission.REQUEST_INSTALL_PACKAGES
+                )
+            ) {
+                Log.e(
+                    LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+                        + Manifest.permission.REQUEST_INSTALL_PACKAGES
+                )
+                return false
+            }
+        }
+        return true
+    }
+
+    /**
+     * @param pm the [PackageManager] object
+     * @param uid the UID of the caller who's permission is being checked
+     * @param permission the permission name to check
+     * @return `true` if the caller is requesting the said permission in its Manifest
+     */
+    private fun isUidRequestingPermission(
+        pm: PackageManager,
+        uid: Int,
+        permission: String,
+    ): Boolean {
+        val packageNames = pm.getPackagesForUid(uid) ?: return false
+        for (packageName in packageNames) {
+            val packageInfo: PackageInfo = try {
+                pm.getPackageInfo(packageName!!, PackageManager.GET_PERMISSIONS)
+            } catch (e: PackageManager.NameNotFoundException) {
+                // Ignore and try the next package
+                continue
+            }
+            if (packageInfo.requestedPermissions != null
+                && listOf(*packageInfo.requestedPermissions!!).contains(permission)
+            ) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * @param pi the [PackageInstaller] object to use
+     * @param originatingUid the UID of the package performing a session based install
+     * @param sessionId ID of the install session
+     * @return `true` if the caller is the session owner
+     */
+    @JvmStatic
+    fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
+        if (originatingUid == Process.ROOT_UID) {
+            return true
+        }
+        val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
+        val installerUid = sessionInfo.getInstallerUid()
+        return originatingUid == installerUid
+    }
+
+    /**
+     * Generates a stub [PackageInfo] object for the given packageName
+     */
+    @JvmStatic
+    fun generateStubPackageInfo(packageName: String?): PackageInfo {
+        val info = PackageInfo()
+        val aInfo = ApplicationInfo()
+        info.applicationInfo = aInfo
+        info.applicationInfo!!.packageName = packageName
+        info.packageName = info.applicationInfo!!.packageName
+        return info
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * [PackageInstaller.SessionInfo] object
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, info: PackageInstaller.SessionInfo): AppSnippet {
+        val pm = context.packageManager
+        val label = info.getAppLabel()
+        val icon = if (info.getAppIcon() != null) BitmapDrawable(
+            context.resources,
+            info.getAppIcon()
+        ) else pm.defaultActivityIcon
+        return AppSnippet(label, icon)
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * [PackageInfo] object
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, pkgInfo: PackageInfo): AppSnippet {
+        return pkgInfo.applicationInfo?.let { getAppSnippet(context, it) } ?: run {
+            AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+        }
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * [ApplicationInfo] object
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, appInfo: ApplicationInfo): AppSnippet {
+        val pm = context.packageManager
+        val label = pm.getApplicationLabel(appInfo)
+        val icon = pm.getApplicationIcon(appInfo)
+        return AppSnippet(label, icon)
+    }
+
+    /**
+     * Generates an [AppSnippet] containing an appIcon and appLabel from the
+     * supplied APK file
+     */
+    @JvmStatic
+    fun getAppSnippet(context: Context, pkgInfo: PackageInfo, sourceFile: File): AppSnippet {
+        pkgInfo.applicationInfo?.let {
+            val appInfoFromFile = processAppInfoForFile(it, sourceFile)
+            val label = getAppLabelFromFile(context, appInfoFromFile)
+            val icon = getAppIconFromFile(context, appInfoFromFile)
+            return AppSnippet(label, icon)
+        } ?: run {
+            return AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+        }
+    }
+
+    /**
+     * Utility method to load application label
+     *
+     * @param context context of package that can load the resources
+     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+     */
+    private fun getAppLabelFromFile(context: Context, appInfo: ApplicationInfo): CharSequence? {
+        val pm = context.packageManager
+        var label: CharSequence? = null
+        // Try to load the label from the package's resources. If an app has not explicitly
+        // specified any label, just use the package name.
+        if (appInfo.labelRes != 0) {
+            try {
+                label = appInfo.loadLabel(pm)
+            } catch (e: Resources.NotFoundException) {
+            }
+        }
+        if (label == null) {
+            label = if (appInfo.nonLocalizedLabel != null) appInfo.nonLocalizedLabel
+            else appInfo.packageName
+        }
+        return label
+    }
+
+    /**
+     * Utility method to load application icon
+     *
+     * @param context context of package that can load the resources
+     * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+     */
+    private fun getAppIconFromFile(context: Context, appInfo: ApplicationInfo): Drawable? {
+        val pm = context.packageManager
+        var icon: Drawable? = null
+        // Try to load the icon from the package's resources. If an app has not explicitly
+        // specified any resource, just use the default icon for now.
+        try {
+            if (appInfo.icon != 0) {
+                try {
+                    icon = appInfo.loadIcon(pm)
+                } catch (e: Resources.NotFoundException) {
+                }
+            }
+            if (icon == null) {
+                icon = context.packageManager.defaultActivityIcon
+            }
+        } catch (e: OutOfMemoryError) {
+            Log.i(LOG_TAG, "Could not load app icon", e)
+        }
+        return icon
+    }
+
+    private fun processAppInfoForFile(appInfo: ApplicationInfo, sourceFile: File): ApplicationInfo {
+        val archiveFilePath = sourceFile.absolutePath
+        appInfo.publicSourceDir = archiveFilePath
+        if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
+            val files = sourceFile.parentFile?.listFiles()
+            val splits = appInfo.splitNames!!
+                .mapNotNull { findFilePath(files, "$it.apk") }
+                .toTypedArray()
+
+            appInfo.splitSourceDirs = splits
+            appInfo.splitPublicSourceDirs = splits
+        }
+        return appInfo
+    }
+
+    private fun findFilePath(files: Array<File>?, postfix: String): String? {
+        files?.let {
+            for (file in it) {
+                val path = file.absolutePath
+                if (path.endsWith(postfix)) {
+                    return path
+                }
+            }
+        }
+        return null
+    }
+
+    /**
+     * @return the packageName corresponding to a UID.
+     */
+    @JvmStatic
+    fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
+        if (sourceUid == Process.INVALID_UID) {
+            return null
+        }
+        // If the sourceUid belongs to the system downloads provider, we explicitly return the
+        // name of the Download Manager package. This is because its UID is shared with multiple
+        // packages, resulting in uncertainty about which package will end up first in the list
+        // of packages associated with this UID
+        val pm = context.packageManager
+        val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+        if (systemDownloadProviderInfo != null) {
+            return systemDownloadProviderInfo.packageName
+        }
+        val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+        if (packagesForUid.size > 1) {
+            if (callingPackage != null) {
+                for (packageName in packagesForUid) {
+                    if (packageName == callingPackage) {
+                        return packageName
+                    }
+                }
+            }
+            Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
+        }
+        return packagesForUid[0]
+    }
+
+    /**
+     * Utility method to get package information for a given [File]
+     */
+    @JvmStatic
+    fun getPackageInfo(context: Context, sourceFile: File, flags: Int): PackageInfo? {
+        var filePath = sourceFile.absolutePath
+        if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
+            val dir = sourceFile.parentFile
+            if ((dir?.listFiles()?.size ?: 0) > 1) {
+                // split apks, use file directory to get archive info
+                filePath = dir.path
+            }
+        }
+        return try {
+            context.packageManager.getPackageArchiveInfo(filePath, flags)
+        } catch (ignored: Exception) {
+            null
+        }
+    }
+
+    /**
+     * Is a profile part of a user?
+     *
+     * @param userManager The user manager
+     * @param userHandle The handle of the user
+     * @param profileHandle The handle of the profile
+     *
+     * @return If the profile is part of the user or the profile parent of the user
+     */
+    @JvmStatic
+    fun isProfileOfOrSame(
+        userManager: UserManager,
+        userHandle: UserHandle,
+        profileHandle: UserHandle?,
+    ): Boolean {
+        if (profileHandle == null) {
+            return false
+        }
+        return if (userHandle == profileHandle) {
+            true
+        } else userManager.getProfileParent(profileHandle) != null
+            && userManager.getProfileParent(profileHandle) == userHandle
+    }
+
+    /**
+     * The class to hold an incoming package's icon and label.
+     * See [getAppSnippet]
+     */
+    data class AppSnippet(var label: CharSequence?, var icon: Drawable?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
deleted file mode 100644
index a2c81f1..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> {
-
-    private static final String TAG = SessionStager.class.getSimpleName();
-    private final Context mContext;
-    private final Uri mUri;
-    private final int mStagedSessionId;
-    private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0);
-    private final SessionStageListener mListener;
-
-    SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) {
-        mContext = context;
-        mUri = uri;
-        mStagedSessionId = stagedSessionId;
-        mListener = listener;
-    }
-
-    @Override
-    protected PackageInstaller.SessionInfo doInBackground(Void... params) {
-        PackageInstaller pi = mContext.getPackageManager().getPackageInstaller();
-        try (PackageInstaller.Session session = pi.openSession(mStagedSessionId);
-            InputStream in = mContext.getContentResolver().openInputStream(mUri)) {
-            session.setStagingProgress(0);
-
-            if (in == null) {
-                return null;
-            }
-            final long sizeBytes = getContentSizeBytes();
-            mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1);
-
-            long totalRead = 0;
-            try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
-                byte[] buffer = new byte[1024 * 1024];
-                while (true) {
-                    int numRead = in.read(buffer);
-
-                    if (numRead == -1) {
-                        session.fsync(out);
-                        break;
-                    }
-
-                    if (isCancelled()) {
-                        break;
-                    }
-
-                    out.write(buffer, 0, numRead);
-                    if (sizeBytes > 0) {
-                        totalRead += numRead;
-                        float fraction = ((float) totalRead / (float) sizeBytes);
-                        session.setStagingProgress(fraction);
-                        publishProgress((int) (fraction * 100.0));
-                    }
-                }
-            }
-            return pi.getSessionInfo(mStagedSessionId);
-        } catch (IOException | SecurityException | IllegalStateException
-                 | IllegalArgumentException e) {
-            Log.w(TAG, "Error staging apk from content URI", e);
-            return null;
-        }
-    }
-
-    private long getContentSizeBytes() {
-        try (AssetFileDescriptor afd = mContext.getContentResolver()
-            .openAssetFileDescriptor(mUri, "r")) {
-            return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to open asset file descriptor", e);
-            return UNKNOWN_LENGTH;
-        }
-    }
-
-    public MutableLiveData<Integer> getProgress() {
-        return mProgressLiveData;
-    }
-
-    @Override
-    protected void onProgressUpdate(Integer... progress) {
-        if (progress != null && progress.length > 0) {
-            mProgressLiveData.setValue(progress[0]);
-        }
-    }
-
-    @Override
-    protected void onPostExecute(SessionInfo sessionInfo) {
-        if (sessionInfo == null || !sessionInfo.isActive()
-            || sessionInfo.getResolvedBaseApkPath() == null) {
-            Log.w(TAG, "Session info is invalid: " + sessionInfo);
-            mListener.onStagingFailure();
-            return;
-        }
-        mListener.onStagingSuccess(sessionInfo);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
new file mode 100644
index 0000000..c9bfa17
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.content.Context
+import android.content.pm.PackageInstaller
+import android.content.res.AssetFileDescriptor
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import java.io.IOException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class SessionStager internal constructor(
+    private val context: Context,
+    private val uri: Uri,
+    private val stagedSessionId: Int
+) {
+
+    companion object {
+        private val LOG_TAG = SessionStager::class.java.simpleName
+    }
+
+    private val _progress = MutableLiveData(0)
+    val progress: LiveData<Int>
+        get() = _progress
+
+    suspend fun execute(): Boolean = withContext(Dispatchers.IO) {
+        val pi: PackageInstaller = context.packageManager.packageInstaller
+        var sessionInfo: PackageInstaller.SessionInfo?
+        try {
+            val session = pi.openSession(stagedSessionId)
+            context.contentResolver.openInputStream(uri).use { instream ->
+                session.setStagingProgress(0f)
+
+                if (instream == null) {
+                    return@withContext false
+                }
+
+                val sizeBytes = getContentSizeBytes()
+                publishProgress(if (sizeBytes > 0) 0 else -1)
+
+                var totalRead: Long = 0
+                session.openWrite("PackageInstaller", 0, sizeBytes).use { out ->
+                    val buffer = ByteArray(1024 * 1024)
+                    while (true) {
+                        val numRead = instream.read(buffer)
+                        if (numRead == -1) {
+                            session.fsync(out)
+                            break
+                        }
+                        out.write(buffer, 0, numRead)
+
+                        if (sizeBytes > 0) {
+                            totalRead += numRead.toLong()
+                            val fraction = totalRead.toFloat() / sizeBytes.toFloat()
+                            session.setStagingProgress(fraction)
+                            publishProgress((fraction * 100.0).toInt())
+                        }
+                    }
+                }
+                sessionInfo = pi.getSessionInfo(stagedSessionId)
+            }
+        } catch (e: Exception) {
+            Log.w(LOG_TAG, "Error staging apk from content URI", e)
+            sessionInfo = null
+        }
+
+        return@withContext if (sessionInfo == null
+            || !sessionInfo?.isActive!!
+            || sessionInfo?.resolvedBaseApkPath == null
+        ) {
+            Log.w(LOG_TAG, "Session info is invalid: $sessionInfo")
+            false
+        } else {
+            true
+        }
+    }
+
+    private fun getContentSizeBytes(): Long {
+        return try {
+            context.contentResolver
+                .openAssetFileDescriptor(uri, "r")
+                .use { afd -> afd?.length ?: AssetFileDescriptor.UNKNOWN_LENGTH }
+        } catch (e: IOException) {
+            Log.w(LOG_TAG, "Failed to open asset file descriptor", e)
+            AssetFileDescriptor.UNKNOWN_LENGTH
+        }
+    }
+
+    private suspend fun publishProgress(progressValue: Int) = withContext(Dispatchers.Main) {
+        _progress.value = progressValue
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
deleted file mode 100644
index a07c532..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
+++ /dev/null
@@ -1,716 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
-import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
-import static com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_APP_UNAVAILABLE;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_GENERIC_ERROR;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.app.usage.StorageStats;
-import android.app.usage.StorageStatsManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.UninstallCompleteCallback;
-import android.content.pm.VersionedPackage;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import java.io.IOException;
-import java.util.List;
-
-public class UninstallRepository {
-
-    private static final String TAG = UninstallRepository.class.getSimpleName();
-    private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall_failure";
-    private static final String BROADCAST_ACTION =
-        "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
-    private static final String EXTRA_UNINSTALL_ID =
-        "com.android.packageinstaller.extra.UNINSTALL_ID";
-    private static final String EXTRA_APP_LABEL =
-        "com.android.packageinstaller.extra.APP_LABEL";
-    private static final String EXTRA_IS_CLONE_APP =
-        "com.android.packageinstaller.extra.IS_CLONE_APP";
-    private static final String EXTRA_PACKAGE_NAME =
-        "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME";
-
-    private final Context mContext;
-    private final AppOpsManager mAppOpsManager;
-    private final PackageManager mPackageManager;
-    private final UserManager mUserManager;
-    private final NotificationManager mNotificationManager;
-    private final MutableLiveData<UninstallStage> mUninstallResult = new MutableLiveData<>();
-    public UserHandle mUninstalledUser;
-    public UninstallCompleteCallback mCallback;
-    private ApplicationInfo mTargetAppInfo;
-    private ActivityInfo mTargetActivityInfo;
-    private Intent mIntent;
-    private CharSequence mTargetAppLabel;
-    private String mTargetPackageName;
-    private String mCallingActivity;
-    private boolean mUninstallFromAllUsers;
-    private boolean mIsClonedApp;
-    private int mUninstallId;
-
-    public UninstallRepository(Context context) {
-        mContext = context;
-        mAppOpsManager = context.getSystemService(AppOpsManager.class);
-        mPackageManager = context.getPackageManager();
-        mUserManager = context.getSystemService(UserManager.class);
-        mNotificationManager = context.getSystemService(NotificationManager.class);
-    }
-
-    public UninstallStage performPreUninstallChecks(Intent intent, CallerInfo callerInfo) {
-        mIntent = intent;
-
-        int callingUid = callerInfo.getUid();
-        mCallingActivity = callerInfo.getActivityName();
-
-        if (callingUid == Process.INVALID_UID) {
-            Log.e(TAG, "Could not determine the launching uid.");
-            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-            // TODO: should we give any indication to the user?
-        }
-
-        String callingPackage = getPackageNameForUid(mContext, callingUid, null);
-        if (callingPackage == null) {
-            Log.e(TAG, "Package not found for originating uid " + callingUid);
-            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-        } else {
-            if (mAppOpsManager.noteOpNoThrow(
-                AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
-                != MODE_ALLOWED) {
-                Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
-                return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-            }
-        }
-
-        if (getMaxTargetSdkVersionForUid(mContext, callingUid) >= Build.VERSION_CODES.P
-            && !isPermissionGranted(mContext, Manifest.permission.REQUEST_DELETE_PACKAGES,
-            callingUid)
-            && !isPermissionGranted(mContext, Manifest.permission.DELETE_PACKAGES, callingUid)) {
-            Log.e(TAG, "Uid " + callingUid + " does not have "
-                + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
-                + Manifest.permission.DELETE_PACKAGES);
-
-            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
-        }
-
-        // Get intent information.
-        // We expect an intent with URI of the form package:<packageName>#<className>
-        // className is optional; if specified, it is the activity the user chose to uninstall
-        final Uri packageUri = intent.getData();
-        if (packageUri == null) {
-            Log.e(TAG, "No package URI in intent");
-            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
-        }
-        mTargetPackageName = packageUri.getEncodedSchemeSpecificPart();
-        if (mTargetPackageName == null) {
-            Log.e(TAG, "Invalid package name in URI: " + packageUri);
-            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
-        }
-
-        mUninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
-            false);
-        if (mUninstallFromAllUsers && !mUserManager.isAdminUser()) {
-            Log.e(TAG, "Only admin user can request uninstall for all users");
-            return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
-        }
-
-        mUninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
-        if (mUninstalledUser == null) {
-            mUninstalledUser = Process.myUserHandle();
-        } else {
-            List<UserHandle> profiles = mUserManager.getUserProfiles();
-            if (!profiles.contains(mUninstalledUser)) {
-                Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
-                    + "for user " + mUninstalledUser);
-                return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
-            }
-        }
-
-        mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
-            PackageManager.UninstallCompleteCallback.class);
-
-        try {
-            mTargetAppInfo = mPackageManager.getApplicationInfo(mTargetPackageName,
-                PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Unable to get packageName");
-        }
-
-        if (mTargetAppInfo == null) {
-            Log.e(TAG, "Invalid packageName: " + mTargetPackageName);
-            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
-        }
-
-        // The class name may have been specified (e.g. when deleting an app from all apps)
-        final String className = packageUri.getFragment();
-        if (className != null) {
-            try {
-                mTargetActivityInfo = mPackageManager.getActivityInfo(
-                    new ComponentName(mTargetPackageName, className),
-                    PackageManager.ComponentInfoFlags.of(0));
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.e(TAG, "Unable to get className");
-                // Continue as the ActivityInfo isn't critical.
-            }
-        }
-
-        return new UninstallReady();
-    }
-
-    public UninstallStage generateUninstallDetails() {
-        UninstallUserActionRequired.Builder uarBuilder = new UninstallUserActionRequired.Builder();
-        StringBuilder messageBuilder = new StringBuilder();
-
-        mTargetAppLabel = mTargetAppInfo.loadSafeLabel(mPackageManager);
-
-        // If the Activity label differs from the App label, then make sure the user
-        // knows the Activity belongs to the App being uninstalled.
-        if (mTargetActivityInfo != null) {
-            final CharSequence activityLabel = mTargetActivityInfo.loadSafeLabel(mPackageManager);
-            if (CharSequence.compare(activityLabel, mTargetAppLabel) != 0) {
-                messageBuilder.append(
-                    mContext.getString(R.string.uninstall_activity_text, activityLabel));
-                messageBuilder.append(" ").append(mTargetAppLabel).append(".\n\n");
-            }
-        }
-
-        final boolean isUpdate =
-            (mTargetAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
-        final UserHandle myUserHandle = Process.myUserHandle();
-        boolean isSingleUser = isSingleUser();
-
-        if (isUpdate) {
-            messageBuilder.append(mContext.getString(
-                isSingleUser ? R.string.uninstall_update_text :
-                    R.string.uninstall_update_text_multiuser));
-        } else if (mUninstallFromAllUsers && !isSingleUser) {
-            messageBuilder.append(mContext.getString(
-                R.string.uninstall_application_text_all_users));
-        } else if (!mUninstalledUser.equals(myUserHandle)) {
-            // Uninstalling user is issuing uninstall for another user
-            UserManager customUserManager = mContext.createContextAsUser(mUninstalledUser, 0)
-                .getSystemService(UserManager.class);
-            String userName = customUserManager.getUserName();
-
-            String uninstalledUserType = getUninstalledUserType(myUserHandle, mUninstalledUser);
-            String messageString;
-            if (USER_TYPE_PROFILE_MANAGED.equals(uninstalledUserType)) {
-                messageString = mContext.getString(
-                    R.string.uninstall_application_text_current_user_work_profile, userName);
-            } else if (USER_TYPE_PROFILE_CLONE.equals(uninstalledUserType)) {
-                mIsClonedApp = true;
-                messageString = mContext.getString(
-                    R.string.uninstall_application_text_current_user_clone_profile);
-            } else {
-                messageString = mContext.getString(
-                    R.string.uninstall_application_text_user, userName);
-            }
-            messageBuilder.append(messageString);
-        } else if (isCloneProfile(mUninstalledUser)) {
-            mIsClonedApp = true;
-            messageBuilder.append(mContext.getString(
-                R.string.uninstall_application_text_current_user_clone_profile));
-        } else if (myUserHandle.equals(UserHandle.SYSTEM)
-            && hasClonedInstance(mTargetAppInfo.packageName)) {
-            messageBuilder.append(mContext.getString(
-                R.string.uninstall_application_text_with_clone_instance, mTargetAppLabel));
-        } else {
-            messageBuilder.append(mContext.getString(R.string.uninstall_application_text));
-        }
-
-        uarBuilder.setMessage(messageBuilder.toString());
-
-        if (mIsClonedApp) {
-            uarBuilder.setTitle(mContext.getString(R.string.cloned_app_label, mTargetAppLabel));
-        } else {
-            uarBuilder.setTitle(mTargetAppLabel.toString());
-        }
-
-        boolean suggestToKeepAppData = false;
-        try {
-            PackageInfo pkgInfo = mPackageManager.getPackageInfo(mTargetPackageName, 0);
-            suggestToKeepAppData =
-                pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.hasFragileUserData();
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Cannot check hasFragileUserData for " + mTargetPackageName, e);
-        }
-
-        long appDataSize = 0;
-        if (suggestToKeepAppData) {
-            appDataSize = getAppDataSize(mTargetPackageName,
-                mUninstallFromAllUsers ? null : mUninstalledUser);
-        }
-        uarBuilder.setAppDataSize(appDataSize);
-
-        return uarBuilder.build();
-    }
-
-    /**
-     * Returns whether there is only one "full" user on this device.
-     *
-     * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
-     * headless system user mode}, the system user is not "full", so it's not be considered in the
-     * calculation.</p>
-     */
-    private boolean isSingleUser() {
-        final int userCount = mUserManager.getUserCount();
-        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
-    }
-
-    /**
-     * Returns the type of the user from where an app is being uninstalled. We are concerned with
-     * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
-     * belong to the same profile group.
-     */
-    @Nullable
-    private String getUninstalledUserType(UserHandle myUserHandle,
-        UserHandle uninstalledUserHandle) {
-        if (!mUserManager.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
-            return null;
-        }
-
-        UserManager customUserManager = mContext.createContextAsUser(uninstalledUserHandle, 0)
-            .getSystemService(UserManager.class);
-        String[] userTypes = {USER_TYPE_PROFILE_MANAGED, USER_TYPE_PROFILE_CLONE};
-        for (String userType : userTypes) {
-            if (customUserManager.isUserOfType(userType)) {
-                return userType;
-            }
-        }
-        return null;
-    }
-
-    private boolean hasClonedInstance(String packageName) {
-        // Check if clone user is present on the device.
-        UserHandle cloneUser = null;
-        List<UserHandle> profiles = mUserManager.getUserProfiles();
-        for (UserHandle userHandle : profiles) {
-            if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
-                cloneUser = userHandle;
-                break;
-            }
-        }
-        // Check if another instance of given package exists in clone user profile.
-        try {
-            return cloneUser != null
-                && mPackageManager.getPackageUidAsUser(packageName,
-                PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    private boolean isCloneProfile(UserHandle userHandle) {
-        UserManager customUserManager = mContext.createContextAsUser(userHandle, 0)
-            .getSystemService(UserManager.class);
-        return customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE);
-    }
-
-    /**
-     * Get number of bytes of the app data of the package.
-     *
-     * @param pkg The package that might have app data.
-     * @param user The user the package belongs to or {@code null} if files of all users should
-     *     be counted.
-     * @return The number of bytes.
-     */
-    private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
-        if (user != null) {
-            return getAppDataSizeForUser(pkg, user);
-        }
-        // We are uninstalling from all users. Get cumulative app data size for all users.
-        List<UserHandle> userHandles = mUserManager.getUserHandles(true);
-        long totalAppDataSize = 0;
-        int numUsers = userHandles.size();
-        for (int i = 0; i < numUsers; i++) {
-            totalAppDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
-        }
-        return totalAppDataSize;
-    }
-
-    /**
-     * Get number of bytes of the app data of the package.
-     *
-     * @param pkg The package that might have app data.
-     * @param user The user the package belongs to
-     * @return The number of bytes.
-     */
-    private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
-        StorageStatsManager storageStatsManager =
-            mContext.getSystemService(StorageStatsManager.class);
-        try {
-            StorageStats stats = storageStatsManager.queryStatsForPackage(
-                mPackageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user);
-            return stats.getDataBytes();
-        } catch (PackageManager.NameNotFoundException | IOException | SecurityException e) {
-            Log.e(TAG, "Cannot determine amount of app data for " + pkg, e);
-        }
-        return 0;
-    }
-
-    public void initiateUninstall(boolean keepData) {
-        // Get an uninstallId to track results and show a notification on non-TV devices.
-        try {
-            mUninstallId = UninstallEventReceiver.addObserver(mContext,
-                EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult);
-        } catch (EventResultPersister.OutOfIdsException e) {
-            Log.e(TAG, "Failed to start uninstall", e);
-            handleUninstallResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
-            return;
-        }
-
-        // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
-        mUninstallResult.setValue(new UninstallUninstalling(mTargetAppLabel, mIsClonedApp));
-
-        Bundle uninstallData = new Bundle();
-        uninstallData.putInt(EXTRA_UNINSTALL_ID, mUninstallId);
-        uninstallData.putString(EXTRA_PACKAGE_NAME, mTargetPackageName);
-        uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, mUninstallFromAllUsers);
-        uninstallData.putCharSequence(EXTRA_APP_LABEL, mTargetAppLabel);
-        uninstallData.putBoolean(EXTRA_IS_CLONE_APP, mIsClonedApp);
-        Log.i(TAG, "Uninstalling extras = " + uninstallData);
-
-        // Get a PendingIntent for result broadcast and issue an uninstall request
-        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
-        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
-        broadcastIntent.setPackage(mContext.getPackageName());
-
-        PendingIntent pendingIntent =
-            PendingIntent.getBroadcast(mContext, mUninstallId, broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
-        if (!startUninstall(mTargetPackageName, mUninstalledUser, pendingIntent,
-            mUninstallFromAllUsers, keepData)) {
-            handleUninstallResult(PackageInstaller.STATUS_FAILURE,
-                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
-        }
-    }
-
-    private void handleUninstallResult(int status, int legacyStatus, @Nullable String message,
-        int serviceId) {
-        if (mCallback != null) {
-            // The caller will be informed about the result via a callback
-            mCallback.onUninstallComplete(mTargetPackageName, legacyStatus, message);
-
-            // Since the caller already received the results, just finish the app at this point
-            mUninstallResult.setValue(null);
-            return;
-        }
-
-        boolean returnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-        if (returnResult || mCallingActivity != null) {
-            Intent intent = new Intent();
-            intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
-
-            if (status == PackageInstaller.STATUS_SUCCESS) {
-                UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
-                    .setResultIntent(intent)
-                    .setActivityResultCode(Activity.RESULT_OK);
-                mUninstallResult.setValue(successBuilder.build());
-            } else {
-                UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(true)
-                    .setResultIntent(intent)
-                    .setActivityResultCode(Activity.RESULT_FIRST_USER);
-                mUninstallResult.setValue(failedBuilder.build());
-            }
-            return;
-        }
-
-        // Caller did not want the result back. So, we either show a Toast, or a Notification.
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
-                .setActivityResultCode(legacyStatus)
-                .setMessage(mIsClonedApp
-                    ? mContext.getString(R.string.uninstall_done_clone_app, mTargetAppLabel)
-                    : mContext.getString(R.string.uninstall_done_app, mTargetAppLabel));
-            mUninstallResult.setValue(successBuilder.build());
-        } else {
-            UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(false);
-            Notification.Builder uninstallFailedNotification = null;
-
-            NotificationChannel uninstallFailureChannel = new NotificationChannel(
-                UNINSTALL_FAILURE_CHANNEL,
-                mContext.getString(R.string.uninstall_failure_notification_channel),
-                NotificationManager.IMPORTANCE_DEFAULT);
-            mNotificationManager.createNotificationChannel(uninstallFailureChannel);
-
-            uninstallFailedNotification = new Notification.Builder(mContext,
-                UNINSTALL_FAILURE_CHANNEL);
-
-            UserHandle myUserHandle = Process.myUserHandle();
-            switch (legacyStatus) {
-                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
-                    // Find out if the package is an active admin for some non-current user.
-                    UserHandle otherBlockingUserHandle =
-                        findUserOfDeviceAdmin(myUserHandle, mTargetPackageName);
-
-                    if (otherBlockingUserHandle == null) {
-                        Log.d(TAG, "Uninstall failed because " + mTargetPackageName
-                            + " is a device admin");
-
-                        addDeviceManagerButton(mContext, uninstallFailedNotification);
-                        setBigText(uninstallFailedNotification, mContext.getString(
-                            R.string.uninstall_failed_device_policy_manager));
-                    } else {
-                        Log.d(TAG, "Uninstall failed because " + mTargetPackageName
-                            + " is a device admin of user " + otherBlockingUserHandle);
-
-                        String userName =
-                            mContext.createContextAsUser(otherBlockingUserHandle, 0)
-                                .getSystemService(UserManager.class).getUserName();
-                        setBigText(uninstallFailedNotification, String.format(
-                            mContext.getString(
-                                R.string.uninstall_failed_device_policy_manager_of_user),
-                            userName));
-                    }
-                }
-                case PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
-                    UserHandle otherBlockingUserHandle = findBlockingUser(mTargetPackageName);
-                    boolean isProfileOfOrSame = isProfileOfOrSame(mUserManager, myUserHandle,
-                        otherBlockingUserHandle);
-
-                    if (isProfileOfOrSame) {
-                        addDeviceManagerButton(mContext, uninstallFailedNotification);
-                    } else {
-                        addManageUsersButton(mContext, uninstallFailedNotification);
-                    }
-
-                    String bigText = null;
-                    if (otherBlockingUserHandle == null) {
-                        Log.d(TAG, "Uninstall failed for " + mTargetPackageName +
-                            " with code " + status + " no blocking user");
-                    } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
-                        bigText = mContext.getString(
-                            R.string.uninstall_blocked_device_owner);
-                    } else {
-                        bigText = mContext.getString(mUninstallFromAllUsers ?
-                            R.string.uninstall_all_blocked_profile_owner
-                            : R.string.uninstall_blocked_profile_owner);
-                    }
-                    if (bigText != null) {
-                        setBigText(uninstallFailedNotification, bigText);
-                    }
-                }
-                default -> {
-                    Log.d(TAG, "Uninstall blocked for " + mTargetPackageName
-                        + " with legacy code " + legacyStatus);
-                }
-            }
-
-            uninstallFailedNotification.setContentTitle(
-                mContext.getString(R.string.uninstall_failed_app, mTargetAppLabel));
-            uninstallFailedNotification.setOngoing(false);
-            uninstallFailedNotification.setSmallIcon(R.drawable.ic_error);
-            failedBuilder.setUninstallNotification(mUninstallId,
-                uninstallFailedNotification.build());
-
-            mUninstallResult.setValue(failedBuilder.build());
-        }
-    }
-
-    /**
-     * @param myUserHandle {@link UserHandle} of the current user.
-     * @param packageName Name of the package being uninstalled.
-     * @return the {@link UserHandle} of the user in which a package is a device admin.
-     */
-    @Nullable
-    private UserHandle findUserOfDeviceAdmin(UserHandle myUserHandle, String packageName) {
-        for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
-            // We only catch the case when the user in question is neither the
-            // current user nor its profile.
-            if (isProfileOfOrSame(mUserManager, myUserHandle, otherUserHandle)) {
-                continue;
-            }
-            DevicePolicyManager dpm = mContext.createContextAsUser(otherUserHandle, 0)
-                    .getSystemService(DevicePolicyManager.class);
-            if (dpm.packageHasActiveAdmins(packageName)) {
-                return otherUserHandle;
-            }
-        }
-        return null;
-    }
-
-    /**
-     *
-     * @param packageName Name of the package being uninstalled.
-     * @return {@link UserHandle} of the user in which a package is blocked from being uninstalled.
-     */
-    @Nullable
-    private UserHandle findBlockingUser(String packageName) {
-        for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
-            // TODO (b/307399586): Add a negation when the logic of the method
-            //  is fixed
-            if (mPackageManager.canUserUninstall(packageName, otherUserHandle)) {
-                return otherUserHandle;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Set big text for the notification.
-     *
-     * @param builder The builder of the notification
-     * @param text The text to set.
-     */
-    private void setBigText(@NonNull Notification.Builder builder,
-        @NonNull CharSequence text) {
-        builder.setStyle(new Notification.BigTextStyle().bigText(text));
-    }
-
-    /**
-     * Add a button to the notification that links to the user management.
-     *
-     * @param context The context the notification is created in
-     * @param builder The builder of the notification
-     */
-    private void addManageUsersButton(@NonNull Context context,
-        @NonNull Notification.Builder builder) {
-        builder.addAction((new Notification.Action.Builder(
-            Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
-            context.getString(R.string.manage_users),
-            PendingIntent.getActivity(context, 0, getUserSettingsIntent(),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
-    }
-
-    private Intent getUserSettingsIntent() {
-        Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
-
-    /**
-     * Add a button to the notification that links to the device policy management.
-     *
-     * @param context The context the notification is created in
-     * @param builder The builder of the notification
-     */
-    private void addDeviceManagerButton(@NonNull Context context,
-        @NonNull Notification.Builder builder) {
-        builder.addAction((new Notification.Action.Builder(
-            Icon.createWithResource(context, R.drawable.ic_lock),
-            context.getString(R.string.manage_device_administrators),
-            PendingIntent.getActivity(context, 0, getDeviceManagerIntent(),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
-    }
-
-    private Intent getDeviceManagerIntent() {
-        Intent intent = new Intent();
-        intent.setClassName("com.android.settings",
-            "com.android.settings.Settings$DeviceAdminSettingsActivity");
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
-
-    /**
-     * Starts an uninstall for the given package.
-     *
-     * @return {@code true} if there was no exception while uninstalling. This does not represent
-     *     the result of the uninstall. Result will be made available in
-     *     {@link #handleUninstallResult(int, int, String, int)}
-     */
-    private boolean startUninstall(String packageName, UserHandle targetUser,
-        PendingIntent pendingIntent, boolean uninstallFromAllUsers, boolean keepData) {
-        int flags = uninstallFromAllUsers ? PackageManager.DELETE_ALL_USERS : 0;
-        flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
-        try {
-            mContext.createContextAsUser(targetUser, 0)
-                .getPackageManager().getPackageInstaller().uninstall(
-                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
-                    flags, pendingIntent.getIntentSender());
-            return true;
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Failed to uninstall", e);
-            return false;
-        }
-    }
-
-    public void cancelInstall() {
-        if (mCallback != null) {
-            mCallback.onUninstallComplete(mTargetPackageName,
-                PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
-        }
-    }
-
-    public MutableLiveData<UninstallStage> getUninstallResult() {
-        return mUninstallResult;
-    }
-
-    public static class CallerInfo {
-
-        private final String mActivityName;
-        private final int mUid;
-
-        public CallerInfo(String activityName, int uid) {
-            mActivityName = activityName;
-            mUid = uid;
-        }
-
-        public String getActivityName() {
-            return mActivityName;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
new file mode 100644
index 0000000..7cc95c5
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -0,0 +1,739 @@
+/*
+ * 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
+ *
+ *      https://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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.app.usage.StorageStatsManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.pm.VersionedPackage
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.UninstallEventReceiver
+import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame
+
+class UninstallRepository(private val context: Context) {
+
+    private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+    private val packageManager: PackageManager = context.packageManager
+    private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+    private val notificationManager: NotificationManager? =
+        context.getSystemService(NotificationManager::class.java)
+    val uninstallResult = MutableLiveData<UninstallStage?>()
+    private var uninstalledUser: UserHandle? = null
+    private var callback: PackageManager.UninstallCompleteCallback? = null
+    private var targetAppInfo: ApplicationInfo? = null
+    private var targetActivityInfo: ActivityInfo? = null
+    private lateinit var intent: Intent
+    private lateinit var targetAppLabel: CharSequence
+    private var targetPackageName: String? = null
+    private var callingActivity: String? = null
+    private var uninstallFromAllUsers = false
+    private var isClonedApp = false
+    private var uninstallId = 0
+
+    fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage {
+        this.intent = intent
+
+        val callingUid = callerInfo.uid
+        callingActivity = callerInfo.activityName
+
+        if (callingUid == Process.INVALID_UID) {
+            Log.e(LOG_TAG, "Could not determine the launching uid.")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+            // TODO: should we give any indication to the user?
+        }
+
+        val callingPackage = getPackageNameForUid(context, callingUid, null)
+        if (callingPackage == null) {
+            Log.e(LOG_TAG, "Package not found for originating uid $callingUid")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+        } else {
+            if (appOpsManager!!.noteOpNoThrow(
+                    AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage
+                ) != AppOpsManager.MODE_ALLOWED
+            ) {
+                Log.e(LOG_TAG, "Install from uid $callingUid disallowed by AppOps")
+                return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+            }
+        }
+
+        if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P
+            && !isPermissionGranted(
+                context, Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid
+            )
+            && !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
+        ) {
+            Log.e(
+                LOG_TAG, "Uid " + callingUid + " does not have "
+                    + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+                    + Manifest.permission.DELETE_PACKAGES
+            )
+            return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+        }
+
+        // Get intent information.
+        // We expect an intent with URI of the form package:<packageName>#<className>
+        // className is optional; if specified, it is the activity the user chose to uninstall
+        val packageUri = intent.data
+        if (packageUri == null) {
+            Log.e(LOG_TAG, "No package URI in intent")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+        }
+        targetPackageName = packageUri.encodedSchemeSpecificPart
+        if (targetPackageName == null) {
+            Log.e(LOG_TAG, "Invalid package name in URI: $packageUri")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+        }
+
+        uninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false)
+        if (uninstallFromAllUsers && !userManager!!.isAdminUser) {
+            Log.e(LOG_TAG, "Only admin user can request uninstall for all users")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+        }
+
+        uninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
+        if (uninstalledUser == null) {
+            uninstalledUser = Process.myUserHandle()
+        } else {
+            val profiles = userManager!!.userProfiles
+            if (!profiles.contains(uninstalledUser)) {
+                Log.e(
+                    LOG_TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+                        + "for user " + uninstalledUser
+                )
+                return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+            }
+        }
+
+        callback = intent.getParcelableExtra(
+            PackageInstaller.EXTRA_CALLBACK, PackageManager.UninstallCompleteCallback::class.java
+        )
+
+        try {
+            targetAppInfo = packageManager.getApplicationInfo(
+                targetPackageName!!,
+                PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong())
+            )
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(LOG_TAG, "Unable to get packageName")
+        }
+
+        if (targetAppInfo == null) {
+            Log.e(LOG_TAG, "Invalid packageName: $targetPackageName")
+            return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+        }
+
+        // The class name may have been specified (e.g. when deleting an app from all apps)
+        val className = packageUri.fragment
+        if (className != null) {
+            try {
+                targetActivityInfo = packageManager.getActivityInfo(
+                    ComponentName(targetPackageName!!, className),
+                    PackageManager.ComponentInfoFlags.of(0)
+                )
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(LOG_TAG, "Unable to get className")
+                // Continue as the ActivityInfo isn't critical.
+            }
+        }
+
+        return UninstallReady()
+    }
+
+    fun generateUninstallDetails(): UninstallStage {
+        val messageBuilder = StringBuilder()
+
+        targetAppLabel = targetAppInfo!!.loadSafeLabel(packageManager)
+
+        // If the Activity label differs from the App label, then make sure the user
+        // knows the Activity belongs to the App being uninstalled.
+        if (targetActivityInfo != null) {
+            val activityLabel = targetActivityInfo!!.loadSafeLabel(packageManager)
+            if (!activityLabel.contentEquals(targetAppLabel)) {
+                messageBuilder.append(
+                    context.getString(R.string.uninstall_activity_text, activityLabel)
+                )
+                messageBuilder.append(" ").append(targetAppLabel).append(".\n\n")
+            }
+        }
+
+        val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+        val myUserHandle = Process.myUserHandle()
+        val isSingleUser = isSingleUser()
+
+        if (isUpdate) {
+            messageBuilder.append(context.getString(
+                    if (isSingleUser) R.string.uninstall_update_text
+                    else R.string.uninstall_update_text_multiuser
+                )
+            )
+        } else if (uninstallFromAllUsers && !isSingleUser) {
+            messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users))
+        } else if (uninstalledUser != myUserHandle) {
+            // Uninstalling user is issuing uninstall for another user
+            val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
+                .getSystemService(UserManager::class.java)
+            val userName = customUserManager!!.userName
+
+            val uninstalledUserType = getUninstalledUserType(myUserHandle, uninstalledUser!!)
+            val messageString: String
+            when (uninstalledUserType) {
+                UserManager.USER_TYPE_PROFILE_MANAGED -> {
+                    messageString = context.getString(
+                        R.string.uninstall_application_text_current_user_work_profile, userName
+                    )
+                }
+
+                UserManager.USER_TYPE_PROFILE_CLONE -> {
+                    isClonedApp = true
+                    messageString = context.getString(
+                        R.string.uninstall_application_text_current_user_clone_profile
+                    )
+                }
+
+                else -> {
+                    messageString = context.getString(
+                        R.string.uninstall_application_text_user, userName
+                    )
+                }
+
+            }
+            messageBuilder.append(messageString)
+        } else if (isCloneProfile(uninstalledUser!!)) {
+            isClonedApp = true
+            messageBuilder.append(context.getString(
+                    R.string.uninstall_application_text_current_user_clone_profile
+                )
+            )
+        } else if (myUserHandle == UserHandle.SYSTEM
+            && hasClonedInstance(targetAppInfo!!.packageName)
+        ) {
+            messageBuilder.append(context.getString(
+                    R.string.uninstall_application_text_with_clone_instance, targetAppLabel
+                )
+            )
+        } else {
+            messageBuilder.append(context.getString(R.string.uninstall_application_text))
+        }
+
+        val message = messageBuilder.toString()
+
+        val title = if (isClonedApp) {
+            context.getString(R.string.cloned_app_label, targetAppLabel)
+        } else {
+            targetAppLabel.toString()
+        }
+
+        var suggestToKeepAppData = false
+        try {
+            val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0)
+            suggestToKeepAppData =
+                pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData()
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e)
+        }
+
+        var appDataSize: Long = 0
+        if (suggestToKeepAppData) {
+            appDataSize = getAppDataSize(
+                targetPackageName!!,
+                if (uninstallFromAllUsers) null else uninstalledUser
+            )
+        }
+
+        return UninstallUserActionRequired(title, message, appDataSize)
+    }
+
+    /**
+     * Returns whether there is only one "full" user on this device.
+     *
+     * **Note:** On devices that use [headless system user mode]
+     * [android.os.UserManager.isHeadlessSystemUserMode], the system user is not "full",
+     * so it's not be considered in the calculation.
+     */
+    private fun isSingleUser(): Boolean {
+        val userCount = userManager!!.userCount
+        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2)
+    }
+
+    /**
+     * Returns the type of the user from where an app is being uninstalled. We are concerned with
+     * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
+     * belong to the same profile group.
+     */
+    private fun getUninstalledUserType(
+        myUserHandle: UserHandle,
+        uninstalledUserHandle: UserHandle
+    ): String? {
+        if (!userManager!!.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
+            return null
+        }
+        val customUserManager = context.createContextAsUser(uninstalledUserHandle, 0)
+            .getSystemService(UserManager::class.java)
+        val userTypes =
+            arrayOf(UserManager.USER_TYPE_PROFILE_MANAGED, UserManager.USER_TYPE_PROFILE_CLONE)
+
+        for (userType in userTypes) {
+            if (customUserManager!!.isUserOfType(userType)) {
+                return userType
+            }
+        }
+        return null
+    }
+
+    private fun hasClonedInstance(packageName: String): Boolean {
+        // Check if clone user is present on the device.
+        var cloneUser: UserHandle? = null
+        val profiles = userManager!!.userProfiles
+
+        for (userHandle in profiles) {
+            if (userHandle != UserHandle.SYSTEM && isCloneProfile(userHandle)) {
+                cloneUser = userHandle
+                break
+            }
+        }
+        // Check if another instance of given package exists in clone user profile.
+        return try {
+            cloneUser != null
+                && packageManager.getPackageUidAsUser(
+                packageName, PackageManager.PackageInfoFlags.of(0), cloneUser.identifier
+                ) > 0
+        } catch (e: PackageManager.NameNotFoundException) {
+            false
+        }
+    }
+
+    private fun isCloneProfile(userHandle: UserHandle): Boolean {
+        val customUserManager = context.createContextAsUser(userHandle, 0)
+            .getSystemService(UserManager::class.java)
+        return customUserManager!!.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)
+    }
+
+    /**
+     * Get number of bytes of the app data of the package.
+     *
+     * @param pkg The package that might have app data.
+     * @param user The user the package belongs to or `null` if files of all users should
+     * be counted.
+     * @return The number of bytes.
+     */
+    private fun getAppDataSize(pkg: String, user: UserHandle?): Long {
+        if (user != null) {
+            return getAppDataSizeForUser(pkg, user)
+        }
+        // We are uninstalling from all users. Get cumulative app data size for all users.
+        val userHandles = userManager!!.getUserHandles(true)
+        var totalAppDataSize: Long = 0
+        val numUsers = userHandles.size
+        for (i in 0 until numUsers) {
+            totalAppDataSize += getAppDataSizeForUser(pkg, userHandles[i])
+        }
+        return totalAppDataSize
+    }
+
+    /**
+     * Get number of bytes of the app data of the package.
+     *
+     * @param pkg The package that might have app data.
+     * @param user The user the package belongs to
+     * @return The number of bytes.
+     */
+    private fun getAppDataSizeForUser(pkg: String, user: UserHandle): Long {
+        val storageStatsManager = context.getSystemService(StorageStatsManager::class.java)
+        try {
+            val stats = storageStatsManager!!.queryStatsForPackage(
+                packageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user
+            )
+            return stats.getDataBytes()
+        } catch (e: Exception) {
+            Log.e(LOG_TAG, "Cannot determine amount of app data for $pkg", e)
+        }
+        return 0
+    }
+
+    fun initiateUninstall(keepData: Boolean) {
+        // Get an uninstallId to track results and show a notification on non-TV devices.
+        uninstallId = try {
+            UninstallEventReceiver.addObserver(
+                context, EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult
+            )
+        } catch (e: OutOfIdsException) {
+            Log.e(LOG_TAG, "Failed to start uninstall", e)
+            handleUninstallResult(
+                PackageInstaller.STATUS_FAILURE,
+                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+            )
+            return
+        }
+
+        // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
+        uninstallResult.value = UninstallUninstalling(targetAppLabel, isClonedApp)
+
+        val uninstallData = Bundle()
+        uninstallData.putInt(EXTRA_UNINSTALL_ID, uninstallId)
+        uninstallData.putString(EXTRA_PACKAGE_NAME, targetPackageName)
+        uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, uninstallFromAllUsers)
+        uninstallData.putCharSequence(EXTRA_APP_LABEL, targetAppLabel)
+        uninstallData.putBoolean(EXTRA_IS_CLONE_APP, isClonedApp)
+        Log.i(LOG_TAG, "Uninstalling extras = $uninstallData")
+
+        // Get a PendingIntent for result broadcast and issue an uninstall request
+        val broadcastIntent = Intent(BROADCAST_ACTION)
+        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId)
+        broadcastIntent.setPackage(context.packageName)
+        val pendingIntent = PendingIntent.getBroadcast(
+            context, uninstallId, broadcastIntent,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+        )
+        if (!startUninstall(
+                targetPackageName!!, uninstalledUser!!, pendingIntent, uninstallFromAllUsers,
+                keepData
+            )
+        ) {
+            handleUninstallResult(
+                PackageInstaller.STATUS_FAILURE,
+                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+            )
+        }
+    }
+
+    private fun handleUninstallResult(
+        status: Int,
+        legacyStatus: Int,
+        message: String?,
+        serviceId: Int
+    ) {
+        if (callback != null) {
+            // The caller will be informed about the result via a callback
+            callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message)
+
+            // Since the caller already received the results, just finish the app at this point
+            uninstallResult.value = null
+            return
+        }
+        val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+        if (returnResult || callingActivity != null) {
+            val intent = Intent()
+            intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                uninstallResult.setValue(
+                    UninstallSuccess(resultIntent = intent, activityResultCode = Activity.RESULT_OK)
+                )
+            } else {
+                uninstallResult.setValue(
+                    UninstallFailed(
+                        returnResult = true,
+                        resultIntent = intent,
+                        activityResultCode = Activity.RESULT_FIRST_USER
+                    )
+                )
+            }
+            return
+        }
+
+        // Caller did not want the result back. So, we either show a Toast, or a Notification.
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            val statusMessage = if (isClonedApp) context.getString(
+                R.string.uninstall_done_clone_app, targetAppLabel
+            ) else context.getString(R.string.uninstall_done_app, targetAppLabel)
+            uninstallResult.setValue(
+                UninstallSuccess(activityResultCode = legacyStatus, message = statusMessage)
+            )
+        } else {
+            val uninstallFailureChannel = NotificationChannel(
+                UNINSTALL_FAILURE_CHANNEL,
+                context.getString(R.string.uninstall_failure_notification_channel),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            notificationManager!!.createNotificationChannel(uninstallFailureChannel)
+
+            val uninstallFailedNotification: Notification.Builder =
+                Notification.Builder(context, UNINSTALL_FAILURE_CHANNEL)
+
+            val myUserHandle = Process.myUserHandle()
+            when (legacyStatus) {
+                PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
+                    // Find out if the package is an active admin for some non-current user.
+                    val otherBlockingUserHandle =
+                        findUserOfDeviceAdmin(myUserHandle, targetPackageName!!)
+                    if (otherBlockingUserHandle == null) {
+                        Log.d(
+                            LOG_TAG, "Uninstall failed because $targetPackageName"
+                                + " is a device admin"
+                        )
+                        addDeviceManagerButton(context, uninstallFailedNotification)
+                        setBigText(
+                            uninstallFailedNotification, context.getString(
+                                R.string.uninstall_failed_device_policy_manager
+                            )
+                        )
+                    } else {
+                        Log.d(
+                            LOG_TAG, "Uninstall failed because $targetPackageName"
+                                + " is a device admin of user $otherBlockingUserHandle"
+                        )
+                        val userName = context.createContextAsUser(otherBlockingUserHandle, 0)
+                            .getSystemService(UserManager::class.java)!!.userName
+                        setBigText(
+                            uninstallFailedNotification, String.format(
+                                context.getString(
+                                    R.string.uninstall_failed_device_policy_manager_of_user
+                                ), userName
+                            )
+                        )
+                    }
+                }
+
+                PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
+                    val otherBlockingUserHandle = findBlockingUser(targetPackageName!!)
+                    val isProfileOfOrSame = isProfileOfOrSame(
+                        userManager!!, myUserHandle, otherBlockingUserHandle
+                    )
+                    if (isProfileOfOrSame) {
+                        addDeviceManagerButton(context, uninstallFailedNotification)
+                    } else {
+                        addManageUsersButton(context, uninstallFailedNotification)
+                    }
+                    var bigText: String? = null
+                    if (otherBlockingUserHandle == null) {
+                        Log.d(
+                            LOG_TAG, "Uninstall failed for $targetPackageName " +
+                                "with code $status no blocking user"
+                        )
+                    } else if (otherBlockingUserHandle === UserHandle.SYSTEM) {
+                        bigText = context.getString(R.string.uninstall_blocked_device_owner)
+                    } else {
+                        bigText = context.getString(
+                            if (uninstallFromAllUsers) R.string.uninstall_all_blocked_profile_owner
+                            else R.string.uninstall_blocked_profile_owner
+                        )
+                    }
+                    bigText?.let { setBigText(uninstallFailedNotification, it) }
+                }
+
+                else -> {
+                    Log.d(
+                        LOG_TAG, "Uninstall blocked for $targetPackageName"
+                            + " with legacy code $legacyStatus"
+                    )
+                }
+            }
+            uninstallFailedNotification.setContentTitle(
+                context.getString(R.string.uninstall_failed_app, targetAppLabel)
+            )
+            uninstallFailedNotification.setOngoing(false)
+            uninstallFailedNotification.setSmallIcon(R.drawable.ic_error)
+
+            uninstallResult.setValue(
+                UninstallFailed(
+                    returnResult = false,
+                    uninstallNotificationId = uninstallId,
+                    uninstallNotification = uninstallFailedNotification.build()
+                )
+            )
+        }
+    }
+
+    /**
+     * @param myUserHandle [UserHandle] of the current user.
+     * @param packageName Name of the package being uninstalled.
+     * @return the [UserHandle] of the user in which a package is a device admin.
+     */
+    private fun findUserOfDeviceAdmin(myUserHandle: UserHandle, packageName: String): UserHandle? {
+        for (otherUserHandle in userManager!!.getUserHandles(true)) {
+            // We only catch the case when the user in question is neither the
+            // current user nor its profile.
+            if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+                continue
+            }
+            val dpm = context.createContextAsUser(otherUserHandle, 0)
+                .getSystemService(DevicePolicyManager::class.java)
+            if (dpm!!.packageHasActiveAdmins(packageName)) {
+                return otherUserHandle
+            }
+        }
+        return null
+    }
+
+    /**
+     *
+     * @param packageName Name of the package being uninstalled.
+     * @return [UserHandle] of the user in which a package is blocked from being uninstalled.
+     */
+    private fun findBlockingUser(packageName: String): UserHandle? {
+        for (otherUserHandle in userManager!!.getUserHandles(true)) {
+            // TODO (b/307399586): Add a negation when the logic of the method is fixed
+            if (packageManager.canUserUninstall(packageName, otherUserHandle)) {
+                return otherUserHandle
+            }
+        }
+        return null
+    }
+
+    /**
+     * Set big text for the notification.
+     *
+     * @param builder The builder of the notification
+     * @param text The text to set.
+     */
+    private fun setBigText(
+        builder: Notification.Builder,
+        text: CharSequence
+    ) {
+        builder.setStyle(Notification.BigTextStyle().bigText(text))
+    }
+
+    /**
+     * Add a button to the notification that links to the user management.
+     *
+     * @param context The context the notification is created in
+     * @param builder The builder of the notification
+     */
+    private fun addManageUsersButton(
+        context: Context,
+        builder: Notification.Builder
+    ) {
+        builder.addAction(
+            Notification.Action.Builder(
+                Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
+                context.getString(R.string.manage_users),
+                PendingIntent.getActivity(
+                    context, 0, getUserSettingsIntent(),
+                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                )
+            )
+                .build()
+        )
+    }
+
+    private fun getUserSettingsIntent(): Intent {
+        val intent = Intent(Settings.ACTION_USER_SETTINGS)
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+        return intent
+    }
+
+    /**
+     * Add a button to the notification that links to the device policy management.
+     *
+     * @param context The context the notification is created in
+     * @param builder The builder of the notification
+     */
+    private fun addDeviceManagerButton(
+        context: Context,
+        builder: Notification.Builder
+    ) {
+        builder.addAction(
+            Notification.Action.Builder(
+                Icon.createWithResource(context, R.drawable.ic_lock),
+                context.getString(R.string.manage_device_administrators),
+                PendingIntent.getActivity(
+                    context, 0, getDeviceManagerIntent(),
+                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                )
+            )
+                .build()
+        )
+    }
+
+    private fun getDeviceManagerIntent(): Intent {
+        val intent = Intent()
+        intent.setClassName(
+            "com.android.settings",
+            "com.android.settings.Settings\$DeviceAdminSettingsActivity"
+        )
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+        return intent
+    }
+
+    /**
+     * Starts an uninstall for the given package.
+     *
+     * @return `true` if there was no exception while uninstalling. This does not represent
+     * the result of the uninstall. Result will be made available in [handleUninstallResult]
+     */
+    private fun startUninstall(
+        packageName: String,
+        targetUser: UserHandle,
+        pendingIntent: PendingIntent,
+        uninstallFromAllUsers: Boolean,
+        keepData: Boolean
+    ): Boolean {
+        var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0
+        flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0
+
+        return try {
+            context.createContextAsUser(targetUser, 0)
+                .packageManager.packageInstaller.uninstall(
+                    VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    flags, pendingIntent.intentSender
+                )
+            true
+        } catch (e: IllegalArgumentException) {
+            Log.e(LOG_TAG, "Failed to uninstall", e)
+            false
+        }
+    }
+
+    fun cancelInstall() {
+        if (callback != null) {
+            callback!!.onUninstallComplete(
+                targetPackageName!!,
+                PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"
+            )
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = UninstallRepository::class.java.simpleName
+        private const val UNINSTALL_FAILURE_CHANNEL = "uninstall_failure"
+        private const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"
+        private const val EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"
+        private const val EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"
+        private const val EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP"
+        private const val EXTRA_PACKAGE_NAME =
+            "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME"
+    }
+
+    class CallerInfo(val activityName: String?, val uid: Int)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
new file mode 100644
index 0000000..f086209
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
@@ -0,0 +1,112 @@
+/*
+ * 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
+ *
+ *      https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.app.Notification
+import android.content.Intent
+import com.android.packageinstaller.R
+
+sealed class UninstallStage(val stageCode: Int) {
+
+    companion object {
+        const val STAGE_DEFAULT = -1
+        const val STAGE_ABORTED = 0
+        const val STAGE_READY = 1
+        const val STAGE_USER_ACTION_REQUIRED = 2
+        const val STAGE_UNINSTALLING = 3
+        const val STAGE_SUCCESS = 4
+        const val STAGE_FAILED = 5
+    }
+}
+
+class UninstallReady : UninstallStage(STAGE_READY)
+
+data class UninstallUserActionRequired(
+    val title: String? = null,
+    val message: String? = null,
+    val appDataSize: Long = 0
+) : UninstallStage(STAGE_USER_ACTION_REQUIRED)
+
+data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) :
+    UninstallStage(STAGE_UNINSTALLING)
+
+data class UninstallSuccess(
+    val resultIntent: Intent? = null,
+    val activityResultCode: Int = 0,
+    val message: String? = null,
+) : UninstallStage(STAGE_SUCCESS)
+
+data class UninstallFailed(
+    val returnResult: Boolean,
+    /**
+     * If the caller wants the result back, the intent will hold the uninstall failure status code
+     * and legacy code.
+     */
+    val resultIntent: Intent? = null,
+    val activityResultCode: Int = Activity.RESULT_CANCELED,
+    /**
+     * ID used to show [uninstallNotification]
+     */
+    val uninstallNotificationId: Int? = null,
+    /**
+     * When the user does not request a result back, this notification will be shown indicating the
+     * reason for uninstall failure.
+     */
+    val uninstallNotification: Notification? = null,
+) : UninstallStage(STAGE_FAILED) {
+
+    init {
+        if (uninstallNotification != null && uninstallNotificationId == null) {
+            throw IllegalArgumentException(
+                "uninstallNotification cannot be set without uninstallNotificationId"
+            )
+        }
+    }
+}
+
+data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED) {
+
+    var dialogTitleResource = 0
+    var dialogTextResource = 0
+    val activityResultCode = Activity.RESULT_FIRST_USER
+
+    init {
+        when (abortReason) {
+            ABORT_REASON_APP_UNAVAILABLE -> {
+                dialogTitleResource = R.string.app_not_found_dlg_title
+                dialogTextResource = R.string.app_not_found_dlg_text
+            }
+
+            ABORT_REASON_USER_NOT_ALLOWED -> {
+                dialogTitleResource = 0
+                dialogTextResource = R.string.user_is_not_allowed_dlg_text
+            }
+
+            else -> {
+                dialogTitleResource = 0
+                dialogTextResource = R.string.generic_error_dlg_text
+            }
+        }
+    }
+
+    companion object {
+        const val ABORT_REASON_GENERIC_ERROR = 0
+        const val ABORT_REASON_APP_UNAVAILABLE = 1
+        const val ABORT_REASON_USER_NOT_ALLOWED = 2
+    }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
deleted file mode 100644
index 520b6c5..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class InstallAborted extends InstallStage {
-
-    public static final int ABORT_REASON_INTERNAL_ERROR = 0;
-    public static final int ABORT_REASON_POLICY = 1;
-    public static final int ABORT_REASON_DONE = 2;
-    public static final int DLG_PACKAGE_ERROR = 1;
-    private final int mStage = InstallStage.STAGE_ABORTED;
-    private final int mAbortReason;
-
-    /**
-     * It will hold the restriction name, when the restriction was enforced by the system, and not
-     * a device admin.
-     */
-    @NonNull
-    private final String mMessage;
-    /**
-     * <p>If abort reason is ABORT_REASON_POLICY, then this will hold the Intent
-     * to display a support dialog when a feature was disabled by an admin. It will be
-     * {@code null} if the feature is disabled by the system. In this case, the restriction name
-     * will be set in {@link #mMessage} </p>
-     *
-     * <p>If the abort reason is ABORT_REASON_INTERNAL_ERROR, it <b>may</b> hold an
-     * intent to be sent as a result to the calling activity.</p>
-     */
-    @Nullable
-    private final Intent mIntent;
-    private final int mErrorDialogType;
-    private final int mActivityResultCode;
-
-    private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent,
-        int activityResultCode, int errorDialogType) {
-        mAbortReason = reason;
-        mMessage = message;
-        mIntent = intent;
-        mErrorDialogType = errorDialogType;
-        mActivityResultCode = activityResultCode;
-    }
-
-    public int getAbortReason() {
-        return mAbortReason;
-    }
-
-    @NonNull
-    public String getMessage() {
-        return mMessage;
-    }
-
-    @Nullable
-    public Intent getResultIntent() {
-        return mIntent;
-    }
-
-    public int getErrorDialogType() {
-        return mErrorDialogType;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private final int mAbortReason;
-        private String mMessage = "";
-        private Intent mIntent = null;
-        private int mActivityResultCode = Activity.RESULT_CANCELED;
-        private int mErrorDialogType;
-
-        public Builder(int reason) {
-            mAbortReason = reason;
-        }
-
-        public Builder setMessage(@NonNull String message) {
-            mMessage = message;
-            return this;
-        }
-
-        public Builder setResultIntent(@NonNull Intent intent) {
-            mIntent = intent;
-            return this;
-        }
-
-        public Builder setErrorDialogType(int dialogType) {
-            mErrorDialogType = dialogType;
-            return this;
-        }
-
-        public Builder setActivityResultCode(int resultCode) {
-            mActivityResultCode = resultCode;
-            return this;
-        }
-
-        public InstallAborted build() {
-            return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode,
-                mErrorDialogType);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
deleted file mode 100644
index 67e1690..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallFailed extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_FAILED;
-    @NonNull
-    private final AppSnippet mAppSnippet;
-    private final int mStatusCode;
-    private final int mLegacyCode;
-    @Nullable
-    private final String mMessage;
-
-    public InstallFailed(@NonNull AppSnippet appSnippet, int statusCode, int legacyCode,
-        @Nullable String message) {
-        mAppSnippet = appSnippet;
-        mLegacyCode = statusCode;
-        mStatusCode = legacyCode;
-        mMessage = message;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @NonNull
-    public Drawable getAppIcon() {
-        return mAppSnippet.getIcon();
-    }
-
-    @NonNull
-    public String getAppLabel() {
-        return (String) mAppSnippet.getLabel();
-    }
-
-    public int getStatusCode() {
-        return mStatusCode;
-    }
-
-    public int getLegacyCode() {
-        return mLegacyCode;
-    }
-
-    @Nullable
-    public String getMessage() {
-        return mMessage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
deleted file mode 100644
index efd4947..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallInstalling extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_INSTALLING;
-    @NonNull
-    private final AppSnippet mAppSnippet;
-
-    public InstallInstalling(@NonNull AppSnippet appSnippet) {
-        mAppSnippet = appSnippet;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @NonNull
-    public Drawable getAppIcon() {
-        return mAppSnippet.getIcon();
-    }
-
-    @NonNull
-    public String getAppLabel() {
-        return (String) mAppSnippet.getLabel();
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
deleted file mode 100644
index 548f2c5..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.installstagedata;
-
-public class InstallReady extends InstallStage{
-
-    private final int mStage = InstallStage.STAGE_READY;
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
deleted file mode 100644
index f91e64b..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-public abstract class InstallStage {
-
-    public static final int STAGE_DEFAULT = -1;
-    public static final int STAGE_ABORTED = 0;
-    public static final int STAGE_STAGING = 1;
-    public static final int STAGE_READY = 2;
-    public static final int STAGE_USER_ACTION_REQUIRED = 3;
-    public static final int STAGE_INSTALLING = 4;
-    public static final int STAGE_SUCCESS = 5;
-    public static final int STAGE_FAILED = 6;
-
-    /**
-     * @return the integer value representing current install stage.
-     */
-    public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
deleted file mode 100644
index da48256..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallSuccess extends InstallStage {
-
-    private final int mStage = InstallStage.STAGE_SUCCESS;
-
-    @NonNull
-    private final AppSnippet mAppSnippet;
-    private final boolean mShouldReturnResult;
-    /**
-     * <p>If the caller is requesting a result back, this will hold the Intent with
-     * EXTRA_INSTALL_RESULT set to INSTALL_SUCCEEDED which is sent back to the caller.</p>
-     * <p>If the caller doesn't want the result back, this will hold the Intent that launches
-     * the newly installed / updated app.</p>
-     */
-    @NonNull
-    private final Intent mResultIntent;
-
-    public InstallSuccess(@NonNull AppSnippet appSnippet, boolean shouldReturnResult,
-        @NonNull Intent launcherIntent) {
-        mAppSnippet = appSnippet;
-        mShouldReturnResult = shouldReturnResult;
-        mResultIntent = launcherIntent;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @NonNull
-    public Drawable getAppIcon() {
-        return mAppSnippet.getIcon();
-    }
-
-    @NonNull
-    public String getAppLabel() {
-        return (String) mAppSnippet.getLabel();
-    }
-
-    public boolean shouldReturnResult() {
-        return mShouldReturnResult;
-    }
-
-    @NonNull
-    public Intent getResultIntent() {
-        return mResultIntent;
-    }
-
-    public static class Builder {
-
-        private final AppSnippet mAppSnippet;
-        private boolean mShouldReturnResult;
-        private Intent mLauncherIntent;
-
-        public Builder(@NonNull AppSnippet appSnippet) {
-            mAppSnippet = appSnippet;
-        }
-
-        public Builder setShouldReturnResult(boolean returnResult) {
-            mShouldReturnResult = returnResult;
-            return this;
-        }
-
-        public Builder setResultIntent(@NonNull Intent intent) {
-            mLauncherIntent = intent;
-            return this;
-        }
-
-        public InstallSuccess build() {
-            return new InstallSuccess(mAppSnippet, mShouldReturnResult, mLauncherIntent);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
deleted file mode 100644
index 08a7487..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallUserActionRequired extends InstallStage {
-
-    public static final int USER_ACTION_REASON_UNKNOWN_SOURCE = 0;
-    public static final int USER_ACTION_REASON_ANONYMOUS_SOURCE = 1;
-    public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2;
-    private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED;
-    private final int mActionReason;
-    @Nullable
-    private final AppSnippet mAppSnippet;
-    private final boolean mIsAppUpdating;
-    @Nullable
-    private final String mDialogMessage;
-
-    public InstallUserActionRequired(int actionReason, @Nullable AppSnippet appSnippet,
-        boolean isUpdating, @Nullable String dialogMessage) {
-        mActionReason = actionReason;
-        mAppSnippet = appSnippet;
-        mIsAppUpdating = isUpdating;
-        mDialogMessage = dialogMessage;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    @Nullable
-    public Drawable getAppIcon() {
-        return mAppSnippet != null ? mAppSnippet.getIcon() : null;
-    }
-
-    @Nullable
-    public String getAppLabel() {
-        return mAppSnippet != null ? (String) mAppSnippet.getLabel() : null;
-    }
-
-    public boolean isAppUpdating() {
-        return mIsAppUpdating;
-    }
-
-    @Nullable
-    public String getDialogMessage() {
-        return mDialogMessage;
-    }
-
-    public int getActionReason() {
-        return mActionReason;
-    }
-
-    public static class Builder {
-
-        private final int mActionReason;
-        private final AppSnippet mAppSnippet;
-        private boolean mIsAppUpdating;
-        private String mDialogMessage;
-
-        public Builder(int actionReason, @Nullable AppSnippet appSnippet) {
-            mActionReason = actionReason;
-            mAppSnippet = appSnippet;
-        }
-
-        public Builder setAppUpdating(boolean isUpdating) {
-            mIsAppUpdating = isUpdating;
-            return this;
-        }
-
-        public Builder setDialogMessage(@Nullable String message) {
-            mDialogMessage = message;
-            return this;
-        }
-
-        public InstallUserActionRequired build() {
-            return new InstallUserActionRequired(mActionReason, mAppSnippet, mIsAppUpdating,
-                mDialogMessage);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
deleted file mode 100644
index 9aea6b1..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import com.android.packageinstaller.R;
-
-public class UninstallAborted extends UninstallStage {
-
-    public static final int ABORT_REASON_GENERIC_ERROR = 0;
-    public static final int ABORT_REASON_APP_UNAVAILABLE = 1;
-    public static final int ABORT_REASON_USER_NOT_ALLOWED = 2;
-    private final int mStage = UninstallStage.STAGE_ABORTED;
-    private final int mAbortReason;
-    private final int mDialogTitleResource;
-    private final int mDialogTextResource;
-    private final int mActivityResultCode = Activity.RESULT_FIRST_USER;
-
-    public UninstallAborted(int abortReason) {
-        mAbortReason = abortReason;
-        switch (abortReason) {
-            case ABORT_REASON_APP_UNAVAILABLE -> {
-                mDialogTitleResource = R.string.app_not_found_dlg_title;
-                mDialogTextResource = R.string.app_not_found_dlg_text;
-            }
-            case ABORT_REASON_USER_NOT_ALLOWED -> {
-                mDialogTitleResource = 0;
-                mDialogTextResource = R.string.user_is_not_allowed_dlg_text;
-            }
-            default -> {
-                mDialogTitleResource = 0;
-                mDialogTextResource = R.string.generic_error_dlg_text;
-            }
-        }
-    }
-
-    public int getAbortReason() {
-        return mAbortReason;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    public int getDialogTitleResource() {
-        return mDialogTitleResource;
-    }
-
-    public int getDialogTextResource() {
-        return mDialogTextResource;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
deleted file mode 100644
index 6ed8883..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.Intent;
-
-public class UninstallFailed extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_FAILED;
-    private final boolean mReturnResult;
-    /**
-     * If the caller wants the result back, the intent will hold the uninstall failure status code
-     * and legacy code.
-     */
-    private final Intent mResultIntent;
-    /**
-     * When the user does not request a result back, this notification will be shown indicating the
-     * reason for uninstall failure.
-     */
-    private final Notification mUninstallNotification;
-    /**
-     * ID used to show {@link #mUninstallNotification}
-     */
-    private final int mUninstallId;
-    private final int mActivityResultCode;
-
-    public UninstallFailed(boolean returnResult, Intent resultIntent, int activityResultCode,
-        int uninstallId, Notification uninstallNotification) {
-        mReturnResult = returnResult;
-        mResultIntent = resultIntent;
-        mActivityResultCode = activityResultCode;
-        mUninstallId = uninstallId;
-        mUninstallNotification = uninstallNotification;
-    }
-
-    public boolean returnResult() {
-        return mReturnResult;
-    }
-
-    public Intent getResultIntent() {
-        return mResultIntent;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    public Notification getUninstallNotification() {
-        return mUninstallNotification;
-    }
-
-    public int getUninstallId() {
-        return mUninstallId;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private final boolean mReturnResult;
-        private int mActivityResultCode = Activity.RESULT_CANCELED;
-        /**
-         * See {@link UninstallFailed#mResultIntent}
-         */
-        private Intent mResultIntent = null;
-        /**
-         * See {@link UninstallFailed#mUninstallNotification}
-         */
-        private Notification mUninstallNotification;
-        /**
-         * See {@link UninstallFailed#mUninstallId}
-         */
-        private int mUninstallId;
-
-        public Builder(boolean returnResult) {
-            mReturnResult = returnResult;
-        }
-
-        public Builder setUninstallNotification(int uninstallId, Notification notification) {
-            mUninstallId = uninstallId;
-            mUninstallNotification = notification;
-            return this;
-        }
-
-        public Builder setResultIntent(Intent intent) {
-            mResultIntent = intent;
-            return this;
-        }
-
-        public Builder setActivityResultCode(int resultCode) {
-            mActivityResultCode = resultCode;
-            return this;
-        }
-
-        public UninstallFailed build() {
-            return new UninstallFailed(mReturnResult, mResultIntent, mActivityResultCode,
-                mUninstallId, mUninstallNotification);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
deleted file mode 100644
index 0108cb4..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallReady extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_READY;
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
deleted file mode 100644
index 87ca4ec..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.uninstallstagedata;
-
-public abstract class UninstallStage {
-
-    public static final int STAGE_DEFAULT = -1;
-    public static final int STAGE_ABORTED = 0;
-    public static final int STAGE_READY = 1;
-    public static final int STAGE_USER_ACTION_REQUIRED = 2;
-    public static final int STAGE_UNINSTALLING = 3;
-    public static final int STAGE_SUCCESS = 4;
-    public static final int STAGE_FAILED = 5;
-
-    public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
deleted file mode 100644
index 5df6b02..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.content.Intent;
-
-public class UninstallSuccess extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_SUCCESS;
-    private final String mMessage;
-    private final Intent mResultIntent;
-    private final int mActivityResultCode;
-
-    public UninstallSuccess(Intent resultIntent, int activityResultCode, String message) {
-        mResultIntent = resultIntent;
-        mActivityResultCode = activityResultCode;
-        mMessage = message;
-    }
-
-    public String getMessage() {
-        return mMessage;
-    }
-
-    public Intent getResultIntent() {
-        return mResultIntent;
-    }
-
-    public int getActivityResultCode() {
-        return mActivityResultCode;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private Intent mResultIntent;
-        private int mActivityResultCode;
-        private String mMessage;
-
-        public Builder() {
-        }
-
-        public Builder setResultIntent(Intent intent) {
-            mResultIntent = intent;
-            return this;
-        }
-
-        public Builder setActivityResultCode(int resultCode) {
-            mActivityResultCode = resultCode;
-            return this;
-        }
-
-        public Builder setMessage(String message) {
-            mMessage = message;
-            return this;
-        }
-
-        public UninstallSuccess build() {
-            return new UninstallSuccess(mResultIntent, mActivityResultCode, mMessage);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
deleted file mode 100644
index f5156cb..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUninstalling extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_UNINSTALLING;
-
-    private final CharSequence mAppLabel;
-    private final boolean mIsCloneUser;
-
-    public UninstallUninstalling(CharSequence appLabel, boolean isCloneUser) {
-        mAppLabel = appLabel;
-        mIsCloneUser = isCloneUser;
-    }
-
-    public CharSequence getAppLabel() {
-        return mAppLabel;
-    }
-
-    public boolean isCloneUser() {
-        return mIsCloneUser;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
deleted file mode 100644
index b600149..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUserActionRequired extends UninstallStage {
-
-    private final int mStage = UninstallStage.STAGE_USER_ACTION_REQUIRED;
-    private final String mTitle;
-    private final String mMessage;
-    private final long mAppDataSize;
-
-    public UninstallUserActionRequired(String title, String message, long appDataSize) {
-        mTitle = title;
-        mMessage = message;
-        mAppDataSize = appDataSize;
-    }
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public String getMessage() {
-        return mMessage;
-    }
-
-    public long getAppDataSize() {
-        return mAppDataSize;
-    }
-
-    @Override
-    public int getStageCode() {
-        return mStage;
-    }
-
-    public static class Builder {
-
-        private String mTitle;
-        private String mMessage;
-        private long mAppDataSize = 0;
-
-        public Builder setTitle(String title) {
-            mTitle = title;
-            return this;
-        }
-
-        public Builder setMessage(String message) {
-            mMessage = message;
-            return this;
-        }
-
-        public Builder setAppDataSize(long appDataSize) {
-            mAppDataSize = appDataSize;
-            return this;
-        }
-
-        public UninstallUserActionRequired build() {
-            return new UninstallUserActionRequired(mTitle, mMessage, mAppDataSize);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
deleted file mode 100644
index fdb024f..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.ui;
-
-import android.content.Intent;
-
-public interface InstallActionListener {
-
-    /**
-     * Method to handle a positive response from the user
-     */
-    void onPositiveResponse(int stageCode);
-
-    /**
-     * Method to dispatch intent for toggling "install from unknown sources" setting for a package
-     */
-    void sendUnknownAppsIntent(String packageName);
-
-    /**
-     * Method to handle a negative response from the user
-     */
-    void onNegativeResponse(int stageCode);
-    void openInstalledApp(Intent intent);
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
new file mode 100644
index 0000000..c109fc6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.packageinstaller.v2.ui
+
+import android.content.Intent
+
+interface InstallActionListener {
+    /**
+     * Method to handle a positive response from the user.
+     */
+    fun onPositiveResponse(reasonCode: Int)
+
+    /**
+     * Method to dispatch intent for toggling "install from unknown sources" setting for a package.
+     */
+    fun sendUnknownAppsIntent(sourcePackageName: String)
+
+    /**
+     * Method to handle a negative response from the user.
+     */
+    fun onNegativeResponse(stageCode: Int)
+
+    /**
+     * Launch the intent to open the newly installed / updated app.
+     */
+    fun openInstalledApp(intent: Intent?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
deleted file mode 100644
index d06b4b3..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.ui;
-
-import static android.content.Intent.CATEGORY_LAUNCHER;
-import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.os.Process.INVALID_UID;
-import static com.android.packageinstaller.v2.model.InstallRepository.EXTRA_STAGED_SESSION_ID;
-
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Window;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment;
-import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment;
-import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory;
-import java.util.ArrayList;
-import java.util.List;
-
-public class InstallLaunch extends FragmentActivity implements InstallActionListener {
-
-    public static final String EXTRA_CALLING_PKG_UID =
-            InstallLaunch.class.getPackageName() + ".callingPkgUid";
-    public static final String EXTRA_CALLING_PKG_NAME =
-            InstallLaunch.class.getPackageName() + ".callingPkgName";
-    private static final String TAG = InstallLaunch.class.getSimpleName();
-    private static final String TAG_DIALOG = "dialog";
-    private final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
-    private final boolean mLocalLOGV = false;
-    /**
-     * A collection of unknown sources listeners that are actively listening for app ops mode
-     * changes
-     */
-    private final List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1);
-    private InstallViewModel mInstallViewModel;
-    private InstallRepository mInstallRepository;
-    private FragmentManager mFragmentManager;
-    private AppOpsManager mAppOpsManager;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
-
-        mFragmentManager = getSupportFragmentManager();
-        mAppOpsManager = getSystemService(AppOpsManager.class);
-
-        mInstallRepository = new InstallRepository(getApplicationContext());
-        mInstallViewModel = new ViewModelProvider(this,
-                new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get(
-                InstallViewModel.class);
-
-        Intent intent = getIntent();
-        CallerInfo info = new CallerInfo(
-                intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
-                intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
-        mInstallViewModel.preprocessIntent(intent, info);
-
-        mInstallViewModel.getCurrentInstallStage().observe(this, this::onInstallStageChange);
-    }
-
-    /**
-     * Main controller of the UI. This method shows relevant dialogs based on the install stage
-     */
-    private void onInstallStageChange(InstallStage installStage) {
-        switch (installStage.getStageCode()) {
-            case InstallStage.STAGE_STAGING -> {
-                InstallStagingFragment stagingDialog = new InstallStagingFragment();
-                showDialogInner(stagingDialog);
-                mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
-            }
-            case InstallStage.STAGE_ABORTED -> {
-                InstallAborted aborted = (InstallAborted) installStage;
-                switch (aborted.getAbortReason()) {
-                    // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR
-                    case InstallAborted.ABORT_REASON_DONE,
-                        InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
-                        setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true);
-                    case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted);
-                    default -> setResult(RESULT_CANCELED, null, true);
-                }
-            }
-            case InstallStage.STAGE_USER_ACTION_REQUIRED -> {
-                InstallUserActionRequired uar = (InstallUserActionRequired) installStage;
-                switch (uar.getActionReason()) {
-                    case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
-                        InstallConfirmationFragment actionDialog =
-                            new InstallConfirmationFragment(uar);
-                        showDialogInner(actionDialog);
-                    }
-                    case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
-                        ExternalSourcesBlockedFragment externalSourceDialog =
-                            new ExternalSourcesBlockedFragment(uar);
-                        showDialogInner(externalSourceDialog);
-                    }
-                    case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
-                        AnonymousSourceFragment anonymousSourceDialog =
-                            new AnonymousSourceFragment();
-                        showDialogInner(anonymousSourceDialog);
-                    }
-                }
-            }
-            case InstallStage.STAGE_INSTALLING -> {
-                InstallInstalling installing = (InstallInstalling) installStage;
-                InstallInstallingFragment installingDialog =
-                    new InstallInstallingFragment(installing);
-                showDialogInner(installingDialog);
-            }
-            case InstallStage.STAGE_SUCCESS -> {
-                InstallSuccess success = (InstallSuccess) installStage;
-                if (success.shouldReturnResult()) {
-                    Intent successIntent = success.getResultIntent();
-                    setResult(Activity.RESULT_OK, successIntent, true);
-                } else {
-                    InstallSuccessFragment successFragment = new InstallSuccessFragment(success);
-                    showDialogInner(successFragment);
-                }
-            }
-            case InstallStage.STAGE_FAILED -> {
-                InstallFailed failed = (InstallFailed) installStage;
-                InstallFailedFragment failedDialog = new InstallFailedFragment(failed);
-                showDialogInner(failedDialog);
-            }
-            default -> {
-                Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
-                showDialogInner(null);
-            }
-        }
-    }
-
-    private void showPolicyRestrictionDialog(InstallAborted aborted) {
-        String restriction = aborted.getMessage();
-        Intent adminSupportIntent = aborted.getResultIntent();
-        boolean shouldFinish;
-
-        // If the given restriction is set by an admin, display information about the
-        // admin enforcing the restriction for the affected user. If not enforced by the admin,
-        // show the system dialog.
-        if (adminSupportIntent != null) {
-            if (mLocalLOGV) {
-                Log.i(TAG, "Restriction set by admin, starting " + adminSupportIntent);
-            }
-            startActivity(adminSupportIntent);
-            // Finish the package installer app since the next dialog will not be shown by this app
-            shouldFinish = true;
-        } else {
-            if (mLocalLOGV) {
-                Log.i(TAG, "Restriction set by system: " + restriction);
-            }
-            DialogFragment blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction);
-            // Don't finish the package installer app since the next dialog
-            // will be shown by this app
-            shouldFinish = false;
-            showDialogInner(blockedByPolicyDialog);
-        }
-        setResult(RESULT_CANCELED, null, shouldFinish);
-    }
-
-    /**
-     * Create a new dialog based on the install restriction enforced.
-     *
-     * @param restriction The restriction to create the dialog for
-     * @return The dialog
-     */
-    private DialogFragment createDevicePolicyRestrictionDialog(String restriction) {
-        if (mLocalLOGV) {
-            Log.i(TAG, "createDialog(" + restriction + ")");
-        }
-        return switch (restriction) {
-            case UserManager.DISALLOW_INSTALL_APPS ->
-                new SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text);
-            case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
-                new SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text);
-            default -> null;
-        };
-    }
-
-    /**
-     * Replace any visible dialog by the dialog returned by InstallRepository
-     *
-     * @param newDialog The new dialog to display
-     */
-    private void showDialogInner(@Nullable DialogFragment newDialog) {
-        DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
-            TAG_DIALOG);
-        if (currentDialog != null) {
-            currentDialog.dismissAllowingStateLoss();
-        }
-        if (newDialog != null) {
-            newDialog.show(mFragmentManager, TAG_DIALOG);
-        }
-    }
-
-    public void setResult(int resultCode, Intent data, boolean shouldFinish) {
-        super.setResult(resultCode, data);
-        if (shouldFinish) {
-            finish();
-        }
-    }
-
-    @Override
-    public void onPositiveResponse(int reasonCode) {
-        switch (reasonCode) {
-            case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
-                mInstallViewModel.forcedSkipSourceCheck();
-            case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
-                mInstallViewModel.initiateInstall();
-        }
-    }
-
-    @Override
-    public void onNegativeResponse(int stageCode) {
-        if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
-            mInstallViewModel.cleanupInstall();
-        }
-        setResult(Activity.RESULT_CANCELED, null, true);
-    }
-
-    @Override
-    public void sendUnknownAppsIntent(String sourcePackageName) {
-        Intent settingsIntent = new Intent();
-        settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
-        final Uri packageUri = Uri.parse("package:" + sourcePackageName);
-        settingsIntent.setData(packageUri);
-        settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY);
-
-        try {
-            registerAppOpChangeListener(new UnknownSourcesListener(sourcePackageName),
-                sourcePackageName);
-            startActivityForResult(settingsIntent, REQUEST_TRUST_EXTERNAL_SOURCE);
-        } catch (ActivityNotFoundException exc) {
-            Log.e(TAG, "Settings activity not found for action: "
-                + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
-        }
-    }
-
-    @Override
-    public void openInstalledApp(Intent intent) {
-        setResult(RESULT_OK, intent, true);
-        if (intent != null && intent.hasCategory(CATEGORY_LAUNCHER)) {
-            startActivity(intent);
-        }
-    }
-
-    private void registerAppOpChangeListener(UnknownSourcesListener listener, String packageName) {
-        mAppOpsManager.startWatchingMode(
-            AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, packageName,
-            listener);
-        mActiveUnknownSourcesListeners.add(listener);
-    }
-
-    private void unregisterAppOpChangeListener(UnknownSourcesListener listener) {
-        mActiveUnknownSourcesListeners.remove(listener);
-        mAppOpsManager.stopWatchingMode(listener);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == REQUEST_TRUST_EXTERNAL_SOURCE) {
-            mInstallViewModel.reattemptInstall();
-        } else {
-            setResult(Activity.RESULT_CANCELED,  null, true);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        while (!mActiveUnknownSourcesListeners.isEmpty()) {
-            unregisterAppOpChangeListener(mActiveUnknownSourcesListeners.get(0));
-        }
-    }
-
-    private class UnknownSourcesListener implements AppOpsManager.OnOpChangedListener {
-
-        private final String mOriginatingPackage;
-
-        public UnknownSourcesListener(String originatingPackage) {
-            mOriginatingPackage = originatingPackage;
-        }
-
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            if (!mOriginatingPackage.equals(packageName)) {
-                return;
-            }
-            unregisterAppOpChangeListener(this);
-            mActiveUnknownSourcesListeners.remove(this);
-            if (isDestroyed()) {
-                return;
-            }
-            new Handler(Looper.getMainLooper()).postDelayed(() -> {
-                if (!isDestroyed()) {
-                    // Relaunch Pia to continue installation.
-                    startActivity(getIntent()
-                        .putExtra(EXTRA_STAGED_SESSION_ID, mInstallViewModel.getStagedSessionId()));
-
-                    // If the userId of the root of activity stack is different from current userId,
-                    // starting Pia again lead to duplicate instances of the app in the stack.
-                    // As such, finish the old instance. Old Pia is finished even if the userId of
-                    // the root is the same, since there is no way to determine the difference in
-                    // userIds.
-                    finish();
-                }
-            }, 500);
-        }
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
new file mode 100644
index 0000000..2b610d7
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -0,0 +1,348 @@
+/*
+ * 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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Process
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import android.view.Window
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.R
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallAborted
+import com.android.packageinstaller.v2.model.InstallFailed
+import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallSuccess
+import com.android.packageinstaller.v2.model.InstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment
+import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
+import com.android.packageinstaller.v2.viewmodel.InstallViewModel
+import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
+
+class InstallLaunch : FragmentActivity(), InstallActionListener {
+
+    companion object {
+        @JvmField val EXTRA_CALLING_PKG_UID =
+            InstallLaunch::class.java.packageName + ".callingPkgUid"
+        @JvmField val EXTRA_CALLING_PKG_NAME =
+            InstallLaunch::class.java.packageName + ".callingPkgName"
+        private val LOG_TAG = InstallLaunch::class.java.simpleName
+        private const val TAG_DIALOG = "dialog"
+    }
+
+    private val localLOGV = false
+
+    /**
+     * A collection of unknown sources listeners that are actively listening for app ops mode
+     * changes
+     */
+    private val activeUnknownSourcesListeners: MutableList<UnknownSourcesListener> = ArrayList(1)
+    private var installViewModel: InstallViewModel? = null
+    private var installRepository: InstallRepository? = null
+    private var fragmentManager: FragmentManager? = null
+    private var appOpsManager: AppOpsManager? = null
+    private lateinit var unknownAppsIntentLauncher: ActivityResultLauncher<Intent>
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        requestWindowFeature(Window.FEATURE_NO_TITLE)
+        fragmentManager = supportFragmentManager
+        appOpsManager = getSystemService(AppOpsManager::class.java)
+        installRepository = InstallRepository(applicationContext)
+        installViewModel = ViewModelProvider(
+            this, InstallViewModelFactory(this.application, installRepository!!)
+        )[InstallViewModel::class.java]
+
+        val intent = intent
+        val info = InstallRepository.CallerInfo(
+            intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
+            intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+        )
+        installViewModel!!.preprocessIntent(intent, info)
+        installViewModel!!.currentInstallStage.observe(this) { installStage: InstallStage ->
+            onInstallStageChange(installStage)
+        }
+
+        // Used to launch intent for Settings, to manage "install unknown apps" permission
+        unknownAppsIntentLauncher =
+            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+                // Reattempt installation on coming back from Settings, after toggling
+                // "install unknown apps" permission
+                installViewModel!!.reattemptInstall()
+            }
+    }
+
+    /**
+     * Main controller of the UI. This method shows relevant dialogs based on the install stage
+     */
+    private fun onInstallStageChange(installStage: InstallStage) {
+        when (installStage.stageCode) {
+            InstallStage.STAGE_STAGING -> {
+                val stagingDialog = InstallStagingFragment()
+                showDialogInner(stagingDialog)
+                installViewModel!!.stagingProgress.observe(this) { progress: Int ->
+                    stagingDialog.setProgress(progress)
+                }
+            }
+
+            InstallStage.STAGE_ABORTED -> {
+                val aborted = installStage as InstallAborted
+                when (aborted.abortReason) {
+                    InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
+                        setResult(aborted.activityResultCode, aborted.resultIntent, true)
+
+                    InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
+                    else -> setResult(Activity.RESULT_CANCELED, null, true)
+                }
+            }
+
+            InstallStage.STAGE_USER_ACTION_REQUIRED -> {
+                val uar = installStage as InstallUserActionRequired
+                when (uar.actionReason) {
+                    InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
+                        val actionDialog = InstallConfirmationFragment(uar)
+                        showDialogInner(actionDialog)
+                    }
+
+                    InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
+                        val externalSourceDialog = ExternalSourcesBlockedFragment(uar)
+                        showDialogInner(externalSourceDialog)
+                    }
+
+                    InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
+                        val anonymousSourceDialog = AnonymousSourceFragment()
+                        showDialogInner(anonymousSourceDialog)
+                    }
+                }
+            }
+
+            InstallStage.STAGE_INSTALLING -> {
+                val installing = installStage as InstallInstalling
+                val installingDialog = InstallInstallingFragment(installing)
+                showDialogInner(installingDialog)
+            }
+
+            InstallStage.STAGE_SUCCESS -> {
+                val success = installStage as InstallSuccess
+                if (success.shouldReturnResult) {
+                    val successIntent = success.resultIntent
+                    setResult(Activity.RESULT_OK, successIntent, true)
+                } else {
+                    val successFragment = InstallSuccessFragment(success)
+                    showDialogInner(successFragment)
+                }
+            }
+
+            InstallStage.STAGE_FAILED -> {
+                val failed = installStage as InstallFailed
+                val failedDialog = InstallFailedFragment(failed)
+                showDialogInner(failedDialog)
+            }
+
+            else -> {
+                Log.d(LOG_TAG, "Unimplemented stage: " + installStage.stageCode)
+                showDialogInner(null)
+            }
+        }
+    }
+
+    private fun showPolicyRestrictionDialog(aborted: InstallAborted) {
+        val restriction = aborted.message
+        val adminSupportIntent = aborted.resultIntent
+        var shouldFinish: Boolean = false
+
+        // If the given restriction is set by an admin, display information about the
+        // admin enforcing the restriction for the affected user. If not enforced by the admin,
+        // show the system dialog.
+        if (adminSupportIntent != null) {
+            if (localLOGV) {
+                Log.i(LOG_TAG, "Restriction set by admin, starting $adminSupportIntent")
+            }
+            startActivity(adminSupportIntent)
+            // Finish the package installer app since the next dialog will not be shown by this app
+            shouldFinish = true
+        } else {
+            if (localLOGV) {
+                Log.i(LOG_TAG, "Restriction set by system: $restriction")
+            }
+            val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
+            // Don't finish the package installer app since the next dialog
+            // will be shown by this app
+            shouldFinish = blockedByPolicyDialog != null
+            showDialogInner(blockedByPolicyDialog)
+        }
+        setResult(Activity.RESULT_CANCELED, null, shouldFinish)
+    }
+
+    /**
+     * Create a new dialog based on the install restriction enforced.
+     *
+     * @param restriction The restriction to create the dialog for
+     * @return The dialog
+     */
+    private fun createDevicePolicyRestrictionDialog(restriction: String?): DialogFragment? {
+        if (localLOGV) {
+            Log.i(LOG_TAG, "createDialog($restriction)")
+        }
+        return when (restriction) {
+            UserManager.DISALLOW_INSTALL_APPS ->
+                SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text)
+
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
+                SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text)
+
+            else -> null
+        }
+    }
+
+    /**
+     * Replace any visible dialog by the dialog returned by InstallRepository
+     *
+     * @param newDialog The new dialog to display
+     */
+    private fun showDialogInner(newDialog: DialogFragment?) {
+        val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+        currentDialog?.dismissAllowingStateLoss()
+        newDialog?.show(fragmentManager!!, TAG_DIALOG)
+    }
+
+    fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+        super.setResult(resultCode, data)
+        if (shouldFinish) {
+            finish()
+        }
+    }
+
+    override fun onPositiveResponse(reasonCode: Int) {
+        when (reasonCode) {
+            InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
+                installViewModel!!.forcedSkipSourceCheck()
+
+            InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
+                installViewModel!!.initiateInstall()
+        }
+    }
+
+    override fun onNegativeResponse(stageCode: Int) {
+        if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
+            installViewModel!!.cleanupInstall()
+        }
+        setResult(Activity.RESULT_CANCELED, null, true)
+    }
+
+    override fun sendUnknownAppsIntent(sourcePackageName: String) {
+        val settingsIntent = Intent()
+        settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
+        val packageUri = Uri.parse("package:$sourcePackageName")
+        settingsIntent.setData(packageUri)
+        settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+        try {
+            registerAppOpChangeListener(
+                UnknownSourcesListener(sourcePackageName), sourcePackageName
+            )
+            unknownAppsIntentLauncher.launch(settingsIntent)
+        } catch (exc: ActivityNotFoundException) {
+            Log.e(
+                LOG_TAG, "Settings activity not found for action: "
+                    + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES
+            )
+        }
+    }
+
+    override fun openInstalledApp(intent: Intent?) {
+        setResult(Activity.RESULT_OK, intent, true)
+        if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
+            startActivity(intent)
+        }
+    }
+
+    private fun registerAppOpChangeListener(listener: UnknownSourcesListener, packageName: String) {
+        appOpsManager!!.startWatchingMode(
+            AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES,
+            packageName,
+            listener
+        )
+        activeUnknownSourcesListeners.add(listener)
+    }
+
+    private fun unregisterAppOpChangeListener(listener: UnknownSourcesListener) {
+        activeUnknownSourcesListeners.remove(listener)
+        appOpsManager!!.stopWatchingMode(listener)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        while (activeUnknownSourcesListeners.isNotEmpty()) {
+            unregisterAppOpChangeListener(activeUnknownSourcesListeners[0])
+        }
+    }
+
+    private inner class UnknownSourcesListener(private val mOriginatingPackage: String) :
+        AppOpsManager.OnOpChangedListener {
+        override fun onOpChanged(op: String, packageName: String) {
+            if (mOriginatingPackage != packageName) {
+                return
+            }
+            unregisterAppOpChangeListener(this)
+            activeUnknownSourcesListeners.remove(this)
+            if (isDestroyed) {
+                return
+            }
+            Handler(Looper.getMainLooper()).postDelayed({
+                if (!isDestroyed) {
+                    // Relaunch Pia to continue installation.
+                    startActivity(
+                        intent.putExtra(
+                            InstallRepository.EXTRA_STAGED_SESSION_ID,
+                            installViewModel!!.stagedSessionId
+                        )
+                    )
+
+                    // If the userId of the root of activity stack is different from current userId,
+                    // starting Pia again lead to duplicate instances of the app in the stack.
+                    // As such, finish the old instance. Old Pia is finished even if the userId of
+                    // the root is the same, since there is no way to determine the difference in
+                    // userIds.
+                    finish()
+                }
+            }, 500)
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
similarity index 78%
rename from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
rename to packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
index b8a9355..33f5db3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.packageinstaller.v2.ui;
+package com.android.packageinstaller.v2.ui
 
-public interface UninstallActionListener {
-
-    void onPositiveResponse(boolean keepData);
-
-    void onNegativeResponse();
+interface UninstallActionListener {
+    fun onPositiveResponse(keepData: Boolean)
+    fun onNegativeResponse()
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
deleted file mode 100644
index 7638e91..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.ui;
-
-import static android.os.Process.INVALID_UID;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory;
-
-public class UninstallLaunch extends FragmentActivity implements UninstallActionListener {
-
-    public static final String EXTRA_CALLING_PKG_UID =
-        UninstallLaunch.class.getPackageName() + ".callingPkgUid";
-    public static final String EXTRA_CALLING_ACTIVITY_NAME =
-        UninstallLaunch.class.getPackageName() + ".callingActivityName";
-    public static final String TAG = UninstallLaunch.class.getSimpleName();
-    private static final String TAG_DIALOG = "dialog";
-
-    private UninstallViewModel mUninstallViewModel;
-    private UninstallRepository mUninstallRepository;
-    private FragmentManager mFragmentManager;
-    private NotificationManager mNotificationManager;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
-        // Never restore any state, esp. never create any fragments. The data in the fragment might
-        // be stale, if e.g. the app was uninstalled while the activity was destroyed.
-        super.onCreate(null);
-
-        mFragmentManager = getSupportFragmentManager();
-        mNotificationManager = getSystemService(NotificationManager.class);
-
-        mUninstallRepository = new UninstallRepository(getApplicationContext());
-        mUninstallViewModel = new ViewModelProvider(this,
-            new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get(
-            UninstallViewModel.class);
-
-        Intent intent = getIntent();
-        CallerInfo callerInfo = new CallerInfo(
-            intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
-            intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
-        mUninstallViewModel.preprocessIntent(intent, callerInfo);
-
-        mUninstallViewModel.getCurrentUninstallStage().observe(this,
-            this::onUninstallStageChange);
-    }
-
-    /**
-     * Main controller of the UI. This method shows relevant dialogs / fragments based on the
-     * uninstall stage
-     */
-    private void onUninstallStageChange(UninstallStage uninstallStage) {
-        if (uninstallStage.getStageCode() == UninstallStage.STAGE_ABORTED) {
-            UninstallAborted aborted = (UninstallAborted) uninstallStage;
-            if (aborted.getAbortReason() == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
-                aborted.getAbortReason() == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED) {
-                UninstallErrorFragment errorDialog = new UninstallErrorFragment(aborted);
-                showDialogInner(errorDialog);
-            } else {
-                setResult(aborted.getActivityResultCode(), null, true);
-            }
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_USER_ACTION_REQUIRED) {
-            UninstallUserActionRequired uar = (UninstallUserActionRequired) uninstallStage;
-            UninstallConfirmationFragment confirmationDialog = new UninstallConfirmationFragment(
-                uar);
-            showDialogInner(confirmationDialog);
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_UNINSTALLING) {
-            // TODO: This shows a fragment whether or not user requests a result or not.
-            //  Originally, if the user does not request a result, we used to show a notification.
-            //  And a fragment if the user requests a result back. Should we consolidate and
-            //  show a fragment always?
-            UninstallUninstalling uninstalling = (UninstallUninstalling) uninstallStage;
-            UninstallUninstallingFragment uninstallingDialog = new UninstallUninstallingFragment(
-                uninstalling);
-            showDialogInner(uninstallingDialog);
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_FAILED) {
-            UninstallFailed failed = (UninstallFailed) uninstallStage;
-            if (!failed.returnResult()) {
-                mNotificationManager.notify(failed.getUninstallId(),
-                    failed.getUninstallNotification());
-            }
-            setResult(failed.getActivityResultCode(), failed.getResultIntent(), true);
-        } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_SUCCESS) {
-            UninstallSuccess success = (UninstallSuccess) uninstallStage;
-            if (success.getMessage() != null) {
-                Toast.makeText(this, success.getMessage(), Toast.LENGTH_LONG).show();
-            }
-            setResult(success.getActivityResultCode(), success.getResultIntent(), true);
-        } else {
-            Log.e(TAG, "Invalid stage: " + uninstallStage.getStageCode());
-            showDialogInner(null);
-        }
-    }
-
-    /**
-     * Replace any visible dialog by the dialog returned by InstallRepository
-     *
-     * @param newDialog The new dialog to display
-     */
-    private void showDialogInner(DialogFragment newDialog) {
-        DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
-            TAG_DIALOG);
-        if (currentDialog != null) {
-            currentDialog.dismissAllowingStateLoss();
-        }
-        if (newDialog != null) {
-            newDialog.show(mFragmentManager, TAG_DIALOG);
-        }
-    }
-
-    public void setResult(int resultCode, Intent data, boolean shouldFinish) {
-        super.setResult(resultCode, data);
-        if (shouldFinish) {
-            finish();
-        }
-    }
-
-    @Override
-    public void onPositiveResponse(boolean keepData) {
-        mUninstallViewModel.initiateUninstall(keepData);
-    }
-
-    @Override
-    public void onNegativeResponse() {
-        mUninstallViewModel.cancelInstall();
-        setResult(Activity.RESULT_FIRST_USER, null, true);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
new file mode 100644
index 0000000..0050c7e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ *      https://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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.content.Intent
+import android.os.Bundle
+import android.os.Process
+import android.util.Log
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallAborted
+import com.android.packageinstaller.v2.model.UninstallFailed
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+import com.android.packageinstaller.v2.model.UninstallSuccess
+import com.android.packageinstaller.v2.model.UninstallUninstalling
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModel
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory
+
+class UninstallLaunch : FragmentActivity(), UninstallActionListener {
+
+    companion object {
+        @JvmField val EXTRA_CALLING_PKG_UID =
+            UninstallLaunch::class.java.packageName + ".callingPkgUid"
+        @JvmField val EXTRA_CALLING_ACTIVITY_NAME =
+            UninstallLaunch::class.java.packageName + ".callingActivityName"
+        val LOG_TAG = UninstallLaunch::class.java.simpleName
+        private const val TAG_DIALOG = "dialog"
+    }
+
+    private var uninstallViewModel: UninstallViewModel? = null
+    private var uninstallRepository: UninstallRepository? = null
+    private var fragmentManager: FragmentManager? = null
+    private var notificationManager: NotificationManager? = null
+    override fun onCreate(savedInstanceState: Bundle?) {
+        window.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+        // Never restore any state, esp. never create any fragments. The data in the fragment might
+        // be stale, if e.g. the app was uninstalled while the activity was destroyed.
+        super.onCreate(null)
+        fragmentManager = supportFragmentManager
+        notificationManager = getSystemService(NotificationManager::class.java)
+
+        uninstallRepository = UninstallRepository(applicationContext)
+        uninstallViewModel = ViewModelProvider(
+            this, UninstallViewModelFactory(this.application, uninstallRepository!!)
+        ).get(UninstallViewModel::class.java)
+
+        val intent = intent
+        val callerInfo = UninstallRepository.CallerInfo(
+            intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
+            intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+        )
+        uninstallViewModel!!.preprocessIntent(intent, callerInfo)
+        uninstallViewModel!!.currentUninstallStage.observe(this) { uninstallStage: UninstallStage ->
+            onUninstallStageChange(uninstallStage)
+        }
+    }
+
+    /**
+     * Main controller of the UI. This method shows relevant dialogs / fragments based on the
+     * uninstall stage
+     */
+    private fun onUninstallStageChange(uninstallStage: UninstallStage) {
+        when (uninstallStage.stageCode) {
+            UninstallStage.STAGE_ABORTED -> {
+                val aborted = uninstallStage as UninstallAborted
+                if (aborted.abortReason == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
+                    aborted.abortReason == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED
+                ) {
+                    val errorDialog = UninstallErrorFragment(aborted)
+                    showDialogInner(errorDialog)
+                } else {
+                    setResult(aborted.activityResultCode, null, true)
+                }
+            }
+
+            UninstallStage.STAGE_USER_ACTION_REQUIRED -> {
+                val uar = uninstallStage as UninstallUserActionRequired
+                val confirmationDialog = UninstallConfirmationFragment(uar)
+                showDialogInner(confirmationDialog)
+            }
+
+            UninstallStage.STAGE_UNINSTALLING -> {
+                // TODO: This shows a fragment whether or not user requests a result or not.
+                //  Originally, if the user does not request a result, we used to show a notification.
+                //  And a fragment if the user requests a result back. Should we consolidate and
+                //  show a fragment always?
+                val uninstalling = uninstallStage as UninstallUninstalling
+                val uninstallingDialog = UninstallUninstallingFragment(uninstalling)
+                showDialogInner(uninstallingDialog)
+            }
+
+            UninstallStage.STAGE_FAILED -> {
+                val failed = uninstallStage as UninstallFailed
+                if (!failed.returnResult) {
+                    notificationManager!!.notify(
+                        failed.uninstallNotificationId!!, failed.uninstallNotification
+                    )
+                }
+                setResult(failed.activityResultCode, failed.resultIntent, true)
+            }
+
+            UninstallStage.STAGE_SUCCESS -> {
+                val success = uninstallStage as UninstallSuccess
+                if (success.message != null) {
+                    Toast.makeText(this, success.message, Toast.LENGTH_LONG).show()
+                }
+                setResult(success.activityResultCode, success.resultIntent, true)
+            }
+
+            else -> {
+                Log.e(LOG_TAG, "Invalid stage: " + uninstallStage.stageCode)
+                showDialogInner(null)
+            }
+        }
+    }
+
+    /**
+     * Replace any visible dialog by the dialog returned by InstallRepository
+     *
+     * @param newDialog The new dialog to display
+     */
+    private fun showDialogInner(newDialog: DialogFragment?) {
+        val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+        currentDialog?.dismissAllowingStateLoss()
+        newDialog?.show(fragmentManager!!, TAG_DIALOG)
+    }
+
+    fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+        super.setResult(resultCode, data)
+        if (shouldFinish) {
+            finish()
+        }
+    }
+
+    override fun onPositiveResponse(keepData: Boolean) {
+        uninstallViewModel!!.initiateUninstall(keepData)
+    }
+
+    override fun onNegativeResponse() {
+        uninstallViewModel!!.cancelInstall()
+        setResult(Activity.RESULT_FIRST_USER, null, true)
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
index 6d6fcc9..679f696 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
@@ -24,8 +24,8 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index 4cdce52..49901de 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 6398aef..25363d0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -28,7 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
index d45cd76..4667a7a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
@@ -28,7 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
+import com.android.packageinstaller.v2.model.InstallFailed;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
index 9f60f96..7327b5d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
+import com.android.packageinstaller.v2.model.InstallInstalling;
 
 /**
  * Dialog to show when an install is in progress.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
index ab6a932..b2a65faa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
@@ -29,8 +29,8 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallSuccess;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 import java.util.List;
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
index 47fd67f..58b8b2d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
@@ -24,7 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.model.InstallStage;
 import com.android.packageinstaller.v2.ui.InstallActionListener;
 
 public class SimpleErrorFragment extends DialogFragment {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
index 1b0885e..32ac4a6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
@@ -30,7 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired;
 import com.android.packageinstaller.v2.ui.UninstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
index 305daba..eb7183d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
+import com.android.packageinstaller.v2.model.UninstallAborted;
 import com.android.packageinstaller.v2.ui.UninstallActionListener;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
index 23cc421..835efc6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
@@ -22,7 +22,7 @@
 import androidx.annotation.NonNull;
 import androidx.fragment.app.DialogFragment;
 import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
+import com.android.packageinstaller.v2.model.UninstallUninstalling;
 
 /**
  * Dialog to show that the app is uninstalling.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
deleted file mode 100644
index 04a0622..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-
-
-public class InstallViewModel extends AndroidViewModel {
-
-    private static final String TAG = InstallViewModel.class.getSimpleName();
-    private final InstallRepository mRepository;
-    private final MediatorLiveData<InstallStage> mCurrentInstallStage = new MediatorLiveData<>(
-            new InstallStaging());
-
-    public InstallViewModel(@NonNull Application application, InstallRepository repository) {
-        super(application);
-        mRepository = repository;
-    }
-
-    public MutableLiveData<InstallStage> getCurrentInstallStage() {
-        return mCurrentInstallStage;
-    }
-
-    public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
-        InstallStage stage = mRepository.performPreInstallChecks(intent, callerInfo);
-        if (stage.getStageCode() == InstallStage.STAGE_ABORTED) {
-            mCurrentInstallStage.setValue(stage);
-        } else {
-            // Since staging is an async operation, we will get the staging result later in time.
-            // Result of the file staging will be set in InstallRepository#mStagingResult.
-            // As such, mCurrentInstallStage will need to add another MutableLiveData
-            // as a data source
-            mRepository.stageForInstall();
-            mCurrentInstallStage.addSource(mRepository.getStagingResult(), installStage -> {
-                if (installStage.getStageCode() != InstallStage.STAGE_READY) {
-                    mCurrentInstallStage.setValue(installStage);
-                } else {
-                    checkIfAllowedAndInitiateInstall();
-                }
-            });
-        }
-    }
-
-    public MutableLiveData<Integer> getStagingProgress() {
-        return mRepository.getStagingProgress();
-    }
-
-    private void checkIfAllowedAndInitiateInstall() {
-        InstallStage stage = mRepository.requestUserConfirmation();
-        mCurrentInstallStage.setValue(stage);
-    }
-
-    public void forcedSkipSourceCheck() {
-        InstallStage stage = mRepository.forcedSkipSourceCheck();
-        mCurrentInstallStage.setValue(stage);
-    }
-
-    public void cleanupInstall() {
-        mRepository.cleanupInstall();
-    }
-
-    public void reattemptInstall() {
-        InstallStage stage = mRepository.reattemptInstall();
-        mCurrentInstallStage.setValue(stage);
-    }
-
-    public void initiateInstall() {
-        // Since installing is an async operation, we will get the install result later in time.
-        // Result of the installation will be set in InstallRepository#mInstallResult.
-        // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
-        mRepository.initiateInstall();
-        mCurrentInstallStage.addSource(mRepository.getInstallResult(), installStage -> {
-            if (installStage != null) {
-                mCurrentInstallStage.setValue(installStage);
-            }
-        });
-    }
-
-    public int getStagedSessionId() {
-        return mRepository.getStagedSessionId();
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
new file mode 100644
index 0000000..072fb2d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallStaging
+
+class InstallViewModel(application: Application, val repository: InstallRepository) :
+    AndroidViewModel(application) {
+
+    companion object {
+        private val LOG_TAG = InstallViewModel::class.java.simpleName
+    }
+
+    private val _currentInstallStage = MediatorLiveData<InstallStage>(InstallStaging())
+    val currentInstallStage: MutableLiveData<InstallStage>
+        get() = _currentInstallStage
+
+    fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
+        val stage = repository.performPreInstallChecks(intent, callerInfo)
+        if (stage.stageCode == InstallStage.STAGE_ABORTED) {
+            _currentInstallStage.value = stage
+        } else {
+            // Since staging is an async operation, we will get the staging result later in time.
+            // Result of the file staging will be set in InstallRepository#mStagingResult.
+            // As such, mCurrentInstallStage will need to add another MutableLiveData
+            // as a data source
+            repository.stageForInstall()
+            _currentInstallStage.addSource(repository.stagingResult) { installStage: InstallStage ->
+                if (installStage.stageCode != InstallStage.STAGE_READY) {
+                    _currentInstallStage.value = installStage
+                } else {
+                    checkIfAllowedAndInitiateInstall()
+                }
+            }
+        }
+    }
+
+    val stagingProgress: LiveData<Int>
+        get() = repository.stagingProgress
+
+    private fun checkIfAllowedAndInitiateInstall() {
+        val stage = repository.requestUserConfirmation()
+        _currentInstallStage.value = stage
+    }
+
+    fun forcedSkipSourceCheck() {
+        val stage = repository.forcedSkipSourceCheck()
+        _currentInstallStage.value = stage
+    }
+
+    fun cleanupInstall() {
+        repository.cleanupInstall()
+    }
+
+    fun reattemptInstall() {
+        val stage = repository.reattemptInstall()
+        _currentInstallStage.value = stage
+    }
+
+    fun initiateInstall() {
+        // Since installing is an async operation, we will get the install result later in time.
+        // Result of the installation will be set in InstallRepository#mInstallResult.
+        // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
+        repository.initiateInstall()
+        _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? ->
+            if (installStage != null) {
+                _currentInstallStage.value = installStage
+            }
+        }
+    }
+
+    val stagedSessionId: Int
+        get() = repository.stagedSessionId
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
deleted file mode 100644
index ef459e6..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.InstallRepository;
-
-public class InstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
-    private final InstallRepository mRepository;
-    private final Application mApplication;
-
-    public InstallViewModelFactory(Application application, InstallRepository repository) {
-        // Calling super class' ctor ensures that create method is called correctly and the right
-        // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
-        // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
-        super(application);
-        mApplication = application;
-        mRepository = repository;
-    }
-
-    @NonNull
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-        return (T) new InstallViewModel(mApplication, mRepository);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
new file mode 100644
index 0000000..07b2f4f
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
@@ -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 com.android.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.InstallRepository
+
+class InstallViewModelFactory(val application: Application, val repository: InstallRepository) :
+    ViewModelProvider.AndroidViewModelFactory(application) {
+
+    // Calling super class' ctor ensures that create method is called correctly and the right
+    // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
+    // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return InstallViewModel(application, repository) as T
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
deleted file mode 100644
index 3f7bce8..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-
-public class UninstallViewModel extends AndroidViewModel {
-
-    private static final String TAG = UninstallViewModel.class.getSimpleName();
-    private final UninstallRepository mRepository;
-    private final MediatorLiveData<UninstallStage> mCurrentUninstallStage =
-        new MediatorLiveData<>();
-
-    public UninstallViewModel(@NonNull Application application, UninstallRepository repository) {
-        super(application);
-        mRepository = repository;
-    }
-
-    public MutableLiveData<UninstallStage> getCurrentUninstallStage() {
-        return mCurrentUninstallStage;
-    }
-
-    public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
-        UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo);
-        if (stage.getStageCode() != UninstallStage.STAGE_ABORTED) {
-            stage = mRepository.generateUninstallDetails();
-        }
-        mCurrentUninstallStage.setValue(stage);
-    }
-
-    public void initiateUninstall(boolean keepData) {
-        mRepository.initiateUninstall(keepData);
-        // Since uninstall is an async operation, we will get the uninstall result later in time.
-        // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
-        // As such, mCurrentUninstallStage will need to add another MutableLiveData
-        // as a data source
-        mCurrentUninstallStage.addSource(mRepository.getUninstallResult(), uninstallStage -> {
-            if (uninstallStage != null) {
-                mCurrentUninstallStage.setValue(uninstallStage);
-            }
-        });
-    }
-
-    public void cancelInstall() {
-        mRepository.cancelInstall();
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
new file mode 100644
index 0000000..80886e9
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
@@ -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
+ *
+ *      https://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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+
+class UninstallViewModel(application: Application, val repository: UninstallRepository) :
+    AndroidViewModel(application) {
+
+    companion object {
+        private val LOG_TAG = UninstallViewModel::class.java.simpleName
+    }
+
+    private val _currentUninstallStage = MediatorLiveData<UninstallStage>()
+    val currentUninstallStage: MutableLiveData<UninstallStage>
+        get() = _currentUninstallStage
+
+    fun preprocessIntent(intent: Intent, callerInfo: UninstallRepository.CallerInfo) {
+        var stage = repository.performPreUninstallChecks(intent, callerInfo)
+        if (stage.stageCode != UninstallStage.STAGE_ABORTED) {
+            stage = repository.generateUninstallDetails()
+        }
+        _currentUninstallStage.value = stage
+    }
+
+    fun initiateUninstall(keepData: Boolean) {
+        repository.initiateUninstall(keepData)
+        // Since uninstall is an async operation, we will get the uninstall result later in time.
+        // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
+        // As such, _currentUninstallStage will need to add another MutableLiveData
+        // as a data source
+        _currentUninstallStage.addSource(repository.uninstallResult) { uninstallStage: UninstallStage? ->
+            if (uninstallStage != null) {
+                _currentUninstallStage.value = uninstallStage
+            }
+        }
+    }
+
+    fun cancelInstall() {
+        repository.cancelInstall()
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
deleted file mode 100644
index cd9845e..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-
-public class UninstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
-    private final UninstallRepository mRepository;
-    private final Application mApplication;
-
-    public UninstallViewModelFactory(Application application, UninstallRepository repository) {
-        // Calling super class' ctor ensures that create method is called correctly and the right
-        // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
-        // UninstallViewModel(application) is used, and repository isn't initialized in
-        // the viewmodel
-        super(application);
-        mApplication = application;
-        mRepository = repository;
-    }
-
-    @NonNull
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-        return (T) new UninstallViewModel(mApplication, mRepository);
-    }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
new file mode 100644
index 0000000..0a316e7
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallRepository
+
+class UninstallViewModelFactory(val application: Application, val repository: UninstallRepository) :
+    ViewModelProvider.AndroidViewModelFactory(application) {
+
+    // Calling super class' ctor ensures that create method is called correctly and the right
+    // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
+    // UninstallViewModel(application) is used, and repository isn't initialized in
+    // the viewmodel
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        return UninstallViewModel(application, repository) as T
+    }
+}
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index df72a92..044ba87 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -15,9 +15,12 @@
     resource_dirs: ["res"],
 
     static_libs: [
-          "androidx.annotation_annotation",
-          "SettingsLibTile"
+        "androidx.annotation_annotation",
+        "SettingsLibTile",
     ],
 
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2501869..ffe3d1d 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -60,6 +60,9 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
index 986baf7..bd0dbdc 100644
--- a/packages/SettingsLib/EmergencyNumber/Android.bp
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -17,4 +17,7 @@
     ],
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce..d9f74da 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -28,4 +28,7 @@
         "com.android.extservices",
         "com.android.healthfitness",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 028489d..3b04bd9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -30,4 +30,7 @@
         "//apex_available:platform",
         "com.android.permission",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 2d93e4e..22e4e94 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -14,9 +14,12 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
-          "androidx.annotation_annotation",
+        "androidx.annotation_annotation",
     ],
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c07a802..c385d38 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -15,4 +15,7 @@
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index eb9e329..19c59dd 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -14,8 +14,11 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
-          "androidx.annotation_annotation",
+        "androidx.annotation_annotation",
     ],
 
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index c7ad24c..d5a56c8 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -31,4 +31,7 @@
         "com.android.healthfitness",
         "com.android.permission",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 202e7fe..3b14712 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,6 +12,9 @@
     visibility: ["//visibility:private"],
     srcs: ["interface-src/**/*.java"],
     host_supported: true,
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_library {
@@ -24,6 +27,9 @@
 
     sdk_version: "system_current",
     min_sdk_version: "21",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_plugin {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index e5dbe5f..d6e8d26 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -112,5 +112,7 @@
         Settings.Global.Wearable.SCREENSHOT_ENABLED,
         Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
         Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
+        Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+        Settings.Global.FORCE_ENABLE_PSS_PROFILING,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 3027c5f..f8bdcf6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -447,5 +447,7 @@
         VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 0000000..472484a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+    viewModel: KeyguardLongPressViewModel,
+    modifier: Modifier = Modifier,
+    content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+    val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+    val interactionSource = remember { MutableInteractionSource() }
+
+    Box(
+        modifier =
+            modifier
+                .combinedClickable(
+                    enabled = isEnabled,
+                    onLongClick = viewModel::onLongPress,
+                    onClick = {},
+                    interactionSource = interactionSource,
+                    // Passing null for the indication removes the ripple effect.
+                    indication = null,
+                )
+                .pointerInput(settingsMenuBounds) {
+                    awaitEachGesture {
+                        val pointerInputChange = awaitFirstDown()
+                        if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+                            viewModel.onTouchedOutside()
+                        }
+                    }
+                },
+    ) {
+        content(setSettingsMenuBounds)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec..67a6820 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalFoundationApi::class)
-
 package com.android.systemui.keyguard.ui.composable
 
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.Edge
 import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: LockscreenSceneViewModel,
-    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
     private val lockscreenContent: Lazy<LockscreenContent>,
+    private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
@@ -93,9 +72,8 @@
         modifier: Modifier,
     ) {
         LockscreenScene(
-            viewProvider = viewProvider,
-            viewModel = viewModel,
             lockscreenContent = lockscreenContent,
+            viewBasedLockscreenContent = viewBasedLockscreenContent,
             modifier = modifier,
         )
     }
@@ -116,98 +94,21 @@
 
 @Composable
 private fun SceneScope.LockscreenScene(
-    viewProvider: () -> View,
-    viewModel: LockscreenSceneViewModel,
     lockscreenContent: Lazy<LockscreenContent>,
+    viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
     modifier: Modifier = Modifier,
 ) {
-    fun findSettingsMenu(): View {
-        return viewProvider().requireViewById(R.id.keyguard_settings_button)
-    }
-
-    Box(
-        modifier = modifier,
-    ) {
-        LongPressSurface(
-            viewModel = viewModel.longPress,
-            isSettingsMenuVisible = { findSettingsMenu().isVisible },
-            settingsMenuBounds = {
-                val bounds = android.graphics.Rect()
-                findSettingsMenu().getHitRect(bounds)
-                bounds.toComposeRect()
-            },
-            modifier = Modifier.fillMaxSize(),
-        )
-
-        if (UseLockscreenContent) {
-            lockscreenContent
-                .get()
-                .Content(
-                    modifier = Modifier.fillMaxSize(),
-                )
-        } else {
-            AndroidView(
-                factory = { _ ->
-                    val keyguardRootView = viewProvider()
-                    // Remove the KeyguardRootView from any parent it might already have in legacy
-                    // code just in case (a view can't have two parents).
-                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
-                    keyguardRootView
-                },
-                modifier = Modifier.fillMaxSize(),
+    if (UseLockscreenContent) {
+        lockscreenContent
+            .get()
+            .Content(
+                modifier = modifier.fillMaxSize(),
+            )
+    } else {
+        with(viewBasedLockscreenContent.get()) {
+            Content(
+                modifier = modifier.fillMaxSize(),
             )
         }
-
-        val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
-        Layout(
-            modifier = Modifier.fillMaxSize(),
-            content = {
-                NotificationStack(
-                    viewModel = viewModel.notifications,
-                    isScrimVisible = false,
-                )
-            }
-        ) { measurables, constraints ->
-            check(measurables.size == 1)
-            val height = notificationStackPosition.height.toInt()
-            val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
-            val placeable = measurables[0].measure(childConstraints)
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                val start = (constraints.maxWidth - placeable.measuredWidth) / 2
-                placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
-            }
-        }
     }
 }
-
-@Composable
-private fun LongPressSurface(
-    viewModel: KeyguardLongPressViewModel,
-    isSettingsMenuVisible: () -> Boolean,
-    settingsMenuBounds: () -> Rect,
-    modifier: Modifier = Modifier,
-) {
-    val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
-    Box(
-        modifier =
-            modifier
-                .combinedClickable(
-                    enabled = isEnabled,
-                    onLongClick = viewModel::onLongPress,
-                    onClick = {},
-                )
-                .pointerInput(Unit) {
-                    awaitEachGesture {
-                        val pointerInputChange = awaitFirstDown()
-                        if (
-                            isSettingsMenuVisible() &&
-                                !settingsMenuBounds().contains(pointerInputChange.position)
-                        ) {
-                            viewModel.onTouchedOutside()
-                        }
-                    }
-                },
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 0000000..976161b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+    private val viewModel: LockscreenSceneViewModel,
+    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+    @Composable
+    fun SceneScope.Content(
+        modifier: Modifier = Modifier,
+    ) {
+        fun findSettingsMenu(): View {
+            return viewProvider().requireViewById(R.id.keyguard_settings_button)
+        }
+
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { onSettingsMenuPlaced ->
+            AndroidView(
+                factory = { _ ->
+                    val keyguardRootView = viewProvider()
+                    // Remove the KeyguardRootView from any parent it might already have in legacy
+                    // code just in case (a view can't have two parents).
+                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+                    keyguardRootView
+                },
+                modifier = Modifier.fillMaxSize(),
+            )
+
+            val notificationStackPosition by
+                viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+            Layout(
+                modifier =
+                    Modifier.fillMaxSize().onPlaced {
+                        val settingsMenuView = findSettingsMenu()
+                        onSettingsMenuPlaced(
+                            if (settingsMenuView.isVisible) {
+                                val bounds = Rect()
+                                settingsMenuView.getHitRect(bounds)
+                                bounds.toComposeRect()
+                            } else {
+                                null
+                            }
+                        )
+                    },
+                content = {
+                    NotificationStack(
+                        viewModel = viewModel.notifications,
+                        isScrimVisible = false,
+                    )
+                }
+            ) { measurables, constraints ->
+                check(measurables.size == 1)
+                val height = notificationStackPosition.height.toInt()
+                val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+                val placeable = measurables[0].measure(childConstraints)
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+                    placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf..86124c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
 import javax.inject.Inject
 
 /** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+    private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
 
     override val id: String = "communal"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(modifier.background(Color.Black)) {
-            Text(
-                text = "TODO(b/316211368): communal blueprint",
-                color = Color.White,
-                modifier = Modifier.align(Alignment.Center),
-            )
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { _ ->
+            Box(modifier.background(Color.Black)) {
+                Text(
+                    text = "TODO(b/316211368): communal blueprint",
+                    color = Color.White,
+                    modifier = Modifier.align(Alignment.Center),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7314453..d9d98cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,17 +17,20 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.ClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
     private val lockSection: LockSection,
     private val ambientIndicationSection: AmbientIndicationSection,
     private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -59,102 +63,116 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Layout(
-            content = {
-                // Constrained to above the lock icon.
-                Column(
-                    modifier = Modifier.fillMaxWidth(),
-                ) {
-                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                    with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                    with(notificationSection) {
-                        Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                    }
-                    if (!isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-                }
-
-                with(lockSection) { LockIcon() }
-
-                // Aligned to bottom and constrained to below the lock icon.
-                Column(modifier = Modifier.fillMaxWidth()) {
-                    if (isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-
-                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
-                }
-
-                // Aligned to bottom and NOT constrained by the lock icon.
-                with(bottomAreaSection) {
-                    Shortcut(isStart = true, applyPadding = true)
-                    Shortcut(isStart = false, applyPadding = true)
-                }
-            },
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { measurables, constraints ->
-            check(measurables.size == 5)
-            val (
-                aboveLockIconMeasurable,
-                lockIconMeasurable,
-                belowLockIconMeasurable,
-                startShortcutMeasurable,
-                endShortcutMeasurable,
-            ) = measurables
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxWidth(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                        with(notificationSection) {
+                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                        }
+                        if (!isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+                    }
 
-            val noMinConstraints =
-                constraints.copy(
-                    minWidth = 0,
-                    minHeight = 0,
-                )
-            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-            val lockIconBounds =
-                IntRect(
-                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                )
+                    with(lockSection) { LockIcon() }
 
-            val aboveLockIconPlaceable =
-                aboveLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                )
-            val belowLockIconPlaceable =
-                belowLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
-                )
-            val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
-            val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
 
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                aboveLockIconPlaceable.place(
-                    x = 0,
-                    y = 0,
-                )
-                lockIconPlaceable.place(
-                    x = lockIconBounds.left,
-                    y = lockIconBounds.top,
-                )
-                belowLockIconPlaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - belowLockIconPlaceable.height,
-                )
-                startShortcutPleaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - startShortcutPleaceable.height,
-                )
-                endShortcutPleaceable.place(
-                    x = constraints.maxWidth - endShortcutPleaceable.width,
-                    y = constraints.maxHeight - endShortcutPleaceable.height,
-                )
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(bottomAreaSection) {
+                        Shortcut(isStart = true, applyPadding = true)
+                        Shortcut(isStart = false, applyPadding = true)
+                    }
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val lockIconMeasurable = measurables[1]
+                val belowLockIconMeasurable = measurables[2]
+                val startShortcutMeasurable = measurables[3]
+                val endShortcutMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                        )
+                    )
+                val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+                val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    startShortcutPleaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - startShortcutPleaceable.height,
+                    )
+                    endShortcutPleaceable.place(
+                        x = constraints.maxWidth - endShortcutPleaceable.width,
+                        y = constraints.maxHeight - endShortcutPleaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4c119c7..4704f5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,17 +17,20 @@
 package com.android.systemui.keyguard.ui.composable.blueprint
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.ClockSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
 import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
 import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
 import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@
     private val lockSection: LockSection,
     private val ambientIndicationSection: AmbientIndicationSection,
     private val bottomAreaSection: BottomAreaSection,
+    private val settingsMenuSection: SettingsMenuSection,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -59,105 +63,123 @@
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Layout(
-            content = {
-                // Constrained to above the lock icon.
-                Column(
-                    modifier = Modifier.fillMaxWidth(),
-                ) {
-                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                    with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                    with(notificationSection) {
-                        Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                    }
-                    if (!isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-                }
-
-                // Constrained to the left of the lock icon (in left-to-right layouts).
-                with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
-
-                with(lockSection) { LockIcon() }
-
-                // Constrained to the right of the lock icon (in left-to-right layouts).
-                with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
-
-                // Aligned to bottom and constrained to below the lock icon.
-                Column(modifier = Modifier.fillMaxWidth()) {
-                    if (isUdfpsVisible) {
-                        with(ambientIndicationSection) {
-                            AmbientIndication(modifier = Modifier.fillMaxWidth())
-                        }
-                    }
-
-                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
-                }
-            },
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
             modifier = modifier,
-        ) { measurables, constraints ->
-            check(measurables.size == 5)
-            val (
-                aboveLockIconMeasurable,
-                startSideShortcutMeasurable,
-                lockIconMeasurable,
-                endSideShortcutMeasurable,
-                belowLockIconMeasurable,
-            ) = measurables
+        ) { onSettingsMenuPlaced ->
+            Layout(
+                content = {
+                    // Constrained to above the lock icon.
+                    Column(
+                        modifier = Modifier.fillMaxWidth(),
+                    ) {
+                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                        with(notificationSection) {
+                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                        }
+                        if (!isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+                    }
 
-            val noMinConstraints =
-                constraints.copy(
-                    minWidth = 0,
-                    minHeight = 0,
-                )
+                    // Constrained to the left of the lock icon (in left-to-right layouts).
+                    with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
 
-            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
-            val lockIconBounds =
-                IntRect(
-                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                )
+                    with(lockSection) { LockIcon() }
 
-            val aboveLockIconPlaceable =
-                aboveLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
-                )
-            val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
-            val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
-            val belowLockIconPlaceable =
-                belowLockIconMeasurable.measure(
-                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
-                )
+                    // Constrained to the right of the lock icon (in left-to-right layouts).
+                    with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
 
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                aboveLockIconPlaceable.place(
-                    x = 0,
-                    y = 0,
-                )
-                startSideShortcutPlaceable.placeRelative(
-                    x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
-                    y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
-                )
-                lockIconPlaceable.place(
-                    x = lockIconBounds.left,
-                    y = lockIconBounds.top,
-                )
-                endSideShortcutPlaceable.placeRelative(
-                    x =
-                        lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
-                            endSideShortcutPlaceable.width / 2,
-                    y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
-                )
-                belowLockIconPlaceable.place(
-                    x = 0,
-                    y = constraints.maxHeight - belowLockIconPlaceable.height,
-                )
+                    // Aligned to bottom and constrained to below the lock icon.
+                    Column(modifier = Modifier.fillMaxWidth()) {
+                        if (isUdfpsVisible) {
+                            with(ambientIndicationSection) {
+                                AmbientIndication(modifier = Modifier.fillMaxWidth())
+                            }
+                        }
+
+                        with(bottomAreaSection) {
+                            IndicationArea(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+
+                    // Aligned to bottom and NOT constrained by the lock icon.
+                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+                },
+                modifier = Modifier.fillMaxSize(),
+            ) { measurables, constraints ->
+                check(measurables.size == 6)
+                val aboveLockIconMeasurable = measurables[0]
+                val startSideShortcutMeasurable = measurables[1]
+                val lockIconMeasurable = measurables[2]
+                val endSideShortcutMeasurable = measurables[3]
+                val belowLockIconMeasurable = measurables[4]
+                val settingsMenuMeasurable = measurables[5]
+
+                val noMinConstraints =
+                    constraints.copy(
+                        minWidth = 0,
+                        minHeight = 0,
+                    )
+
+                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconBounds =
+                    IntRect(
+                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                    )
+
+                val aboveLockIconPlaceable =
+                    aboveLockIconMeasurable.measure(
+                        noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                    )
+                val startSideShortcutPlaceable =
+                    startSideShortcutMeasurable.measure(noMinConstraints)
+                val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+                val belowLockIconPlaceable =
+                    belowLockIconMeasurable.measure(
+                        noMinConstraints.copy(
+                            maxHeight = constraints.maxHeight - lockIconBounds.bottom
+                        )
+                    )
+                val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    aboveLockIconPlaceable.place(
+                        x = 0,
+                        y = 0,
+                    )
+                    startSideShortcutPlaceable.placeRelative(
+                        x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+                        y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+                    )
+                    lockIconPlaceable.place(
+                        x = lockIconBounds.left,
+                        y = lockIconBounds.top,
+                    )
+                    endSideShortcutPlaceable.placeRelative(
+                        x =
+                            lockIconBounds.right +
+                                (constraints.maxWidth - lockIconBounds.right) / 2 -
+                                endSideShortcutPlaceable.width / 2,
+                        y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+                    )
+                    belowLockIconPlaceable.place(
+                        x = 0,
+                        y = constraints.maxHeight - belowLockIconPlaceable.height,
+                    )
+                    settingsMenuPlaceable.place(
+                        x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+                        y = constraints.maxHeight - settingsMenuPlaceable.height,
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5f..fdf1166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@
  * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
  * tablet form factor).
  */
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+    private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
 
     override val id: String = "split-shade"
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        Box(modifier.background(Color.Black)) {
-            Text(
-                text = "TODO(b/316211368): split shade blueprint",
-                color = Color.White,
-                modifier = Modifier.align(Alignment.Center),
-            )
+        LockscreenLongPress(
+            viewModel = viewModel.longPress,
+            modifier = modifier,
+        ) { _ ->
+            Box(modifier.background(Color.Black)) {
+                Text(
+                    text = "TODO(b/316211368): split shade blueprint",
+                    color = Color.White,
+                    modifier = Modifier.align(Alignment.Center),
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index d93863d..2a6bea7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -19,25 +19,33 @@
 import android.content.Context
 import android.util.DisplayMetrics
 import android.view.WindowManager
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
 import javax.inject.Inject
 
 class LockSection
@@ -46,48 +54,70 @@
     private val windowManager: WindowManager,
     private val authController: AuthController,
     private val featureFlags: FeatureFlagsClassic,
+    private val lockIconViewController: Lazy<LockIconViewController>,
+    private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+    private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+    private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
+    private val falsingManager: Lazy<FalsingManager>,
+    private val vibratorHelper: Lazy<VibratorHelper>,
 ) {
     @Composable
     fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
-        MovableElement(
-            key = LockIconElementKey,
-            modifier = modifier,
-        ) {
-            val context = LocalContext.current
-            Box(
-                modifier =
-                    Modifier.background(Color.Red).layout { measurable, _ ->
-                        val lockIconBounds = lockIconBounds(context)
-                        val placeable =
-                            measurable.measure(
-                                Constraints.fixed(
-                                    width = lockIconBounds.width,
-                                    height = lockIconBounds.height,
-                                )
-                            )
-                        layout(
-                            width = placeable.width,
-                            height = placeable.height,
-                            alignmentLines =
-                                mapOf(
-                                    BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
-                                    BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
-                                    BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
-                                    BlueprintAlignmentLines.LockIcon.Bottom to
-                                        lockIconBounds.bottom,
-                                ),
-                        ) {
-                            placeable.place(0, 0)
-                        }
-                    },
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Lock",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
+        if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+            return
         }
+
+        val context = LocalContext.current
+
+        AndroidView(
+            factory = { context ->
+                val view =
+                    if (DeviceEntryUdfpsRefactor.isEnabled) {
+                        DeviceEntryIconView(context, null).apply {
+                            id = R.id.device_entry_icon_view
+                            DeviceEntryIconViewBinder.bind(
+                                this,
+                                deviceEntryIconViewModel.get(),
+                                deviceEntryForegroundViewModel.get(),
+                                deviceEntryBackgroundViewModel.get(),
+                                falsingManager.get(),
+                                vibratorHelper.get(),
+                            )
+                        }
+                    } else {
+                        // keyguardBottomAreaRefactor()
+                        LockIconView(context, null).apply {
+                            id = R.id.lock_icon_view
+                            lockIconViewController.get().setLockIconView(this)
+                        }
+                    }
+                view
+            },
+            modifier =
+                modifier.element(LockIconElementKey).layout { measurable, _ ->
+                    val lockIconBounds = lockIconBounds(context)
+                    val placeable =
+                        measurable.measure(
+                            Constraints.fixed(
+                                width = lockIconBounds.width,
+                                height = lockIconBounds.height,
+                            )
+                        )
+                    layout(
+                        width = placeable.width,
+                        height = placeable.height,
+                        alignmentLines =
+                            mapOf(
+                                BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+                                BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+                                BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+                                BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+                            ),
+                    ) {
+                        placeable.place(0, 0)
+                    }
+                },
+        )
     }
 
     /**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f135be2..c547e2b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,36 +16,25 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 
-class NotificationSection @Inject constructor() {
+class NotificationSection
+@Inject
+constructor(
+    private val viewModel: NotificationsPlaceholderViewModel,
+) {
     @Composable
     fun SceneScope.Notifications(modifier: Modifier = Modifier) {
-        MovableElement(
-            key = NotificationsElementKey,
+        NotificationStack(
+            viewModel = viewModel,
+            isScrimVisible = false,
             modifier = modifier,
-        ) {
-            Box(
-                modifier = Modifier.fillMaxSize().background(Color.Yellow),
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Notifications",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
-        }
+        )
     }
 }
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 0000000..44b0535
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+    private val viewModel: KeyguardSettingsMenuViewModel,
+    private val longPressViewModel: KeyguardLongPressViewModel,
+    private val vibratorHelper: VibratorHelper,
+    private val activityStarter: ActivityStarter,
+) {
+    @Composable
+    @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+    fun SettingsMenu(
+        onPlaced: (Rect?) -> Unit,
+        modifier: Modifier = Modifier,
+    ) {
+        val (disposableHandle, setDisposableHandle) =
+            remember { mutableStateOf<DisposableHandle?>(null) }
+        AndroidView(
+            factory = { context ->
+                LayoutInflater.from(context)
+                    .inflate(
+                        R.layout.keyguard_settings_popup_menu,
+                        null,
+                    )
+                    .apply {
+                        isVisible = false
+                        alpha = 0f
+
+                        setDisposableHandle(
+                            KeyguardSettingsViewBinder.bind(
+                                view = this,
+                                viewModel = viewModel,
+                                longPressViewModel = longPressViewModel,
+                                rootViewModel = null,
+                                vibratorHelper = vibratorHelper,
+                                activityStarter = activityStarter,
+                            )
+                        )
+                    }
+            },
+            onRelease = { disposableHandle?.dispose() },
+            modifier =
+                modifier
+                    .padding(
+                        bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+                    )
+                    .padding(
+                        horizontal =
+                            dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+                    )
+                    .onPlaced { coordinates ->
+                        onPlaced(
+                            if (!coordinates.size.toSize().isEmpty()) {
+                                Rect(coordinates.positionInParent(), coordinates.size.toSize())
+                            } else {
+                                null
+                            }
+                        )
+                    },
+        )
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 26da1f0..4b21105 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -47,7 +47,7 @@
 
     suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
         withContext(backgroundDispatcher) {
-            secureSettingsRepository.set(
+            secureSettingsRepository.setInt(
                 name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 value = if (enabled) 1 else 0,
             )
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8..754d5dc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@
     ): Flow<Int>
 
     /** Updates the value of the setting with the given name. */
-    suspend fun set(
+    suspend fun setInt(
         name: String,
         value: Int,
     )
 
-    suspend fun get(
+    suspend fun getInt(
         name: String,
         defaultValue: Int = 0,
     ): Int
+
+    suspend fun getString(name: String): String?
 }
 
 class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@
             .flowOn(backgroundDispatcher)
     }
 
-    override suspend fun set(name: String, value: Int) {
+    override suspend fun setInt(name: String, value: Int) {
         withContext(backgroundDispatcher) {
             Settings.Secure.putInt(
                 contentResolver,
@@ -90,7 +92,7 @@
         }
     }
 
-    override suspend fun get(name: String, defaultValue: Int): Int {
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
         return withContext(backgroundDispatcher) {
             Settings.Secure.getInt(
                 contentResolver,
@@ -99,4 +101,13 @@
             )
         }
     }
+
+    override suspend fun getString(name: String): String? {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getString(
+                contentResolver,
+                name,
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a07..37b9792 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@
         return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
     }
 
-    override suspend fun set(name: String, value: Int) {
+    override suspend fun setInt(name: String, value: Int) {
         settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
     }
 
-    override suspend fun get(name: String, defaultValue: Int): Int {
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
         return settings.value[name]?.toInt() ?: defaultValue
     }
+
+    override suspend fun getString(name: String): String? {
+        return settings.value[name]
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index da97a12..1c1335f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -923,14 +923,14 @@
         doReturn(500).when(mResources)
                 .getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
                         .physical_fingerprint_sensor_center_screen_location_y));
-        mAuthController.onConfigurationChanged(null /* newConfig */);
+        mAuthController.onConfigChanged(null /* newConfig */);
 
         final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
 
         doReturn(1000).when(mResources)
                 .getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
                         .physical_fingerprint_sensor_center_screen_location_y));
-        mAuthController.onConfigurationChanged(null /* newConfig */);
+        mAuthController.onConfigChanged(null /* newConfig */);
 
         assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
     }
diff --git a/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
new file mode 100644
index 0000000..2dce37d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners
+        android:topLeftRadius="28dp"
+        android:topRightRadius="28dp"/>
+    <solid android:color="?android:attr/colorBackground" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 8d7f7eb..a71782b 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -22,7 +22,7 @@
     android:orientation="vertical"
     android:paddingHorizontal="@dimen/dialog_side_padding"
     android:paddingTop="@dimen/dialog_top_padding"
-    android:background="@*android:drawable/bottomsheet_background"
+    android:background="@drawable/connected_display_dialog_bg"
     android:paddingBottom="@dimen/dialog_bottom_padding">
 
     <ImageView
@@ -40,7 +40,7 @@
         android:id="@+id/connected_display_dialog_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+        android:layout_marginTop="16dp"
         android:gravity="center"
         android:text="@string/connected_display_dialog_start_mirroring"
         android:textAppearance="@style/TextAppearance.Dialog.Title" />
@@ -51,13 +51,14 @@
         android:layout_height="wrap_content"
         android:gravity="center"
         android:visibility="gone"
+        android:layout_marginTop="16dp"
         android:text="@string/connected_display_dialog_dual_display_stop_warning"
         android:textAppearance="@style/TextAppearance.Dialog.Body" />
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+        android:layout_marginTop="16dp"
         android:orientation="horizontal">
 
         <Button
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b25a7..e7eb984 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3265,7 +3265,7 @@
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
     <!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
-    <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
+    <string name="connected_display_dialog_dual_display_stop_warning">Your inner display will be mirrored. Your front display will be turned off.</string>
     <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
     <string name="mirror_display">Mirror display</string>
     <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 5b59e7d..2b41178 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,6 +34,9 @@
     srcs: [
         ":statslog-SystemUI-java-gen",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_library {
@@ -70,6 +73,9 @@
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
     kotlincflags: ["-Xjvm-default=all"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -81,6 +87,9 @@
     static_kotlin_stdlib: false,
     java_version: "1.8",
     min_sdk_version: "current",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -100,4 +109,7 @@
     },
     java_version: "1.8",
     min_sdk_version: "current",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 92f66902..387f2e1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -101,7 +101,7 @@
     }
 
     /** Alerts listener and plugin that the plugin has been created. */
-    public void onCreate() {
+    public synchronized void onCreate() {
         boolean loadPlugin = mListener.onPluginAttached(this);
         if (!loadPlugin) {
             if (mPlugin != null) {
@@ -128,7 +128,7 @@
     }
 
     /** Alerts listener and plugin that the plugin is being shutdown. */
-    public void onDestroy() {
+    public synchronized void onDestroy() {
         logDebug("onDestroy");
         unloadPlugin();
         mListener.onPluginDetached(this);
@@ -143,12 +143,13 @@
     /**
      * Loads and creates the plugin if it does not exist.
      */
-    public void loadPlugin() {
+    public synchronized void loadPlugin() {
         if (mPlugin != null) {
             logDebug("Load request when already loaded");
             return;
         }
 
+        // Both of these calls take about 1 - 1.5 seconds in test runs
         mPlugin = mPluginFactory.createPlugin();
         mPluginContext = mPluginFactory.createPluginContext();
         if (mPlugin == null || mPluginContext == null) {
@@ -171,7 +172,7 @@
      *
      * This will free the associated memory if there are not other references.
      */
-    public void unloadPlugin() {
+    public synchronized void unloadPlugin() {
         if (mPlugin == null) {
             logDebug("Unload request when already unloaded");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index c07a4d2..7295936 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui;
 
-import android.content.res.Configuration;
-
 import androidx.annotation.NonNull;
 
 import java.io.PrintWriter;
@@ -42,13 +40,6 @@
     /** Main entry point for implementations. Called shortly after SysUI startup. */
     void start();
 
-    /** Called when the device configuration changes. This will not be called before
-     * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
-     *
-     * @see android.app.Application#onConfigurationChanged(Configuration)  */
-    default void onConfigurationChanged(Configuration newConfig) {
-    }
-
     @Override
     default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 008de43..e03c627 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -89,6 +89,7 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
@@ -109,7 +110,8 @@
  * for antialiasing and emulation purposes.
  */
 @SysUISingleton
-public class ScreenDecorations implements CoreStartable, Dumpable {
+public class ScreenDecorations implements
+        CoreStartable, ConfigurationController.ConfigurationListener, Dumpable {
     private static final boolean DEBUG_LOGGING = false;
     private static final String TAG = "ScreenDecorations";
 
@@ -575,7 +577,7 @@
 
                     if (mPendingManualConfigUpdate) {
                         mPendingManualConfigUpdate = false;
-                        onConfigurationChanged(mContext.getResources().getConfiguration());
+                        onConfigChanged(mContext.getResources().getConfiguration());
                     }
                 }
             }
@@ -1062,7 +1064,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
             Log.i(TAG, "ScreenDecorations is disabled");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
new file mode 100644
index 0000000..044312b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ScreenDecorationsModule {
+    /** Start ScreenDecorations. */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenDecorations::class)
+    fun bindScreenDecorationsCoreStartable(impl: ScreenDecorations): CoreStartable
+
+    /** Listen to config changes for ScreenDecorations. */
+    @Binds
+    @IntoSet
+    fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c3f6480..01f6971 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,11 +37,11 @@
 import android.view.ThreadedRenderer;
 import android.view.View;
 
-import com.android.systemui.res.R;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.NotificationChannels;
 
@@ -354,19 +354,6 @@
             }
             configController.onConfigurationChanged(newConfig);
             Trace.endSection();
-            int len = mServices.length;
-            for (int i = 0; i < len; i++) {
-                if (mServices[i] != null) {
-                    if (Trace.isEnabled()) {
-                        Trace.traceBegin(
-                                Trace.TRACE_TAG_APP,
-                                mServices[i].getClass().getSimpleName()
-                                        + ".onConfigurationChanged()");
-                    }
-                    mServices[i].onConfigurationChanged(newConfig);
-                    Trace.endSection();
-                }
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7a8161e..da49201 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility;
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 
 import android.accessibilityservice.AccessibilityService;
@@ -57,6 +58,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.Assert;
 
@@ -71,7 +73,7 @@
  * Class to register system actions with accessibility framework.
  */
 @SysUISingleton
-public class SystemActions implements CoreStartable {
+public class SystemActions implements CoreStartable, ConfigurationController.ConfigurationListener {
     private static final String TAG = "SystemActions";
 
     /**
@@ -234,7 +236,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
         if (!locale.equals(mLocale)) {
             mLocale = locale;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
new file mode 100644
index 0000000..4d6d784
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.accessibility
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface SystemActionsModule {
+    /** Start SystemActions. */
+    @Binds
+    @IntoMap
+    @ClassKey(SystemActions::class)
+    fun bindSystemActionsStartable(sysui: SystemActions): CoreStartable
+
+    /** Listen to config changes for SystemActions. */
+    @Binds @IntoSet fun bindSystemActionsConfigChanges(sysui: SystemActions): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0bd4859..dde9f48 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1303,7 +1303,7 @@
         } else if (id == R.id.close_button) {
             setEditMagnifierSizeMode(false);
         } else {
-            animateBounceEffect();
+            animateBounceEffectIfNeeded();
         }
     }
 
@@ -1465,7 +1465,12 @@
         mBounceEffectDuration = duration;
     }
 
-    private void animateBounceEffect() {
+    private void animateBounceEffectIfNeeded() {
+        if (mMirrorView == null) {
+            // run the animation only if the mirror view is not null
+            return;
+        }
+
         final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
                 PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
                 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1, mBounceEffectAnimationScale, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127..49e0df6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@
  * Controls the interaction between {@link MagnetizedObject} and
  * {@link MagnetizedObject.MagneticTarget}.
  */
-class DismissAnimationController {
+class DragToInteractAnimationController {
     private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
     private static final float COMPLETELY_OPAQUE = 1.0f;
     private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@
     private float mMinDismissSize;
     private float mSizePercent;
 
-    DismissAnimationController(DismissView dismissView, MenuView menuView) {
+    DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
         mDismissView = dismissView;
         mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
         mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@
      * @param event that move the magnetized object which is also the menu list view.
      * @return true if the location of the motion events moves within the magnetic field of a
      * target, but false if didn't set
-     * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+     * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
     boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
         return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@
      * @param event that move the magnetized object which is also the menu list view.
      * @return true if the location of the motion events moves within the magnetic field of a
      * target, but false if didn't set
-     * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+     * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
      */
     boolean maybeConsumeUpMotionEvent(MotionEvent event) {
         return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cec..a270558 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@
     private final ValueAnimator mFadeOutAnimator;
     private final Handler mHandler;
     private boolean mIsFadeEffectEnabled;
-    private DismissAnimationController.DismissCallback mDismissCallback;
+    private DragToInteractAnimationController.DismissCallback mDismissCallback;
     private Runnable mSpringAnimationsEndAction;
 
     // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@
     }
 
     void setDismissCallback(
-            DismissAnimationController.DismissCallback dismissCallback) {
+            DragToInteractAnimationController.DismissCallback dismissCallback) {
         mDismissCallback = dismissCallback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f..52e7b91 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@
     private final PointF mMenuTranslationDown = new PointF();
     private boolean mIsDragging = false;
     private float mTouchSlop;
-    private final DismissAnimationController mDismissAnimationController;
+    private final DragToInteractAnimationController mDragToInteractAnimationController;
     private Optional<Runnable> mOnActionDownEnd = Optional.empty();
 
     MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
-            DismissAnimationController dismissAnimationController) {
+            DragToInteractAnimationController dragToInteractAnimationController) {
         mMenuAnimationController = menuAnimationController;
-        mDismissAnimationController = dismissAnimationController;
+        mDragToInteractAnimationController = dragToInteractAnimationController;
     }
 
     @Override
@@ -67,7 +67,7 @@
                 mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
 
                 mMenuAnimationController.cancelAnimations();
-                mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+                mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
 
                 mOnActionDownEnd.ifPresent(Runnable::run);
                 break;
@@ -78,9 +78,10 @@
                         mMenuAnimationController.onDraggingStart();
                     }
 
-                    mDismissAnimationController.showDismissView(/* show= */ true);
+                    mDragToInteractAnimationController.showDismissView(/* show= */ true);
 
-                    if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+                    if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+                            motionEvent)) {
                         mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
                         mMenuAnimationController.moveToPositionYIfNeeded(
                                 mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@
                     mIsDragging = false;
 
                     if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
-                        mDismissAnimationController.showDismissView(/* show= */ false);
+                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                         mMenuAnimationController.fadeOutIfEnabled();
 
                         return true;
                     }
 
-                    if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+                    if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+                            motionEvent)) {
                         mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
                         mMenuAnimationController.flingMenuThenSpringToEdge(endX,
                                 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-                        mDismissAnimationController.showDismissView(/* show= */ false);
+                        mDragToInteractAnimationController.showDismissView(/* show= */ false);
                     }
 
                     // Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index ff3a9e3..62d5feb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
     private final SecureSettings mSecureSettings;
-    private final DismissAnimationController mDismissAnimationController;
+    private final DragToInteractAnimationController mDragToInteractAnimationController;
     private final MenuViewModel mMenuViewModel;
     private final Observer<Boolean> mDockTooltipObserver =
             this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
-        mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+        mDragToInteractAnimationController = new DragToInteractAnimationController(
+                mDismissView, mMenuView);
+        mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
             }
 
             @Override
             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
                     float velocityX, float velocityY, boolean wasFlungOut) {
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
             }
 
             @Override
             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                 hideMenuAndShowMessage();
                 mDismissView.hide();
-                mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
             }
         });
 
         mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
-                mDismissAnimationController);
+                mDragToInteractAnimationController);
         mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
         mMenuView.setMoveToTuckedListener(this);
 
@@ -243,7 +244,7 @@
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         mDismissView.updateResources();
-        mDismissAnimationController.updateResources();
+        mDragToInteractAnimationController.updateResources();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 5fba761..8a1a2da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -84,6 +84,7 @@
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -114,8 +115,12 @@
  * {@link com.android.keyguard.KeyguardUpdateMonitor}
  */
 @SysUISingleton
-public class AuthController implements CoreStartable, CommandQueue.Callbacks,
-        AuthDialogCallback, DozeReceiver {
+public class AuthController implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks,
+        AuthDialogCallback,
+        DozeReceiver {
 
     private static final String TAG = "AuthController";
     private static final boolean DEBUG = true;
@@ -1297,7 +1302,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         updateSensorLocations();
 
         // TODO(b/287311775): consider removing this to retain the UI cleanly vs re-creating
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 8ae6f87..307b985 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -19,6 +19,7 @@
 import android.content.res.Resources
 import com.android.internal.R
 import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
@@ -38,18 +39,30 @@
 import com.android.systemui.biometrics.udfps.OverlapDetector
 import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.android.systemui.util.concurrency.ThreadFactory
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import java.util.concurrent.Executor
 import javax.inject.Qualifier
 
 /** Dagger module for all things biometric. */
 @Module
 interface BiometricsModule {
+    /** Starts AuthController.  */
+    @Binds
+    @IntoMap
+    @ClassKey(AuthController::class)
+    fun bindAuthControllerStartable(service: AuthController): CoreStartable
+
+    /** Listen to config changes for AuthController. */
+    @Binds
+    @IntoSet
+    fun bindAuthControllerConfigChanges(service: AuthController): ConfigurationListener
 
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index a8c9446..c36e0e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -47,6 +47,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -59,49 +60,56 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Application private val applicationContext: Context,
-    private val biometricStatusInteractor: BiometricStatusInteractor,
-    private val displayStateInteractor: DisplayStateInteractor,
-    private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
-    private val fpsUnlockTracker: FpsUnlockTracker,
-    private val layoutInflater: LayoutInflater,
-    private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
-    private val sfpsSensorInteractor: SideFpsSensorInteractor,
-    private val windowManager: WindowManager
+    private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
+    private val displayStateInteractor: Lazy<DisplayStateInteractor>,
+    private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
+    private val fpsUnlockTracker: Lazy<FpsUnlockTracker>,
+    private val layoutInflater: Lazy<LayoutInflater>,
+    private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
+    private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+    private val windowManager: Lazy<WindowManager>
 ) : CoreStartable {
 
     override fun start() {
         if (!SideFpsControllerRefactor.isEnabled) {
             return
         }
+
         applicationScope
             .launch {
-                combine(
-                        biometricStatusInteractor.sfpsAuthenticationReason,
-                        deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
-                        sideFpsProgressBarViewModel.isVisible,
-                        ::Triple
-                    )
-                    .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
-                    .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
-                        val (
-                            systemServerAuthReason,
-                            showIndicatorForDeviceEntry,
-                            progressBarIsVisible) =
-                            combinedFlows
-                        if (!isInRearDisplayMode) {
-                            if (progressBarIsVisible) {
-                                hide()
-                            } else if (systemServerAuthReason != NotRunning) {
-                                show()
-                            } else if (showIndicatorForDeviceEntry) {
-                                show()
-                            } else {
-                                hide()
+                sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+                    if (isSfpsAvailable) {
+                        combine(
+                                biometricStatusInteractor.get().sfpsAuthenticationReason,
+                                deviceEntrySideFpsOverlayInteractor
+                                    .get()
+                                    .showIndicatorForDeviceEntry,
+                                sideFpsProgressBarViewModel.get().isVisible,
+                                ::Triple
+                            )
+                            .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+                            .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+                                val (
+                                    systemServerAuthReason,
+                                    showIndicatorForDeviceEntry,
+                                    progressBarIsVisible) =
+                                    combinedFlows
+                                if (!isInRearDisplayMode) {
+                                    if (progressBarIsVisible) {
+                                        hide()
+                                    } else if (systemServerAuthReason != NotRunning) {
+                                        show()
+                                    } else if (showIndicatorForDeviceEntry) {
+                                        show()
+                                    } else {
+                                        hide()
+                                    }
+                                }
                             }
-                        }
                     }
+                }
             }
-            .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+            .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
     }
 
     private var overlayView: View? = null
@@ -113,29 +121,29 @@
             if (it.isAttachedToWindow) {
                 lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
                 lottie?.pauseAnimation()
-                windowManager.removeView(it)
+                windowManager.get().removeView(it)
             }
         }
 
-        overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+        overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
         val overlayViewModel =
             SideFpsOverlayViewModel(
                 applicationContext,
-                biometricStatusInteractor,
-                deviceEntrySideFpsOverlayInteractor,
-                displayStateInteractor,
-                sfpsSensorInteractor,
-                sideFpsProgressBarViewModel
+                biometricStatusInteractor.get(),
+                deviceEntrySideFpsOverlayInteractor.get(),
+                displayStateInteractor.get(),
+                sfpsSensorInteractor.get(),
+                sideFpsProgressBarViewModel.get()
             )
-        bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+        bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
         overlayView!!.visibility = View.INVISIBLE
-        windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+        windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
     }
 
     /** Hide the side fingerprint sensor indicator */
     private fun hide() {
         if (overlayView != null) {
-            windowManager.removeView(overlayView)
+            windowManager.get().removeView(overlayView)
             overlayView = null
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 236c5b8..50f861f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -23,6 +23,8 @@
 import android.hardware.SensorPrivacyManager;
 
 import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.battery.BatterySaverModule;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
@@ -34,6 +36,7 @@
 import com.android.systemui.power.dagger.PowerModule;
 import com.android.systemui.qs.dagger.QSModule;
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.reardisplay.RearDisplayModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.rotationlock.RotationLockModule;
@@ -59,6 +62,7 @@
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.toast.ToastModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
@@ -89,19 +93,23 @@
         CollapsedStatusBarFragmentStartableModule.class,
         GestureModule.class,
         HeadsUpModule.class,
+        KeyboardShortcutsModule.class,
         MediaModule.class,
         MultiUserUtilsModule.class,
         NavigationBarControllerModule.class,
         PowerModule.class,
         QSModule.class,
-        ShadeModule.class,
+        RearDisplayModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
-        SceneContainerFrameworkModule.class,
+        ScreenDecorationsModule.class,
+        SystemActionsModule.class,
+        ShadeModule.class,
         StartCentralSurfacesModule.class,
+        SceneContainerFrameworkModule.class,
+        ToastModule.class,
         VolumeModule.class,
-        WallpaperModule.class,
-        KeyboardShortcutsModule.class
+        WallpaperModule.class
 })
 public abstract class ReferenceSystemUIModule {
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d041acb..ac71664 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -19,12 +19,9 @@
 import com.android.keyguard.KeyguardBiometricLockoutLogger
 import com.android.systemui.CoreStartable
 import com.android.systemui.LatencyTester
-import com.android.systemui.ScreenDecorations
 import com.android.systemui.SliceBroadcastRelayHandler
-import com.android.systemui.accessibility.SystemActions
 import com.android.systemui.accessibility.Magnification
 import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.BiometricNotificationService
 import com.android.systemui.clipboardoverlay.ClipboardListener
 import com.android.systemui.controls.dagger.StartControlsStartableModule
@@ -46,10 +43,6 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
 import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.power.PowerUI
-import com.android.systemui.reardisplay.RearDisplayDialogController
-import com.android.systemui.recents.Recents
-import com.android.systemui.recents.ScreenPinningRequest
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -61,11 +54,9 @@
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
-import com.android.systemui.toast.ToastUI
 import com.android.systemui.usb.StorageNotification
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.StartBinderLoggerModule
-import com.android.systemui.volume.VolumeUI
 import com.android.systemui.wallpapers.dagger.WallpaperModule
 import com.android.systemui.wmshell.WMShell
 import dagger.Binds
@@ -74,7 +65,12 @@
 import dagger.multibindings.IntoMap
 
 /**
- * Collection of {@link CoreStartable}s that should be run on AOSP.
+ * DEPRECATED: DO NOT ADD THINGS TO THIS FILE.
+ *
+ * Add a feature specific daggger module for what you are working on. Bind your CoreStartable there.
+ * Include that module where it is needed.
+ *
+ * @deprecated
  */
 @Module(
     includes = [
@@ -85,12 +81,6 @@
     ]
 )
 abstract class SystemUICoreStartableModule {
-    /** Inject into AuthController.  */
-    @Binds
-    @IntoMap
-    @ClassKey(AuthController::class)
-    abstract fun bindAuthController(service: AuthController): CoreStartable
-
     /** Inject into BiometricNotificationService */
     @Binds
     @IntoMap
@@ -158,18 +148,6 @@
     @PerUser
     abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
 
-    /** Inject into PowerUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(PowerUI::class)
-    abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
-
-    /** Inject into Recents.  */
-    @Binds
-    @IntoMap
-    @ClassKey(Recents::class)
-    abstract fun bindRecents(sysui: Recents): CoreStartable
-
     /** Inject into ImmersiveModeConfirmation.  */
     @Binds
     @IntoMap
@@ -182,12 +160,6 @@
     @ClassKey(RingtonePlayer::class)
     abstract fun bind(sysui: RingtonePlayer): CoreStartable
 
-    /** Inject into ScreenDecorations.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ScreenDecorations::class)
-    abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
-
     /** Inject into GesturePointerEventHandler. */
     @Binds
     @IntoMap
@@ -218,23 +190,12 @@
     @ClassKey(StorageNotification::class)
     abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
 
-    /** Inject into SystemActions.  */
-    @Binds
-    @IntoMap
-    @ClassKey(SystemActions::class)
-    abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
-
     /** Inject into ThemeOverlayController.  */
     @Binds
     @IntoMap
     @ClassKey(ThemeOverlayController::class)
     abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
 
-    /** Inject into ToastUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(ToastUI::class)
-    abstract fun bindToastUI(service: ToastUI): CoreStartable
 
     /** Inject into MediaOutputSwitcherDialogUI.  */
     @Binds
@@ -242,12 +203,6 @@
     @ClassKey(MediaOutputSwitcherDialogUI::class)
     abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
 
-    /** Inject into VolumeUI.  */
-    @Binds
-    @IntoMap
-    @ClassKey(VolumeUI::class)
-    abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
-
     /** Inject into Magnification.  */
     @Binds
     @IntoMap
@@ -293,11 +248,6 @@
     abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
 
 
-    /** Inject into RearDisplayDialogController) */
-    @Binds
-    @IntoMap
-    @ClassKey(RearDisplayDialogController::class)
-    abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
 
     /** Inject into StylusUsiPowerStartable) */
     @Binds
@@ -361,9 +311,4 @@
     @IntoMap
     @ClassKey(KeyguardDismissBinder::class)
     abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
-
-    @Binds
-    @IntoMap
-    @ClassKey(ScreenPinningRequest::class)
-    abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 734a879..1b35005 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -605,11 +605,6 @@
     val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
         unreleasedFlag("bigpicture_notification_lazy_loading")
 
-    // TODO(b/292062937): Tracking bug
-    @JvmField
-    val NOTIFICATION_CLEARABLE_REFACTOR =
-            unreleasedFlag("notification_clearable_refactor")
-
     // TODO(b/283740863): Tracking Bug
     @JvmField
     val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361..cfa5294 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@
             SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
         }
     }
+
+    /** The arrow navigation that was operating the slider has stopped. */
+    fun onArrowUp() {
+        _currentEvent.update { previousEvent ->
+            SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63..10098fa 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@
 
     override suspend fun iterateState(event: SliderEvent) {
         when (currentState) {
-            SliderState.IDLE -> handleIdle(event.type)
+            SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
             SliderState.WAIT -> handleWait(event.type, event.currentProgress)
             SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
             SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@
             SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
             SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
             SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+            SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+            SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+                handleArrowContinuous(event.type, event.currentProgress)
+            SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
         }
         latestProgress = event.currentProgress
     }
 
-    private fun handleIdle(newEventType: SliderEventType) {
+    private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
         if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
             timerJob = launchTimer()
             // The WAIT state will wait for the timer to complete or a slider progress to occur.
             // This will disambiguate between an imprecise touch that acquires the slider handle,
             // and a select and jump operation in the slider track.
             setState(SliderState.WAIT)
+        } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+            val state =
+                if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+                else SliderState.ARROW_HANDLE_MOVED_ONCE
+            setState(state)
         }
     }
 
@@ -176,6 +185,13 @@
             SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
             SliderState.JUMP_TRACK_LOCATION_SELECTED ->
                 sliderListener.onProgressJump(latestProgress)
+            SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+            SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+            SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+                executeOnBookend()
+                // This transitory execution must also reset the state
+                resetState()
+            }
             else -> {}
         }
     }
@@ -204,6 +220,43 @@
             currentProgress <= config.lowerBookendThreshold
     }
 
+    private fun handleArrowOnce(newEventType: SliderEventType) {
+        val nextState =
+            when (newEventType) {
+                SliderEventType.STARTED_TRACKING_TOUCH -> {
+                    // Launching the timer and going to WAIT
+                    timerJob = launchTimer()
+                    SliderState.WAIT
+                }
+                SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+                    SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+                SliderEventType.ARROW_UP -> SliderState.IDLE
+                else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+            }
+        setState(nextState)
+    }
+
+    private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+        val reachedBookend = bookendReached(currentProgress)
+        val nextState =
+            when (newEventType) {
+                SliderEventType.ARROW_UP -> SliderState.IDLE
+                SliderEventType.STARTED_TRACKING_TOUCH -> {
+                    // Launching the timer and going to WAIT
+                    timerJob = launchTimer()
+                    SliderState.WAIT
+                }
+                SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+                    if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+                    else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+                }
+                else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+            }
+        setState(nextState)
+    }
+
+    private fun handleArrowBookend() = setState(SliderState.IDLE)
+
     @VisibleForTesting
     fun setState(state: SliderState) {
         currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e277..4a63941 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@
     /* The slider has stopped tracking touch events. */
     STOPPED_TRACKING_TOUCH,
     /* The external (not touch) stimulus that was modifying the slider progress has stopped. */
-    EXTERNAL_STIMULUS_RELEASE,
+    ARROW_UP,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e6..de6ddd7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@
     DRAG_HANDLE_REACHED_BOOKEND,
     /* A location in the slider track has been selected. */
     JUMP_TRACK_LOCATION_SELECTED,
-    /* The slider handled moved to a bookend after it was selected. */
+    /* The slider handle moved to a bookend after it was selected. */
     JUMP_BOOKEND_SELECTED,
+    /** The slider handle moved due to single select-and-arrow operation */
+    ARROW_HANDLE_MOVED_ONCE,
+    /** The slider handle moves continuously due to constant select-and-arrow operations */
+    ARROW_HANDLE_MOVES_CONTINUOUSLY,
+    /** The slider handle reached a bookend due to a select-and-arrow operation */
+    ARROW_HANDLE_REACHED_BOOKEND,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 4d60dd0..17d7836 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -626,17 +626,19 @@
             faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
             return
         }
-        detectCancellationSignal?.cancel()
-        detectCancellationSignal = CancellationSignal()
         withContext(mainDispatcher) {
             // We always want to invoke face detect in the main thread.
             faceAuthLogger.faceDetectionStarted()
-            faceManager?.detectFace(
-                checkNotNull(detectCancellationSignal),
-                detectionCallback,
-                SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
-                    .toFaceAuthenticateOptions()
-            )
+            detectCancellationSignal?.cancel()
+            detectCancellationSignal = CancellationSignal()
+            detectCancellationSignal?.let {
+                faceManager?.detectFace(
+                    it,
+                    detectionCallback,
+                    SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
+                        .toFaceAuthenticateOptions()
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index c98f637..ecf78d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -23,15 +23,18 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.res.R
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
 
 /**
  * Encapsulates business logic for device entry events that impact the side fingerprint sensor
@@ -41,6 +44,7 @@
 class DeviceEntrySideFpsOverlayInteractor
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -50,7 +54,13 @@
 
     init {
         if (!DeviceEntryUdfpsRefactor.isEnabled) {
-            alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+            applicationScope.launch {
+                deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType ->
+                    if (sensorType == BiometricType.SIDE_FINGERPRINT) {
+                        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+                    }
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206..96e83b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@
                                 vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
                                 settingsMenu.setOnTouchListener(
                                     KeyguardSettingsButtonOnTouchListener(
-                                        view = settingsMenu,
                                         viewModel = viewModel.settingsMenuViewModel,
                                     )
                                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 362e7e6..fad0370 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -255,6 +255,7 @@
                                 vibratorHelper.performHapticFeedback(
                                     view,
                                     HapticFeedbackConstants.CONFIRM,
+                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
                                 )
                             }
                         }
@@ -264,6 +265,7 @@
                                 vibratorHelper.performHapticFeedback(
                                     view,
                                     HapticFeedbackConstants.REJECT,
+                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
                                 )
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c..c6dfcb0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.ui.view.rawDistanceFrom
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
 
 class KeyguardSettingsButtonOnTouchListener(
-    private val view: LaunchableLinearLayout,
     private val viewModel: KeyguardSettingsMenuViewModel,
 ) : View.OnTouchListener {
 
@@ -41,8 +39,10 @@
             MotionEvent.ACTION_UP -> {
                 view.isPressed = false
                 val distanceMoved =
-                    motionEvent
-                        .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+                    motionEvent.rawDistanceFrom(
+                        downPositionDisplayCoords.x,
+                        downPositionDisplayCoords.y
+                    )
                 val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
                 viewModel.onTouchGestureEnded(isClick)
                 if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e7..f67cb68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@
 
 object KeyguardSettingsViewBinder {
     fun bind(
-        parentView: View,
+        view: View,
         viewModel: KeyguardSettingsMenuViewModel,
         longPressViewModel: KeyguardLongPressViewModel,
-        rootViewModel: KeyguardRootViewModel,
+        rootViewModel: KeyguardRootViewModel?,
         vibratorHelper: VibratorHelper,
         activityStarter: ActivityStarter
     ): DisposableHandle {
-        val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@
                                 vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
                                 view.setOnTouchListener(
                                     KeyguardSettingsButtonOnTouchListener(
-                                        view = view,
                                         viewModel = viewModel,
                                     )
                                 )
@@ -96,7 +92,7 @@
                     }
 
                     launch {
-                        rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+                        rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
                             if (view.isVisible) {
                                 val hitRect = Rect()
                                 view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 24240df..940d1e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -142,6 +142,11 @@
             return true
         }
 
+        if (renderer == null || onDestroy == null) {
+            Log.wtf(TAG, "Renderer/onDestroy should not be null.")
+            return true
+        }
+
         when (message.what) {
             KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
                 message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569..36bbe4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@
 constructor(
     private val interactor: KeyguardBlueprintInteractor,
     private val authController: AuthController,
+    val longPress: KeyguardLongPressViewModel,
 ) {
     val isUdfpsVisible: Boolean
         get() = authController.isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index e827a1e..3e6d46c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -25,12 +25,12 @@
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.ViewPriority
@@ -162,7 +162,7 @@
         logger: MediaTttSenderLogger,
         instanceId: InstanceId,
     ): ChipbarInfo {
-        val packageName = checkNotNull(routeInfo.clientPackageName)
+        val packageName = routeInfo.clientPackageName
         val otherDeviceName =
             if (routeInfo.name.isBlank()) {
                 context.getString(R.string.media_ttt_default_device_type)
@@ -171,7 +171,7 @@
             }
         val icon =
             MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
-                logger.logPackageNotFound(packageName)
+                packageName?.let { logger.logPackageNotFound(it) }
             }
 
         val timeout =
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1534653..958ace35 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -48,12 +48,13 @@
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -62,7 +63,10 @@
 import javax.inject.Inject;
 
 @SysUISingleton
-public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
+public class PowerUI implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
 
     static final String TAG = "PowerUI";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -223,7 +227,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
 
         // Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 7184fa0..8dd0ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,14 +16,19 @@
 
 package com.android.systemui.power.dagger;
 
+import com.android.systemui.CoreStartable;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.power.PowerNotificationWarnings;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.power.data.repository.PowerRepositoryModule;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
 
 
 /** Dagger Module for code in the power package. */
@@ -33,6 +38,17 @@
         }
 )
 public interface PowerModule {
+    /** Starts PowerUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(PowerUI.class)
+    CoreStartable bindPowerUIStartable(PowerUI impl);
+
+    /** Listen to config changes for PowerUI.  */
+    @Binds
+    @IntoSet
+    ConfigurationController.ConfigurationListener bindPowerUIConfigChanges(PowerUI impl);
+
     /** */
     @Binds
     EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 4b21e44..f071623 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -29,11 +29,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieDrawable;
@@ -57,7 +58,10 @@
  */
 @SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
 @SysUISingleton
-public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+public class RearDisplayDialogController implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
 
     private int[] mFoldedStates;
     private boolean mStartedFolded;
@@ -96,7 +100,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
                 && mDialogViewContainer != null) {
             // Refresh the dialog view when configuration is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
new file mode 100644
index 0000000..6ab294d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.reardisplay
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface RearDisplayModule {
+
+    /** Start RearDisplayDialogController. */
+    @Binds
+    @IntoMap
+    @ClassKey(RearDisplayDialogController::class)
+    abstract fun bindRearDisplayDialogControllerStartable(
+        impl: RearDisplayDialogController
+    ): CoreStartable
+
+    /** Listen to config changes for RearDisplayDialogController. */
+    @Binds
+    @IntoSet
+    fun bindRearDisplayDialogControllerConfigChanges(
+        impl: RearDisplayDialogController
+    ): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b041f95..4ee65b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -23,13 +23,17 @@
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
 
 /**
  * A proxy to a Recents implementation.
  */
-public class Recents implements CoreStartable, CommandQueue.Callbacks {
+public class Recents implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
 
     private final Context mContext;
     private final RecentsImplementation mImpl;
@@ -53,7 +57,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         mImpl.onConfigurationChanged(newConfig);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
index 77a4b9f7..1108917 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
@@ -18,14 +18,17 @@
 
 import android.content.Context;
 
-import com.android.systemui.res.R;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
 
 /**
  * Dagger injection module for {@link RecentsImplementation}
@@ -33,6 +36,28 @@
 @Module
 public abstract class RecentsModule {
 
+    /** Start Recents.  */
+    @Binds
+    @IntoMap
+    @ClassKey(Recents.class)
+    abstract CoreStartable bindRecentsStartable(Recents impl);
+
+    /** Listen to config changes for Recents.  */
+    @Binds
+    @IntoSet
+    abstract ConfigurationListener bindRecentsConfigChanges(Recents impl);
+
+    /** Start ScreenPinningRequest.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenPinningRequest.class)
+    abstract CoreStartable bindScreenPinningRequestStartable(ScreenPinningRequest impl);
+
+    /** Listen to config changes for ScreenPinningRequest.  */
+    @Binds
+    @IntoSet
+    abstract ConfigurationListener bindScreenPinningRequestConfigChanges(ScreenPinningRequest impl);
+
     /**
      * @return The {@link RecentsImplementation} from the config.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3e574e7..2b717cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -54,25 +54,29 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.leak.RotationUtils;
 
+import dagger.Lazy;
+
 import java.util.ArrayList;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 @SysUISingleton
-public class ScreenPinningRequest implements View.OnClickListener,
-        NavigationModeController.ModeChangedListener, CoreStartable {
+public class ScreenPinningRequest implements
+        View.OnClickListener,
+        NavigationModeController.ModeChangedListener,
+        CoreStartable,
+        ConfigurationController.ConfigurationListener {
     private static final String TAG = "ScreenPinningRequest";
 
     private final Context mContext;
@@ -149,7 +153,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (mRequestWindow != null) {
             mRequestWindow.onConfigurationChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d13edf0..d382b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,8 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
 
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
 import android.app.Activity;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -87,6 +89,17 @@
         if (mShadeInteractor.isQsExpanded().getValue()) {
             finish();
         }
+
+        View view = findViewById(R.id.brightness_mirror_container);
+        if (view != null) {
+            collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange);
+        }
+    }
+
+    private void onShadeStateChange(boolean isQsExpanded) {
+        if (isQsExpanded) {
+            requestFinish();
+        }
     }
 
     private void setWindowAttributes() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f..be1fa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@
                 mListener.onChanged(mTracking, progress, false);
                 SeekableSliderEventProducer eventProducer =
                         mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
-                if (eventProducer != null) {
+                if (eventProducer != null && fromUser) {
                     eventProducer.onProgressChanged(seekBar, progress, fromUser);
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 8fe0022..b76cdb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -237,7 +237,7 @@
 
                 // Add and bind.
                 val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
-                for ((idx, notifKey) in toAdd.withIndex()) {
+                for (notifKey in toAdd) {
                     // Lookup the StatusBarIconView from the store.
                     val sbiv = viewStore.iconView(notifKey)
                     if (sbiv == null) {
@@ -256,7 +256,7 @@
                         // added again.
                         removeTransientView(sbiv)
                     }
-                    view.addView(sbiv, idx)
+                    view.addView(sbiv)
                     boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
                     boundViewsByNotifKey[notifKey] =
                         Pair(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 97cb45a..6e8ad2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -21,13 +21,17 @@
 import android.os.LocaleList
 import android.view.View.LAYOUT_DIRECTION_RTL
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import javax.inject.Inject
 
 @SysUISingleton
-class ConfigurationControllerImpl @Inject constructor(context: Context) : ConfigurationController {
+class ConfigurationControllerImpl @Inject constructor(
+        @Application context: Context,
+        ) : ConfigurationController {
 
-    private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
+    private val listeners: MutableList<ConfigurationListener> = ArrayList()
     private val lastConfig = Configuration()
     private var density: Int = 0
     private var smallestScreenWidth: Int = 0
@@ -143,14 +147,12 @@
         }
     }
 
-
-
-    override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+    override fun addCallback(listener: ConfigurationListener) {
         listeners.add(listener)
         listener.onDensityOrFontScaleChanged()
     }
 
-    override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+    override fun removeCallback(listener: ConfigurationListener) {
         listeners.remove(listener)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
new file mode 100644
index 0000000..90ebaf2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@SysUISingleton
+class ConfigurationControllerStartable
+@Inject
+constructor(
+    private val configurationController: ConfigurationController,
+    private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
+) : CoreStartable {
+    override fun start() {
+        listeners.forEach { configurationController.addCallback(it) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
deleted file mode 100644
index 7048a78..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.UserHandle;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
- */
-public class StatusBarIconHolder {
-    public static final int TYPE_ICON = 0;
-    /**
-     * TODO (b/249790733): address this once the new pipeline is in place
-     * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
-     * to inform the old view system about changes to the data set (the list of mobile icons). The
-     * design of the new pipeline should allow for removal of this icon holder type, and obsolete
-     * the need for this entire class.
-     *
-     * @deprecated This field only exists so the new status bar pipeline can interface with the
-     * view holder system.
-     */
-    @Deprecated
-    public static final int TYPE_MOBILE_NEW = 3;
-
-    /**
-     * TODO (b/238425913): address this once the new pipeline is in place
-     * This type exists so that the new wifi pipeline can be used to inform the old view system
-     * about the existence of the wifi icon. The design of the new pipeline should allow for removal
-     * of this icon holder type, and obsolete the need for this entire class.
-     *
-     * @deprecated This field only exists so the new status bar pipeline can interface with the
-     * view holder system.
-     */
-    @Deprecated
-    public static final int TYPE_WIFI_NEW = 4;
-
-    @IntDef({
-            TYPE_ICON,
-            TYPE_MOBILE_NEW,
-            TYPE_WIFI_NEW
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface IconType {}
-
-    private StatusBarIcon mIcon;
-    private @IconType int mType = TYPE_ICON;
-    private int mTag = 0;
-
-    /** Returns a human-readable string representing the given type. */
-    public static String getTypeString(@IconType int type) {
-        switch(type) {
-            case TYPE_ICON: return "ICON";
-            case TYPE_MOBILE_NEW: return "MOBILE_NEW";
-            case TYPE_WIFI_NEW: return "WIFI_NEW";
-            default: return "UNKNOWN";
-        }
-    }
-
-    private StatusBarIconHolder() {
-    }
-
-    public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
-        StatusBarIconHolder wrapper = new StatusBarIconHolder();
-        wrapper.mIcon = icon;
-
-        return wrapper;
-    }
-
-    /** Creates a new holder with for the new wifi icon. */
-    public static StatusBarIconHolder forNewWifiIcon() {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mType = TYPE_WIFI_NEW;
-        return holder;
-    }
-
-    /**
-     * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
-     * determine icon ordering and building the correct view model
-     */
-    public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mType = TYPE_MOBILE_NEW;
-        holder.mTag = subId;
-
-        return holder;
-    }
-
-    /**
-     * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
-     */
-    public static StatusBarIconHolder fromCallIndicatorState(
-            Context context,
-            CallIndicatorIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
-        String contentDescription = state.isNoCalling
-                ? state.noCallingDescription : state.callStrengthDescription;
-        holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
-                Icon.createWithResource(context, resId), 0, 0, contentDescription);
-        holder.mTag = state.subId;
-        return holder;
-    }
-
-    public @IconType int getType() {
-        return mType;
-    }
-
-    @Nullable
-    public StatusBarIcon getIcon() {
-        return mIcon;
-    }
-
-    public void setIcon(StatusBarIcon icon) {
-        mIcon = icon;
-    }
-
-    public boolean isVisible() {
-        switch (mType) {
-            case TYPE_ICON:
-                return mIcon.visible;
-            case TYPE_MOBILE_NEW:
-            case TYPE_WIFI_NEW:
-                // The new pipeline controls visibilities via the view model and view binder, so
-                // this is effectively an unused return value.
-                return true;
-            default:
-                return true;
-        }
-    }
-
-    public void setVisible(boolean visible) {
-        if (isVisible() == visible) {
-            return;
-        }
-
-        switch (mType) {
-            case TYPE_ICON:
-                mIcon.visible = visible;
-                break;
-
-            case TYPE_MOBILE_NEW:
-            case TYPE_WIFI_NEW:
-                // The new pipeline controls visibilities via the view model and view binder, so
-                // ignore setVisible.
-                break;
-        }
-    }
-
-    public int getTag() {
-        return mTag;
-    }
-
-    @Override
-    public String toString() {
-        return "StatusBarIconHolder(type=" + getTypeString(mType)
-                + " tag=" + getTag()
-                + " visible=" + isVisible() + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
new file mode 100644
index 0000000..5b55a1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.annotation.IntDef
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState
+
+/** Wraps [com.android.internal.statusbar.StatusBarIcon] so we can still have a uniform list */
+class StatusBarIconHolder private constructor() {
+    @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW)
+    @Retention(AnnotationRetention.SOURCE)
+    internal annotation class IconType
+
+    var icon: StatusBarIcon? = null
+
+    @IconType
+    var type = TYPE_ICON
+        private set
+
+    var tag = 0
+        private set
+
+    var isVisible: Boolean
+        get() =
+            when (type) {
+                TYPE_ICON -> icon!!.visible
+
+                // The new pipeline controls visibilities via the view model and
+                // view binder, so
+                // this is effectively an unused return value.
+                TYPE_MOBILE_NEW,
+                TYPE_WIFI_NEW -> true
+                else -> true
+            }
+        set(visible) {
+            if (isVisible == visible) {
+                return
+            }
+            when (type) {
+                TYPE_ICON -> icon!!.visible = visible
+                TYPE_MOBILE_NEW,
+                TYPE_WIFI_NEW -> {}
+            }
+        }
+
+    override fun toString(): String {
+        return ("StatusBarIconHolder(type=${getTypeString(type)}" +
+            " tag=$tag" +
+            " visible=$isVisible)")
+    }
+
+    companion object {
+        const val TYPE_ICON = 0
+
+        /**
+         * TODO (b/249790733): address this once the new pipeline is in place This type exists so
+         * that the new pipeline (see [MobileIconViewModel]) can be used to inform the old view
+         * system about changes to the data set (the list of mobile icons). The design of the new
+         * pipeline should allow for removal of this icon holder type, and obsolete the need for
+         * this entire class.
+         */
+        @Deprecated(
+            """This field only exists so the new status bar pipeline can interface with the
+      view holder system."""
+        )
+        const val TYPE_MOBILE_NEW = 3
+
+        /**
+         * TODO (b/238425913): address this once the new pipeline is in place This type exists so
+         * that the new wifi pipeline can be used to inform the old view system about the existence
+         * of the wifi icon. The design of the new pipeline should allow for removal of this icon
+         * holder type, and obsolete the need for this entire class.
+         */
+        @Deprecated(
+            """This field only exists so the new status bar pipeline can interface with the
+      view holder system."""
+        )
+        const val TYPE_WIFI_NEW = 4
+
+        /** Returns a human-readable string representing the given type. */
+        fun getTypeString(@IconType type: Int): String {
+            return when (type) {
+                TYPE_ICON -> "ICON"
+                TYPE_MOBILE_NEW -> "MOBILE_NEW"
+                TYPE_WIFI_NEW -> "WIFI_NEW"
+                else -> "UNKNOWN"
+            }
+        }
+
+        @JvmStatic
+        fun fromIcon(icon: StatusBarIcon?): StatusBarIconHolder {
+            val wrapper = StatusBarIconHolder()
+            wrapper.icon = icon
+            return wrapper
+        }
+
+        /** Creates a new holder with for the new wifi icon. */
+        @JvmStatic
+        fun forNewWifiIcon(): StatusBarIconHolder {
+            val holder = StatusBarIconHolder()
+            holder.type = TYPE_WIFI_NEW
+            return holder
+        }
+
+        /**
+         * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+         * determine icon ordering and building the correct view model
+         */
+        @JvmStatic
+        fun fromSubIdForModernMobileIcon(subId: Int): StatusBarIconHolder {
+            val holder = StatusBarIconHolder()
+            holder.type = TYPE_MOBILE_NEW
+            holder.tag = subId
+            return holder
+        }
+
+        /** Creates a new StatusBarIconHolder from a CallIndicatorIconState. */
+        @JvmStatic
+        fun fromCallIndicatorState(
+            context: Context,
+            state: CallIndicatorIconState,
+        ): StatusBarIconHolder {
+            val holder = StatusBarIconHolder()
+            val resId = if (state.isNoCalling) state.noCallingResId else state.callStrengthResId
+            val contentDescription =
+                if (state.isNoCalling) state.noCallingDescription else state.callStrengthDescription
+            holder.icon =
+                StatusBarIcon(
+                    UserHandle.SYSTEM,
+                    context.packageName,
+                    Icon.createWithResource(context, resId),
+                    0,
+                    0,
+                    contentDescription,
+                )
+            holder.tag = state.subId
+            return holder
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 942d186..b048da492 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -16,12 +16,16 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
+import com.android.systemui.statusbar.phone.ConfigurationControllerStartable;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 /**
  * Dagger Module providing {@link CentralSurfacesImpl}.
@@ -34,4 +38,12 @@
     @Binds
     @SysUISingleton
     CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl);
+
+    /**
+     * Starts {@link ConfigurationControllerStartable}
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(ConfigurationControllerStartable.class)
+    CoreStartable bindConfigControllerStartable(ConfigurationControllerStartable impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
new file mode 100644
index 0000000..6cd9993
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.toast
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ToastModule {
+    /** Starts ToastUI. */
+    @Binds
+    @IntoMap
+    @ClassKey(ToastUI::class)
+    fun bindToastUIStartable(service: ToastUI): CoreStartable
+
+    /** Listen to config changes for ToastUI. */
+    @Binds @IntoSet fun bindToastUIConfigChanges(service: ToastUI): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 27f8121..85a455d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -42,6 +42,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.util.Objects;
 
@@ -51,7 +52,10 @@
  * Controls display of text toasts.
  */
 @SysUISingleton
-public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
+public class ToastUI implements
+        CoreStartable,
+        ConfigurationController.ConfigurationListener,
+        CommandQueue.Callbacks {
     // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
     private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
     private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
@@ -187,7 +191,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (newConfig.orientation != mOrientation) {
             mOrientation = newConfig.orientation;
             if (mToast != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 3451ae0..dc2b80c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -22,16 +22,17 @@
 import android.util.Log;
 
 import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
 @SysUISingleton
-public class VolumeUI implements CoreStartable {
+public class VolumeUI implements CoreStartable, ConfigurationController.ConfigurationListener {
     private static final String TAG = "VolumeUI";
     private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -60,7 +61,7 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         if (!mEnabled) return;
         mVolumeComponent.onConfigurationChanged(newConfig);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 53217d4..8d06a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,6 +21,7 @@
 import android.os.Looper;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
@@ -36,15 +37,30 @@
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
+import com.android.systemui.volume.VolumeUI;
 
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
 
 /** Dagger Module for code in the volume package. */
 @Module
 public interface VolumeModule {
+    /** Starts VolumeUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(VolumeUI.class)
+    CoreStartable bindVolumeUIStartable(VolumeUI impl);
+
+    /** Listen to config changes for VolumeUI. */
+    @Binds
+    @IntoSet
+    ConfigurationController.ConfigurationListener bindVolumeUIConfigChanges(VolumeUI impl);
+
     /** */
     @Binds
     VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 639276e..b3eab8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -248,8 +248,8 @@
             }
 
             @Override
-            public void onConfigurationChanged(Configuration newConfig) {
-                super.onConfigurationChanged(newConfig);
+            public void onConfigChanged(Configuration newConfig) {
+                super.onConfigChanged(newConfig);
                 mExecutor.runAllReady();
             }
 
@@ -892,7 +892,7 @@
         // Switch to long edge cutout(left).
         mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
     }
 
@@ -913,7 +913,7 @@
         // Switch to long edge cutout(left).
         mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
         verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
         verify(mDotViewController, times(2)).setShowingListener(null);
@@ -949,7 +949,7 @@
         // top cutout
         mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
 
         // Only top windows should be added.
         verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
@@ -976,7 +976,7 @@
         // top cutout
         mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
 
         // Both top and bottom windows should be added with VISIBLE because of privacy dot and
         // cutout, but rounded corners visibility shall be gone because of no rounding.
@@ -1013,7 +1013,7 @@
         doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
         mDisplayInfo.rotation = Surface.ROTATION_270;
 
-        mScreenDecorations.onConfigurationChanged(null);
+        mScreenDecorations.onConfigChanged(null);
 
         assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
         assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
@@ -1145,7 +1145,7 @@
         assertThat(mScreenDecorations.mIsRegistered, is(false));
 
         doReturn(true).when(mScreenDecorations).hasOverlays();
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1156,7 +1156,7 @@
         mScreenDecorations.start();
         assertThat(mScreenDecorations.mIsRegistered, is(true));
 
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1168,7 +1168,7 @@
         assertThat(mScreenDecorations.mIsRegistered, is(true));
 
         doReturn(false).when(mScreenDecorations).hasOverlays();
-        mScreenDecorations.onConfigurationChanged(new Configuration());
+        mScreenDecorations.onConfigChanged(new Configuration());
         assertThat(mScreenDecorations.mIsRegistered, is(false));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e3..9bcab57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
-    private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+    private DragToInteractAnimationController mDragToInteractAnimationController;
     private DismissView mDismissView;
 
     @Rule
@@ -65,19 +65,20 @@
                 stubMenuViewAppearance);
         mDismissView = spy(new DismissView(mContext));
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+        mDragToInteractAnimationController = new DragToInteractAnimationController(
+                mDismissView, stubMenuView);
     }
 
     @Test
     public void showDismissView_success() {
-        mDismissAnimationController.showDismissView(true);
+        mDragToInteractAnimationController.showDismissView(true);
 
         verify(mDismissView).show();
     }
 
     @Test
     public void hideDismissView_success() {
-        mDismissAnimationController.showDismissView(false);
+        mDragToInteractAnimationController.showDismissView(false);
 
         verify(mDismissView).hide();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 7f12c05..9c8de30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -62,7 +62,7 @@
     @Mock
     private SecureSettings mSecureSettings;
     @Mock
-    private DismissAnimationController.DismissCallback mStubDismissCallback;
+    private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
 
     private RecyclerView mStubListView;
     private MenuView mMenuView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 9797f2a..e1522f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -68,7 +68,7 @@
     private MenuView mStubMenuView;
     private MenuListViewTouchHandler mTouchHandler;
     private MenuAnimationController mMenuAnimationController;
-    private DismissAnimationController mDismissAnimationController;
+    private DragToInteractAnimationController mDragToInteractAnimationController;
     private RecyclerView mStubListView;
     private DismissView mDismissView;
 
@@ -92,10 +92,10 @@
                 mStubMenuView, stubMenuViewAppearance));
         mDismissView = spy(new DismissView(mContext));
         DismissViewUtils.setup(mDismissView);
-        mDismissAnimationController =
-                spy(new DismissAnimationController(mDismissView, mStubMenuView));
+        mDragToInteractAnimationController =
+                spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
-                mDismissAnimationController);
+                mDragToInteractAnimationController);
         final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
         mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
         mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@
 
     @Test
     public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
-        doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+        doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
                 any(MotionEvent.class));
         final int offset = 100;
         final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index b4ae00d..42d2c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -217,6 +217,7 @@
 
         deviceEntrySideFpsOverlayInteractor =
             DeviceEntrySideFpsOverlayInteractor(
+                testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
                 primaryBouncerInteractor,
@@ -260,14 +261,14 @@
             SideFpsOverlayViewBinder(
                 testScope.backgroundScope,
                 mContext,
-                biometricStatusInteractor,
-                displayStateInteractor,
-                deviceEntrySideFpsOverlayInteractor,
-                fpsUnlockTracker,
-                layoutInflater,
-                sideFpsProgressBarViewModel,
-                sfpsSensorInteractor,
-                windowManager
+                { biometricStatusInteractor },
+                { displayStateInteractor },
+                { deviceEntrySideFpsOverlayInteractor },
+                { fpsUnlockTracker },
+                { layoutInflater },
+                { sideFpsProgressBarViewModel },
+                { sfpsSensorInteractor },
+                { windowManager }
             )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2267bdc..983e4b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -220,6 +220,7 @@
 
         deviceEntrySideFpsOverlayInteractor =
             DeviceEntrySideFpsOverlayInteractor(
+                testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
                 primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd..c22d35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@
 
             assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
         }
+
+    @Test
+    fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onStartTrackingTouch(seekBar)
+        eventProducer.onArrowUp()
+
+        assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+    }
+
+    @Test
+    fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+        val progress = 50
+        val latest by collectLastValue(eventFlow)
+
+        eventProducer.onProgressChanged(seekBar, progress, false)
+        eventProducer.onArrowUp()
+
+        assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e49..db04962 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@
         verifyNoMoreInteractions(sliderStateListener)
     }
 
+    @Test
+    fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        initTracker(testScheduler)
+
+        // GIVEN a progress due to an external source that lands at the middle of the slider
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+        verify(sliderStateListener).onSelectAndArrow(progress)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+
+        // GIVEN a progress due to an external source that lands at the upper bookend
+        val progress = config.upperBookendThreshold + 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes upper bookend haptics before moving back to IDLE
+        verify(sliderStateListener).onUpperBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the IDLE state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+
+        // WHEN a progress is recorded due to an external source that lands at the lower bookend
+        val progress = config.lowerBookendThreshold - 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes lower bookend haptics before moving to IDLE
+        verify(sliderStateListener).onLowerBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the external stimulus is released
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+        // THEN the tracker moves back to IDLE and there are no haptics
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the slider starts tracking touch
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+        // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+        assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+        // WHEN the slider gets an external progress change
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+        // haptics
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+        verify(sliderStateListener).onProgress(progress)
+    }
+
+    @Test
+    fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the external stimulus is released
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+        // THEN the tracker moves to IDLE and no haptics are played
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider starts tracking touch
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+        // THEN the tracker moves to WAIT and the wait job starts.
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+        assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+        verifyZeroInteractions(sliderStateListener)
+    }
+
+    @Test
+    fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        initTracker(testScheduler)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider changes progress programmatically at the middle
+        val progress = 0.5f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker stays in the same state and haptics are delivered appropriately
+        assertThat(mSeekableSliderTracker.currentState)
+            .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+        verify(sliderStateListener).onProgress(progress)
+    }
+
+    @Test
+    fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider reaches the lower bookend programmatically
+        val progress = config.lowerBookendThreshold - 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes lower bookend haptics before moving to IDLE
+        verify(sliderStateListener).onLowerBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
+    @Test
+    fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+        // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+        val config = SeekableSliderTrackerConfig()
+        initTracker(testScheduler, config)
+        mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+        // WHEN the slider reaches the lower bookend programmatically
+        val progress = config.upperBookendThreshold + 0.01f
+        sliderEventProducer.sendEvent(
+            SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+        )
+
+        // THEN the tracker executes upper bookend haptics before moving to IDLE
+        verify(sliderStateListener).onUpperBookend()
+        assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+    }
+
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun initTracker(
         scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 0616a34..027dfa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -109,6 +109,7 @@
             )
         underTest =
             DeviceEntrySideFpsOverlayInteractor(
+                testScope.backgroundScope,
                 mContext,
                 FakeDeviceEntryFingerprintAuthRepository(),
                 primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index c108a80..273ce85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -28,8 +28,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -82,7 +82,7 @@
         TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
                 R.id.rear_display_title_text_view);
 
-        controller.onConfigurationChanged(new Configuration());
+        controller.onConfigChanged(new Configuration());
         assertTrue(controller.mRearDisplayEducationDialog.isShowing());
         TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
                 R.id.rear_display_title_text_view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 88c728f..b94e483 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -36,8 +36,12 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.flow.timeout
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -59,7 +63,6 @@
     @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
     @Mock private lateinit var brightnessController: BrightnessController
     @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
-    @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor>
     @Mock private lateinit var shadeInteractor: ShadeInteractor
 
     private val clock = FakeSystemClock()
@@ -89,7 +92,6 @@
             .thenReturn(brightnessSliderController)
         `when`(brightnessSliderController.rootView).thenReturn(View(context))
         `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
-        whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor)
         whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
     }
 
@@ -180,6 +182,22 @@
         assertThat(activityRule.activity.isFinishing()).isFalse()
     }
 
+    @OptIn(FlowPreview::class)
+    @Test
+    fun testFinishOnQSExpanded() = runTest {
+        val isQSExpanded = MutableStateFlow(false)
+        `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded)
+        activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        isQSExpanded.value = true
+        // Observe the activity's state until is it finishing or the timeout is reached, whatever
+        // comes first. This fixes the flakiness seen when using advanceUntilIdle().
+        activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {}
+        assertThat(activityRule.activity.isFinishing()).isTrue()
+    }
+
     class TestDialog(
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
         brightnessControllerFactory: BrightnessController.Factory,
@@ -194,14 +212,14 @@
             accessibilityMgr,
             shadeInteractor
         ) {
-        private var finishing = false
+        var finishing = MutableStateFlow(false)
 
         override fun isFinishing(): Boolean {
-            return finishing
+            return finishing.value
         }
 
         override fun requestFinish() {
-            finishing = true
+            finishing.value = true
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 80f8cf1..50349be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -58,13 +58,13 @@
         testScope.runTest {
             val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
 
-            secureSettingsRepository.set(
+            secureSettingsRepository.setInt(
                 name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 value = 1,
             )
             assertThat(showNotifs).isEqualTo(true)
 
-            secureSettingsRepository.set(
+            secureSettingsRepository.setInt(
                 name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 value = 0,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 6eabf44..5e57c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -17,13 +17,17 @@
 package com.android.systemui.shared.plugins;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -40,7 +44,11 @@
 
 import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -104,6 +112,7 @@
         mPluginInstance = mPluginInstanceFactory.create(
                 mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
                 TestPlugin.class, mPluginListener);
+        mPluginInstance.setIsDebug(true);
         mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
     }
 
@@ -158,7 +167,7 @@
 
     @Test
     public void testOnAttach_SkipLoad() {
-        mPluginListener.mAttachReturn = false;
+        mPluginListener.mOnAttach = () -> false;
         mPluginInstance.onCreate();
         assertEquals(1, mPluginListener.mAttachedCount);
         assertEquals(0, mPluginListener.mLoadCount);
@@ -166,6 +175,65 @@
         assertInstances(0, 0);
     }
 
+    @Test
+    public void testLoadUnloadSimultaneous_HoldsUnload() throws Exception {
+        final Semaphore loadLock = new Semaphore(1);
+        final Semaphore unloadLock = new Semaphore(1);
+
+        mPluginListener.mOnAttach = () -> false;
+        mPluginListener.mOnLoad = () -> {
+            assertNotNull(mPluginInstance.getPlugin());
+
+            // Allow the bg thread the opportunity to delete the plugin
+            loadLock.release();
+            Thread.yield();
+            boolean isLocked = getLock(unloadLock, 1000);
+
+            // Ensure the bg thread failed to do delete the plugin
+            assertNotNull(mPluginInstance.getPlugin());
+            // We expect that bgThread deadlocked holding the semaphore
+            assertFalse(isLocked);
+        };
+
+        AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
+        Thread bgThread = new Thread(() -> {
+            assertTrue(getLock(unloadLock, 10));
+            assertTrue(getLock(loadLock, 3000)); // Wait for the foreground thread
+            assertNotNull(mPluginInstance.getPlugin());
+            // Attempt to delete the plugin, this should block until the load completes
+            mPluginInstance.unloadPlugin();
+            assertNull(mPluginInstance.getPlugin());
+            unloadLock.release();
+            loadLock.release();
+        });
+
+        // This protects the test suite from crashing due to the uncaught exception.
+        bgThread.setUncaughtExceptionHandler((Thread t, Throwable ex) -> {
+            Log.e("testLoadUnloadSimultaneous_HoldsUnload", "Exception from BG Thread", ex);
+            isBgThreadFailed.set(true);
+        });
+
+        loadLock.acquire();
+        mPluginInstance.onCreate();
+
+        assertNull(mPluginInstance.getPlugin());
+        bgThread.start();
+        mPluginInstance.loadPlugin();
+
+        bgThread.join(5000);
+        assertFalse(isBgThreadFailed.get());
+        assertNull(mPluginInstance.getPlugin());
+    }
+
+    private boolean getLock(Semaphore lock, long millis) {
+        try {
+            return lock.tryAcquire(millis, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ex) {
+            fail();
+            return false;
+        }
+    }
+
     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
     // the mock version info is called.
     @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -226,7 +294,10 @@
     }
 
     public class FakeListener implements PluginListener<TestPlugin> {
-        public boolean mAttachReturn = true;
+        public Supplier<Boolean> mOnAttach = null;
+        public Runnable mOnDetach = null;
+        public Runnable mOnLoad = null;
+        public Runnable mOnUnload = null;
         public int mAttachedCount = 0;
         public int mDetachedCount = 0;
         public int mLoadCount = 0;
@@ -236,13 +307,16 @@
         public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
             mAttachedCount++;
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
-            return mAttachReturn;
+            return mOnAttach != null ? mOnAttach.get() : true;
         }
 
         @Override
         public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
             mDetachedCount++;
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnDetach != null) {
+                mOnDetach.run();
+            }
         }
 
         @Override
@@ -261,6 +335,9 @@
                 assertEquals(expectedContext, pluginContext);
             }
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnLoad != null) {
+                mOnLoad.run();
+            }
         }
 
         @Override
@@ -274,6 +351,9 @@
                 assertEquals(expectedPlugin, plugin);
             }
             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+            if (mOnUnload != null) {
+                mOnUnload.run();
+            }
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7558974..1236fcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -38,6 +38,7 @@
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.metrics.LogMaker;
+import android.platform.test.annotations.DisableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -84,6 +85,7 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,6 +220,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
         initController(/* viewIsAttached= */ true);
@@ -238,6 +241,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -258,6 +262,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -285,6 +290,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -306,6 +312,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -327,6 +334,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -348,6 +356,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
@@ -504,6 +513,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
         initController(/* viewIsAttached= */ true);
         mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
@@ -545,6 +555,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
         // GIVEN: Controller is attached, active notifications is empty,
         // and mNotificationStackScrollLayout.onKeyguard() is true
@@ -561,6 +572,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is not empty,
         // and mNotificationStackScrollLayout.onKeyguard() is true
@@ -584,6 +596,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is not empty,
         // and mNotificationStackScrollLayout.onKeyguard() is false
@@ -607,6 +620,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
         // GIVEN: Controller is attached, active notifications is empty,
         // and mNotificationStackScrollLayout.onKeyguard() is false
@@ -623,6 +637,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
         initController(/* viewIsAttached= */ true);
         mController.onKeyguardTransitionChanged(
@@ -633,6 +648,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
         initController(/* viewIsAttached= */ true);
         mController.onKeyguardTransitionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ad7dee3..83ba684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -51,6 +51,8 @@
 
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
@@ -81,6 +83,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -191,7 +194,7 @@
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setResetUserExpandedStatesRunnable(()->{});
+        mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -309,7 +312,9 @@
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ true,
+                /* hasFilteredOutSeenNotifications = */ false);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -319,7 +324,9 @@
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ false,
+                /* hasFilteredOutSeenNotifications = */ false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -328,10 +335,14 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true, false);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ false,
+                /* hasFilteredOutSeenNotifications = */ false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        mStackScroller.updateEmptyShadeView(true, true);
+        mStackScroller.updateEmptyShadeView(/* visible = */ true,
+                /* areNotificationsHiddenInShade = */ true,
+                /* hasFilteredOutSeenNotifications = */ false);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
@@ -385,8 +396,8 @@
         mStackScroller.setExpandedHeight(100f);
     }
 
-
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void manageNotifications_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -399,6 +410,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void clearAll_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -411,6 +423,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testInflateFooterView() {
         mStackScroller.inflateFooterView();
         ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -444,7 +457,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
     }
 
     @Test
@@ -459,7 +472,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, false, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
     }
 
     @Test
@@ -474,7 +487,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
     }
 
     @Test
@@ -490,7 +503,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, true, false);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
     }
 
     @Test
@@ -505,7 +518,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(false, true, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
     }
 
     @Test
@@ -521,7 +534,7 @@
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
-        verify(mStackScroller).updateFooterView(true, false, true);
+        verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
     }
 
     @Test
@@ -529,7 +542,8 @@
         mStackScroller.setCurrentUserSetup(true);
 
         // add footer
-        mStackScroller.inflateFooterView();
+        FooterView view = mock(FooterView.class);
+        mStackScroller.setFooterView(view);
 
         // add notification
         ExpandableNotificationRow row = createClearableRow();
@@ -545,6 +559,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testReInflatesFooterViews() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
@@ -554,6 +569,16 @@
     }
 
     @Test
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
+    public void testReInflatesEmptyShadeView() {
+        when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
+        clearInvocations(mStackScroller);
+        mStackScroller.reinflateViews();
+        verify(mStackScroller, never()).setFooterView(any());
+        verify(mStackScroller).setEmptyShadeView(any());
+    }
+
+    @Test
     public void testSetIsBeingDraggedResetsExposedMenu() {
         mStackScroller.setIsBeingDragged(true);
         verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
@@ -601,6 +626,8 @@
 
     @Test
     public void testClearNotifications_clearAllInProgress() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         ExpandableNotificationRow row = createClearableRow();
         when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
         doReturn(true).when(mStackScroller).isVisible(row);
@@ -645,6 +672,8 @@
 
     @Test
     public void testAddNotificationUpdatesSpeedBumpIndex() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -661,6 +690,8 @@
 
     @Test
     public void testAddAmbientNotificationNoSpeedBumpUpdate() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated  == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -677,6 +708,8 @@
 
     @Test
     public void testRemoveNotificationUpdatesSpeedBump() {
+        mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
         // initial state calculated == 0
         assertEquals(0, mStackScroller.getSpeedBumpIndex());
 
@@ -872,6 +905,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void hasFilteredOutSeenNotifs_updateFooter() {
         mStackScroller.setCurrentUserSetup(true);
 
@@ -887,6 +921,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
         mStackScroller.setHasFilteredOutSeenNotifications(true);
         mStackScroller.updateEmptyShadeView(true, false);
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce..e52cefb 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,4 +39,7 @@
     sdk_version: "current",
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 7744fca..491ed22 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -55,6 +55,7 @@
 android.os.Parcel
 android.os.Parcelable
 android.os.Process
+android.os.ServiceSpecificException
 android.os.SystemClock
 android.os.ThreadLocalWorkSource
 android.os.TimestampedValue
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index acb5911..d08a97e 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -19,7 +19,13 @@
     defaults: ["platform_service_defaults"],
     srcs: [":services.backup-sources"],
     libs: ["services.core"],
-    static_libs: ["app-compat-annotations", "backup_flags_lib"],
+    static_libs: [
+        "app-compat-annotations",
+        "backup_flags_lib",
+    ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 aconfig_declarations {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 550e17b..2bfdd0a 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -31,4 +31,7 @@
         "virtualdevice_flags_lib",
         "virtual_camera_service_aidl-java",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5111b08..dd001ec 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -212,6 +212,9 @@
         "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
         "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_genrule {
@@ -230,6 +233,9 @@
 java_library {
     name: "services.core",
     static_libs: ["services.core.priorityboosted"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library_host {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efb..08093c0 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@
      */
     @NonNull
     public abstract PackageArchiver getPackageArchiver();
+
+    /**
+     * Returns true if the device is upgrading from an SDK version lower than the one specified.
+     */
+    public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d461643..96b1650 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface FgsStopReason {}
 
+    /**
+     * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+     * except:
+     * <ul>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+     *     <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+     * </ul>
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+    @Overridable
+    public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
     final ActivityManagerService mAm;
 
     // Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@
         }
     }
 
+    private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+        @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+                r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+                                                      : REASON_DENIED;
+        if (Flags.fgsBootCompleted()
+                && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+                && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+            // Filter through types
+            return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+        }
+        // Not BOOT_COMPLETED
+        return true;
+    }
+
     private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
             int callingUid, int callingPid, String callingProcessName,
             int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@
                 // anyway, so we just remove the SHORT_SERVICE type.
                 foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
             }
+            if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+                throw new ForegroundServiceStartNotAllowedException("FGS type "
+                        + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+                        + " not allowed to start from BOOT_COMPLETED!");
+            }
 
             boolean alreadyStartedOp = false;
             boolean stopProcStatsOp = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1d69905..72e62c3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
 
 package com.android.server.am;
 
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
 
@@ -73,6 +79,9 @@
             = "fgservice_screen_on_before_time";
     private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
             = "fgservice_screen_on_after_time";
+
+    private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
     private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
     private static final String KEY_GC_TIMEOUT = "gc_timeout";
     private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@
     private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
     private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
     private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+    private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+            FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+                | FOREGROUND_SERVICE_TYPE_HEALTH
+                | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+                | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+                | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+                | FOREGROUND_SERVICE_TYPE_LOCATION;
+
     private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
     private static final long DEFAULT_GC_TIMEOUT = 5*1000;
     private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -446,6 +464,9 @@
     // on until we will stop reporting it.
     public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
 
+    // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+    public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
     // How long we will retain processes hosting content providers in the "last activity"
     // state before allowing them to drop down to the regular cached LRU list.  This is
     // to avoid thrashing of provider processes under low memory situations.
@@ -629,6 +650,10 @@
     // foreground service background start restriction.
     volatile boolean mFgsStartRestrictionNotificationEnabled = false;
 
+    // Indicates whether PSS profiling in AppProfiler is force-enabled, even if RSS is used by
+    // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
+    volatile boolean mForceEnablePssProfiling = false;
+
     /**
      * Indicates whether the foreground service background start restriction is enabled for
      * caller app that is targeting S+.
@@ -958,6 +983,9 @@
     private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI =
             Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS);
 
+    private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
+            Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
+
     /**
      * The threshold to decide if a given association should be dumped into metrics.
      */
@@ -1368,6 +1396,7 @@
             mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI,
                     false, this);
         }
+        mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
         updateConstants();
         if (mSystemServerAutomaticHeapDumpEnabled) {
             updateEnableAutomaticSystemServerHeapDumps();
@@ -1383,6 +1412,7 @@
         // The following read from Settings.
         updateActivityStartsLoggingEnabled();
         updateForegroundServiceStartsLoggingEnabled();
+        updateForceEnablePssProfiling();
         // Read DropboxRateLimiter params from flags.
         mService.initDropboxRateLimiter();
     }
@@ -1424,6 +1454,8 @@
             updateForegroundServiceStartsLoggingEnabled();
         } else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) {
             updateEnableAutomaticSystemServerHeapDumps();
+        } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
+            updateForceEnablePssProfiling();
         }
     }
 
@@ -1450,6 +1482,8 @@
                     DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
             FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
                     DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+            FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+                    DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
             CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
                     DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
             GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -1536,6 +1570,11 @@
                 Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, 1) == 1;
     }
 
+    private void updateForceEnablePssProfiling() {
+        mForceEnablePssProfiling = Settings.Global.getInt(mResolver,
+                Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
+    }
+
     private void updateBackgroundActivityStarts() {
         mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2091,6 +2130,8 @@
         pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
         pw.print("  "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
         pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+        pw.print("  "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+        pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
         pw.print("  "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
         pw.println(CONTENT_PROVIDER_RETAIN_TIME);
         pw.print("  "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f8451fd..671c8e9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -327,7 +327,6 @@
 import android.os.DropBoxManager;
 import android.os.FactoryTest;
 import android.os.FileUtils;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdentifiersPolicyService;
@@ -8608,7 +8607,7 @@
                         final long initialIdlePssOrRss, lastPssOrRss, lastSwapPss;
                         synchronized (mAppProfiler.mProfilerLock) {
                             initialIdlePssOrRss = pr.getInitialIdlePssOrRss();
-                            lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+                            lastPssOrRss = mAppProfiler.isProfilingPss()
                                     ? pr.getLastPss() : pr.getLastRss();
                             lastSwapPss = pr.getLastSwapPss();
                         }
@@ -8618,14 +8617,14 @@
                             final StringBuilder sb2 = new StringBuilder(128);
                             sb2.append("Kill");
                             sb2.append(proc.processName);
-                            if (!Flags.removeAppProfilerPssCollection()) {
+                            if (mAppProfiler.isProfilingPss()) {
                                 sb2.append(" in idle maint: pss=");
                             } else {
                                 sb2.append(" in idle maint: rss=");
                             }
                             sb2.append(lastPssOrRss);
 
-                            if (!Flags.removeAppProfilerPssCollection()) {
+                            if (mAppProfiler.isProfilingPss()) {
                                 sb2.append(", swapPss=");
                                 sb2.append(lastSwapPss);
                                 sb2.append(", initialPss=");
@@ -8640,7 +8639,7 @@
                             Slog.wtfQuiet(TAG, sb2.toString());
                             mHandler.post(() -> {
                                 synchronized (ActivityManagerService.this) {
-                                    proc.killLocked(!Flags.removeAppProfilerPssCollection()
+                                    proc.killLocked(mAppProfiler.isProfilingPss()
                                             ? "idle maint (pss " : "idle maint (rss " + lastPssOrRss
                                             + " from " + initialIdlePssOrRss + ")",
                                             ApplicationExitInfo.REASON_OTHER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 00dd169..848a2b0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@
 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
 import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.Process.INVALID_UID;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@
                 } else if (opt.equals("--dismiss-keyguard-if-insecure")
                       || opt.equals("--dismiss-keyguard")) {
                     mDismissKeyguardIfInsecure = true;
+                } else if (opt.equals("--allow-fgs-start-reason")) {
+                    final int reasonCode = Integer.parseInt(getNextArgRequired());
+                    mBroadcastOptions = BroadcastOptions.makeBasic();
+                    mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+                            TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                            reasonCode,
+                            "");
                 } else {
                     return false;
                 }
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 2e0aec9..e4956b3 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -602,7 +602,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case COLLECT_PSS_BG_MSG:
-                    if (!Flags.removeAppProfilerPssCollection()) {
+                    if (isProfilingPss()) {
                         collectPssInBackground();
                     } else {
                         collectRssInBackground();
@@ -748,6 +748,11 @@
         } while (true);
     }
 
+    boolean isProfilingPss() {
+        return !Flags.removeAppProfilerPssCollection()
+                || mService.mConstants.mForceEnablePssProfiling;
+    }
+
     // This method is analogous to collectPssInBackground() and is intended to be used as a
     // replacement if Flags.removeAppProfilerPssCollection() is enabled. References to PSS in
     // methods outside of AppProfiler have generally been kept where a new RSS equivalent is not
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b507a60..f49e25a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -143,7 +143,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ServiceInfo;
 import android.net.NetworkPolicyManager;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
@@ -2418,7 +2417,7 @@
                     // normally be a B service, but if we are low on RAM and it
                     // is large we want to force it down since we would prefer to
                     // keep launcher over it.
-                    long lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+                    long lastPssOrRss = mService.mAppProfiler.isProfilingPss()
                             ? app.mProfile.getLastPss() : app.mProfile.getLastRss();
 
                     // RSS is larger than PSS, but the RSS/PSS ratio varies per-process based on how
@@ -2427,9 +2426,8 @@
                     //
                     // TODO(b/296454553): Tune the second value so that the relative number of
                     // service B is similar before/after this flag is enabled.
-                    double thresholdModifier = !Flags.removeAppProfilerPssCollection()
-                            ? 1
-                            : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
+                    double thresholdModifier = mService.mAppProfiler.isProfilingPss()
+                            ? 1 : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
                     double cachedRestoreThreshold =
                             mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e57206e..b03183c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -94,7 +94,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.DropBoxManager;
-import android.os.Flags;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -2482,7 +2481,6 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         app.getDisabledCompatChanges(),
-                        bindOverrideSysprops,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else if (hostingRecord.usesAppZygote()) {
                 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -4733,7 +4731,7 @@
                 pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
                 pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
                 // These values won't be collected if the flag is enabled.
-                if (!Flags.removeAppProfilerPssCollection()) {
+                if (service.mAppProfiler.isProfilingPss()) {
                     pw.print(" lastPss=");
                     DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
                     pw.print(" lastSwapPss=");
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 8ca64f8..d8f797c 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,7 +23,6 @@
 import android.app.ProcessMemoryState.HostingComponentType;
 import android.content.pm.ApplicationInfo;
 import android.os.Debug;
-import android.os.Flags;
 import android.os.Process;
 import android.os.SystemClock;
 import android.util.DebugUtils;
@@ -677,7 +676,7 @@
     void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
         synchronized (mProfilerLock) {
             // TODO(b/297542292): Remove this case once PSS profiling is replaced
-            if (!Flags.removeAppProfilerPssCollection()) {
+            if (mService.mAppProfiler.isProfilingPss()) {
                 pw.print(prefix);
                 pw.print("lastPssTime=");
                 TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 5ad921f..3391ec7 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -29,7 +29,6 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.app.ActivityManager;
 import android.content.ComponentName;
-import android.os.Flags;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Slog;
@@ -1351,7 +1350,7 @@
         }
         if (mNotCachedSinceIdle) {
             pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
-            if (!Flags.removeAppProfilerPssCollection()) {
+            if (mService.mAppProfiler.isProfilingPss()) {
                 pw.print(" initialIdlePss=");
             } else {
                 pw.print(" initialIdleRss=");
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index cc56110..d0d647c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -179,6 +179,7 @@
         "text",
         "threadnetwork",
         "tv_system_ui",
+        "usb",
         "vibrator",
         "virtual_devices",
         "wallet_integration",
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 2cceb5a..cbcd8f5 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -346,6 +346,7 @@
             if (apc.getPlayerProxy() != null) {
                 applyVolumeShaperInternal(apc, piid, volShaper,
                         skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+                mFadedPlayers.put(piid, volShaper);
             } else {
                 if (DEBUG) {
                     Slog.v(TAG, "Error fading out player piid:" + piid
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6af223b..0f964bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -78,6 +78,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -99,6 +100,9 @@
             mBiometricStateCallback;
     @NonNull
     private final FaceProviderFunction mFaceProviderFunction;
+    @NonNull private final Function<String, FaceProvider> mFaceProvider;
+    @NonNull
+    private final Supplier<String[]> mAidlInstanceNameSupplier;
 
     interface FaceProviderFunction {
         FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
@@ -671,23 +675,9 @@
             final List<ServiceProvider> providers = new ArrayList<>();
 
             for (String instance : instances) {
-                final String fqName = IFace.DESCRIPTOR + "/" + instance;
-                final IFace face = IFace.Stub.asInterface(
-                        Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
-                if (face == null) {
-                    Slog.e(TAG, "Unable to get declared service: " + fqName);
-                    continue;
-                }
-                try {
-                    final SensorProps[] props = face.getSensorProps();
-                    final FaceProvider provider = new FaceProvider(getContext(),
-                            mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
-                            BiometricContext.getInstance(getContext()),
-                            false /* resetLockoutRequiresChallenge */);
-                    providers.add(provider);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
-                }
+                final FaceProvider provider = mFaceProvider.apply(instance);
+                Slog.i(TAG, "Adding AIDL provider: " + instance);
+                providers.add(provider);
             }
 
             return providers;
@@ -700,7 +690,7 @@
 
             mRegistry.registerAll(() -> {
                 List<String> aidlSensors = new ArrayList<>();
-                final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+                final String[] instances = mAidlInstanceNameSupplier.get();
                 if (instances != null) {
                     aidlSensors.addAll(Lists.newArrayList(instances));
                 }
@@ -813,11 +803,15 @@
 
     public FaceService(Context context) {
         this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
-                ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
+                () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
     }
 
-    @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
-            Supplier<IBiometricService> biometricServiceSupplier) {
+    @VisibleForTesting FaceService(Context context,
+            FaceProviderFunction faceProviderFunction,
+            Supplier<IBiometricService> biometricServiceSupplier,
+            Function<String, FaceProvider> faceProvider,
+            Supplier<String[]> aidlInstanceNameSupplier) {
         super(context);
         mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
@@ -830,6 +824,28 @@
                 mBiometricStateCallback.start(mRegistry.getProviders());
             }
         });
+        mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
+
+        mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
+            final String fqName = IFace.DESCRIPTOR + "/" + name;
+            final IFace face = IFace.Stub.asInterface(
+                    Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+            if (face == null) {
+                Slog.e(TAG, "Unable to get declared service: " + fqName);
+                return null;
+            }
+            try {
+                final SensorProps[] props = face.getSensorProps();
+                return new FaceProvider(getContext(),
+                        mBiometricStateCallback, props, name, mLockoutResetDispatcher,
+                        BiometricContext.getInstance(getContext()),
+                        false /* resetLockoutRequiresChallenge */);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+            }
+
+            return null;
+        };
 
         if (Flags.deHidl()) {
             mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3024dd2..8910b6e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,6 +82,7 @@
     public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
     public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
     public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+    public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
 
     // How long the current sensor reading is assumed to be valid beyond the current time.
     // This provides a bit of prediction, as well as ensures that the weight for the last sample is
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08f..bc3f9dd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@
                     if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
                         final DisplayPowerControllerInterface displayPowerController =
                                 mDisplayPowerControllers.get(id);
-                        ready &= displayPowerController.requestPowerState(request,
-                                waitForNegativeProximity);
+                        if (displayPowerController != null) {
+                            ready &= displayPowerController.requestPowerState(request,
+                                    waitForNegativeProximity);
+                        }
                     }
                 }
 
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9..a43f93a 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
@@ -65,6 +67,22 @@
         return true;
     }
 
+    @Override
+    public float[] getAutoBrightnessLevels(int mode) {
+        if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+            throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+        return mDisplayPowerController.getAutoBrightnessLevels(mode);
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(int mode) {
+        if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+            throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+        return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+    }
+
     /**
      * Start the offload session. The method returns if the session is already active.
      * @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 06e5f99..734381b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2214,6 +2214,20 @@
     }
 
     @Override
+    public float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        // The old DPC is no longer supported
+        return null;
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        // The old DPC is no longer supported
+        return null;
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 519224a..7df6114 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1886,6 +1886,24 @@
     }
 
     @Override
+    public float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+    }
+
+    @Override
+    public float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c279184..13acb3f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@
      * Indicate that boot has been completed and the screen is ready to update.
      */
     void onBootCompleted();
+
+    /**
+     * Get the brightness levels used to determine automatic brightness based on lux levels.
+     * @param mode The auto-brightness mode
+     * @return The brightness levels for the specified mode. The values are between
+     * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+     */
+    float[] getAutoBrightnessLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+    /**
+     * Get the lux levels used to determine automatic brightness.
+     * @param mode The auto-brightness mode
+     * @return The lux levels for the specified mode
+     */
+    float[] getAutoBrightnessLuxLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 200d88a..01a8d360a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -104,8 +104,7 @@
     public void resetHdrConfig(HdrBrightnessData data, int width, int height,
             float minimumHdrPercentOfScreen, IBinder displayToken) {
         mHdrBrightnessData = data;
-        mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
-                : (float) (width * height) * minimumHdrPercentOfScreen;
+        mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
         if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
             if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
                 mHdrListener.unregister(mRegisteredDisplayToken);
@@ -115,7 +114,7 @@
             // new token not null and hdr min % of the screen is set, subscribe.
             // e.g. for virtual display, HBM data will be missing and HdrListener
             // should not be registered
-            if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
+            if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
                 mHdrListener.register(displayToken);
                 mRegisteredDisplayToken = displayToken;
             }
@@ -140,8 +139,11 @@
         pw.println("  mDesiredMaxBrightness=" + mDesiredMaxBrightness);
         pw.println("  mTransitionRate=" + mTransitionRate);
         pw.println("  mDesiredTransitionRate=" + mDesiredTransitionRate);
+        pw.println("  mHdrVisible=" + mHdrVisible);
+        pw.println("  mHdrListener.mHdrMinPixels=" + mHdrListener.mHdrMinPixels);
         pw.println("  mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null"
                 : mHdrBrightnessData.toString()));
+        pw.println("  mHdrListener registered=" + (mRegisteredDisplayToken != null));
         pw.println("  mAmbientLux=" + mAmbientLux);
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 24e23003..087c525 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2523,9 +2523,9 @@
     // Native callback.
     @SuppressWarnings("unused")
     private int interceptMotionBeforeQueueingNonInteractive(int displayId,
-            long whenNanos, int policyFlags) {
+            int source, int action, long whenNanos, int policyFlags) {
         return mWindowManagerCallbacks.interceptMotionBeforeQueueingNonInteractive(
-                displayId, whenNanos, policyFlags);
+                displayId, source, action, whenNanos, policyFlags);
     }
 
     // Native callback.
@@ -2901,8 +2901,8 @@
          * processing when the device is in a non-interactive state since these events are normally
          * dropped.
          */
-        int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-                int policyFlags);
+        int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+                long whenNanos, int policyFlags);
 
         /**
          * This callback is invoked just before the key is about to be sent to an application.
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5..9c4225d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
 import android.location.flags.Flags;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@
 
         location.setExtras(mLocationExtras.getBundle());
 
-        reportLocation(LocationResult.wrap(location).validate());
+        try {
+            reportLocation(LocationResult.wrap(location).validate());
+        } catch (BadLocationException e) {
+            throw new IllegalArgumentException(e);
+        }
 
         if (mStarted) {
             mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@
                 }
             }
 
-            reportLocation(LocationResult.wrap(locations).validate());
+            try {
+                reportLocation(LocationResult.wrap(locations).validate());
+            } catch (BadLocationException e) {
+                throw new IllegalArgumentException(e);
+            }
         }
 
         Runnable[] listeners;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 91e6a80..7d44aec 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
 import android.location.altitude.AltitudeConverter;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@
                                         < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
                                     if (D) {
                                         Log.v(TAG, mName + " provider registration " + getIdentity()
-                                                + " dropped delivery - too fast");
+                                                + " dropped delivery - too fast (deltaMs="
+                                                + deltaMs + ").");
                                     }
                                     return false;
                                 }
@@ -2574,29 +2576,17 @@
     @GuardedBy("mMultiplexerLock")
     @Nullable
     private LocationResult processReportedLocation(LocationResult locationResult) {
-        LocationResult processed = locationResult.filter(location -> {
-            if (!location.isMock()) {
-                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
-                    Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
-                    return false;
-                }
-            }
-
-            if (!location.isComplete()) {
-                Log.e(TAG, "blocking incomplete location from " + mName + " provider");
-                return false;
-            }
-
-            return true;
-        });
-        if (processed == null) {
+        try {
+            locationResult.validate();
+        } catch (BadLocationException e) {
+            Log.e(TAG, "Dropping invalid locations: " + e);
             return null;
         }
 
         // Attempt to add a missing MSL altitude on behalf of the provider.
         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
                 "enable_location_provider_manager_msl", true)) {
-            return processed.map(location -> {
+            return locationResult.map(location -> {
                 if (!location.hasMslAltitude() && location.hasAltitude()) {
                     try {
                         Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@
                 return location;
             });
         }
-        return processed;
+        return locationResult;
     }
 
     @GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4..4efacd7 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@
         Location location = new Location(l);
         location.setIsFromMockProvider(true);
         mLocation = location;
-        reportLocation(LocationResult.wrap(location).validate());
+        try {
+            reportLocation(LocationResult.wrap(location).validate());
+        } catch (BadLocationException e) {
+            throw new IllegalArgumentException(e);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da..a597edd 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@
                     return;
                 }
 
-                reportLocation(LocationResult.wrap(location).validate());
+                reportLocation(LocationResult.wrap(location));
             }
         }
 
@@ -316,8 +316,7 @@
                 if (mProxy != this) {
                     return;
                 }
-
-                reportLocation(LocationResult.wrap(locations).validate());
+                reportLocation(LocationResult.wrap(locations));
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 38f0df4..9088cb9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2700,11 +2700,8 @@
                 // session info from them.
                 sessionInfo = mSystemProvider.getDefaultSessionInfo();
             }
-            // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
-            notifySessionCreatedToRouter(
-                    matchingRequest.mRouterRecord,
-                    toOriginalRequestId(uniqueRequestId),
-                    sessionInfo);
+            matchingRequest.mRouterRecord.notifySessionCreated(
+                    toOriginalRequestId(uniqueRequestId), sessionInfo);
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2812,11 +2809,6 @@
             return true;
         }
 
-        private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
-                int requestId, @NonNull RoutingSessionInfo sessionInfo) {
-            routerRecord.notifySessionCreated(requestId, sessionInfo);
-        }
-
         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
                 int requestId) {
             try {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c20..07b333a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.app.ForegroundServiceDelegationOptions;
 import android.media.MediaController2;
 import android.media.Session2CommandGroup;
 import android.media.Session2Token;
@@ -89,6 +90,12 @@
     }
 
     @Override
+    public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+        // TODO: Implement when MediaSession2 knows about its owner pid.
+        return null;
+    }
+
+    @Override
     public boolean isSystemPriority() {
         // System priority session is currently only allowed for telephony, so it's OK to stick to
         // the media1 API at this moment.
@@ -217,7 +224,8 @@
             synchronized (mLock) {
                 service = mService;
             }
-            service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+            service.onSessionPlaybackStateChanged(
+                    MediaSession2Record.this, playbackActive, /* playbackState= */ null);
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca..cce66e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@
     private final Context mContext;
     private final boolean mVolumeAdjustmentForRemoteGroupSessions;
 
+    private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
     private final Object mLock = new Object();
     private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
             mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@
         mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
 
+        mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
         // May throw RemoteException if the session app is killed.
         mSessionCb.mCb.asBinder().linkToDeath(this, 0);
     }
 
+    private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+        return new ForegroundServiceDelegationOptions.Builder()
+                .setClientPid(mOwnerPid)
+                .setClientUid(getUid())
+                .setClientPackageName(getPackageName())
+                .setClientAppThread(null)
+                .setSticky(false)
+                .setClientInstanceName(
+                        "MediaSessionFgsDelegate_"
+                                + getUid()
+                                + "_"
+                                + mOwnerPid
+                                + "_"
+                                + getPackageName())
+                .setForegroundServiceTypes(0)
+                .setDelegationService(
+                        ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+                .build();
+    }
+
     /**
      * Get the session binder for the {@link MediaSession}.
      *
@@ -681,6 +706,11 @@
         return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
     }
 
+    @Override
+    public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+        return mForegroundServiceDelegationOptions;
+    }
+
     private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
             final String callingOpPackageName, final int callingPid, final int callingUid,
             final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 mService.onSessionPlaybackStateChanged(
-                        MediaSessionRecord.this, shouldUpdatePriority);
+                        MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02..99c8ea9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
 
 package com.android.server.media;
 
+import android.app.ForegroundServiceDelegationOptions;
 import android.media.AudioManager;
+import android.media.session.PlaybackState;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 
@@ -51,6 +53,15 @@
     int getUserId();
 
     /**
+     * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+     * service with changes in the {@link PlaybackState} for this session.
+     *
+     * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+     *     manager service with changes in the {@link PlaybackState} for this session.
+     */
+    ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+    /**
      * Check if this session has system priority and should receive media buttons before any other
      * sessions.
      *
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c59511..db39b5e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -59,6 +61,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -144,6 +147,7 @@
     private AudioManager mAudioManager;
     private boolean mHasFeatureLeanback;
     private ActivityManagerLocal mActivityManagerLocal;
+    private ActivityManagerInternal mActivityManagerInternal;
 
     // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
     // It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@
         mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
 
         mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
     }
 
     @Override
@@ -285,7 +290,8 @@
                 }
                 user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-
+            setForegroundServiceAllowance(
+                    record, /* allowRunningInForeground= */ record.isActive());
             mHandler.postSessionsChanged(record);
         }
     }
@@ -371,8 +377,10 @@
         }
     }
 
-    void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
-            boolean shouldUpdatePriority) {
+    void onSessionPlaybackStateChanged(
+            MediaSessionRecordImpl record,
+            boolean shouldUpdatePriority,
+            @Nullable PlaybackState playbackState) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +388,10 @@
                 return;
             }
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+            if (playbackState != null) {
+                setForegroundServiceAllowance(
+                        record, playbackState.shouldAllowServiceToRunInForeground());
+            }
         }
     }
 
@@ -543,9 +555,30 @@
         }
 
         session.close();
+        setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
         mHandler.postSessionsChanged(session);
     }
 
+    private void setForegroundServiceAllowance(
+            MediaSessionRecordImpl record, boolean allowRunningInForeground) {
+        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
+        ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+                record.getForegroundServiceDelegationOptions();
+        if (foregroundServiceDelegationOptions == null) {
+            // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+            return;
+        }
+        if (allowRunningInForeground) {
+            mActivityManagerInternal.startForegroundServiceDelegate(
+                    foregroundServiceDelegationOptions, /* connection= */ null);
+        } else {
+            mActivityManagerInternal.stopForegroundServiceDelegate(
+                    foregroundServiceDelegationOptions);
+        }
+    }
+
     void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
             int callingPid, int callingUid, String callingPackage, String reason) {
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46e7041..e4e48bd 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4090,6 +4090,7 @@
                 }
                 fout.decreaseIndent();
 
+                fout.println();
                 fout.println("Admin restricted uids for metered data:");
                 fout.increaseIndent();
                 size = mMeteredRestrictedUids.size();
@@ -4099,6 +4100,7 @@
                 }
                 fout.decreaseIndent();
 
+                fout.println();
                 fout.println("Network to interfaces:");
                 fout.increaseIndent();
                 for (int i = 0; i < mNetworkToIfaces.size(); ++i) {
@@ -4108,6 +4110,10 @@
                 fout.decreaseIndent();
 
                 fout.println();
+                fout.print("Active notifications: ");
+                fout.println(mActiveNotifs);
+
+                fout.println();
                 mStatLogger.dump(fout);
 
                 mLogger.dumpLogs(fout);
@@ -6672,7 +6678,7 @@
          * Build unique tag that identifies an active {@link NetworkPolicy}
          * notification of a specific type, like {@link #TYPE_LIMIT}.
          */
-        private String buildNotificationTag(NetworkPolicy policy, int type) {
+        private static String buildNotificationTag(NetworkPolicy policy, int type) {
             return TAG + ":" + policy.template.hashCode() + ":" + type;
         }
 
@@ -6683,5 +6689,10 @@
         public int getId() {
             return mId;
         }
+
+        @Override
+        public String toString() {
+            return mTag;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 13bf336c..ff415c1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5472,20 +5472,13 @@
         }
 
         @Override
-        public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
+        public void setAutomaticZenRuleState(String id, Condition condition) {
             Objects.requireNonNull(id, "id is null");
             Objects.requireNonNull(condition, "Condition is null");
             condition.validate();
 
             enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
-
-            if (android.app.Flags.modesApi()) {
-                if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
-                    throw new IllegalArgumentException(String.format(
-                            "Mismatch between fromUser (%s) and condition.source (%s)",
-                            fromUser, Condition.sourceToString(condition.source)));
-                }
-            }
+            boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
 
             mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
                     Binder.getCallingUid());
@@ -5508,7 +5501,7 @@
             if (android.app.Flags.modesApi()
                     && fromUser
                     && !isCallerSystemOrSystemUiOrShell()) {
-                throw new SecurityException(String.format(
+                throw new SecurityException(TextUtils.formatSimple(
                         "Calling %s with fromUser == true is only allowed for system", method));
             }
         }
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1660c3e..e546f42 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -21,13 +21,11 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.BugreportParams;
@@ -39,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -96,6 +95,7 @@
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
     private final Object mLock = new Object();
+    private final Injector mInjector;
     private final Context mContext;
     private final AppOpsManager mAppOps;
     private final TelephonyManager mTelephonyManager;
@@ -346,6 +346,14 @@
         AtomicFile getMappingFile() {
             return mMappingFile;
         }
+
+        UserManager getUserManager() {
+            return mContext.getSystemService(UserManager.class);
+        }
+
+        DevicePolicyManager getDevicePolicyManager() {
+            return mContext.getSystemService(DevicePolicyManager.class);
+        }
     }
 
     BugreportManagerServiceImpl(Context context) {
@@ -357,6 +365,7 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     BugreportManagerServiceImpl(Injector injector) {
+        mInjector = injector;
         mContext = injector.getContext();
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -389,12 +398,7 @@
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, bugreportMode
                 == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            ensureUserCanTakeBugReport(bugreportMode);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        ensureUserCanTakeBugReport(bugreportMode);
 
         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
         synchronized (mLock) {
@@ -433,7 +437,6 @@
     @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
     public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
             FileDescriptor bugreportFd, String bugreportFile,
-
             boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, false);
@@ -565,54 +568,48 @@
     }
 
     /**
-     * Validates that the current user is an admin user or, when bugreport is requested remotely
-     * that the current user is an affiliated user.
+     * Validates that the calling user is an admin user or, when bugreport is requested remotely
+     * that the user is an affiliated user.
      *
-     * @throws IllegalArgumentException if the current user is not an admin user
+     * @throws IllegalArgumentException if the calling user is not an admin user
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
-        UserInfo currentUser = null;
+        // Get the calling userId before clearing the caller identity.
+        int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
+        boolean isAdminUser = false;
+        final long identity = Binder.clearCallingIdentity();
         try {
-            currentUser = ActivityManager.getService().getCurrentUser();
-        } catch (RemoteException e) {
-            // Impossible to get RemoteException for an in-process call.
+            isAdminUser = mInjector.getUserManager().isUserAdmin(callingUserId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-
-        if (currentUser == null) {
-            logAndThrow("There is no current user, so no bugreport can be requested.");
-        }
-
-        if (!currentUser.isAdmin()) {
+        if (!isAdminUser) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
-                    && isCurrentUserAffiliated(currentUser.id)) {
+                    && isUserAffiliated(callingUserId)) {
                 return;
             }
-            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
-                    + " Only admin users are allowed to take bugreport.", currentUser.id));
+            logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
+                    + " Only admin users are allowed to take bugreport.", callingUserId));
         }
     }
 
     /**
-     * Returns {@code true} if the device has device owner and the current user is affiliated
+     * Returns {@code true} if the device has device owner and the specified user is affiliated
      * with the device owner.
      */
-    private boolean isCurrentUserAffiliated(int currentUserId) {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+    private boolean isUserAffiliated(int userId) {
+        DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
         int deviceOwnerUid = dpm.getDeviceOwnerUserId();
         if (deviceOwnerUid == UserHandle.USER_NULL) {
             return false;
         }
 
-        int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
-
-        Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid
-                + " currentUserId: " + currentUserId);
-
-        if (callingUserId != deviceOwnerUid) {
-            logAndThrow("Caller is not device owner on provisioned device.");
+        if (DEBUG) {
+            Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
         }
-        if (!dpm.isAffiliatedUser(currentUserId)) {
-            logAndThrow("Current user is not affiliated to the device owner.");
+
+        if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
+            logAndThrow("User " + userId + " is not affiliated to the device owner.");
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 7f58e75e..e830d28 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -827,7 +827,8 @@
             // action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
             // installer app or null for registered apps. The callback only need to send back to the
             // registered apps so we check the null condition here.
-            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+            notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList,
+                    null /* filterExtras */);
         }
     }
 
@@ -975,14 +976,16 @@
         final Bundle options = new BroadcastOptions()
                 .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
                 .toBundle();
+        BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver =
+                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+                        snapshot, callingUid, intentExtras);
         mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
                 extras, flags, null /* targetPkg */, null /* finishedReceiver */,
                 new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
-                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
-                        snapshot, callingUid, intentExtras),
+                filterExtrasForReceiver,
                 options));
         notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
-                null /* instantUserIds */, null /* broadcastAllowList */);
+                null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver);
     }
 
     void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
@@ -1068,9 +1071,10 @@
                                       @Nullable Bundle extras,
                                       @NonNull int[] userIds,
                                       @NonNull int[] instantUserIds,
-                                      @Nullable SparseArray<int[]> broadcastAllowList) {
+                                      @Nullable SparseArray<int[]> broadcastAllowList,
+                                      @Nullable BiFunction<Integer, Bundle, Bundle> filterExtras) {
         mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
-                instantUserIds, broadcastAllowList, mHandler);
+                instantUserIds, broadcastAllowList, mHandler, filterExtras);
     }
 
     private void notifyResourcesChanged(boolean mediaStatus,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b704..c920ca8 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@
                         int returnCodeOfChild;
                         for (int childId : childUserIds) {
                             if (childId == userId) continue;
+                            if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+                                continue;
+                            }
 
                             // If package is not present in child then don't attempt to delete.
                             if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 81d5d81..5225529 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -607,6 +607,8 @@
     private final boolean mIsUpgrade;
     private final boolean mIsPreNMR1Upgrade;
     private final boolean mIsPreQUpgrade;
+    // If mIsUpgrade == true, contains the prior SDK version, else -1.
+    private final int mPriorSdkVersion;
 
     // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
     // LOCK HELD.  Can be called with mInstallLock held.
@@ -1891,6 +1893,7 @@
         mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
         mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
         mIsPreQUpgrade = testParams.isPreQupgrade;
+        mPriorSdkVersion = testParams.priorSdkVersion;
         mIsUpgrade = testParams.isUpgrade;
         mMetrics = testParams.Metrics;
         mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2230,7 +2233,7 @@
                         "Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
                                 + PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
             }
-
+            mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
             mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
                     mInjector.getSystemPartitions());
 
@@ -4623,7 +4626,7 @@
                 });
                 mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
                         packageName, extras, userIds, null /* instantUserIds */,
-                        broadcastAllowList, mHandler);
+                        broadcastAllowList, mHandler, null /* filterExtras */);
             }
         }
     }
@@ -7073,7 +7076,7 @@
             }
             mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
                     packageName, extras, userIds, null /* instantUserIds */,
-                    broadcastAllowList, mHandler);
+                    broadcastAllowList, mHandler, null /* filterExtras */);
         }
 
         @Override
@@ -7099,6 +7102,12 @@
             mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
                     visibilityAllowList, mHandler);
         }
+
+        @Override
+        public boolean isUpgradingFromLowerThan(int sdkVersion) {
+            final boolean isUpgrading = mPriorSdkVersion != -1;
+            return isUpgrading && mPriorSdkVersion < sdkVersion;
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 86d78dc..2d79718 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -65,6 +65,7 @@
     public ComponentName instantAppResolverSettingsComponent;
     public boolean isPreNmr1Upgrade;
     public boolean isPreQupgrade;
+    public int priorSdkVersion = -1;
     public boolean isUpgrade;
     public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
     public DisplayMetrics Metrics;
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 1bb0730..d05e4c6 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -41,6 +41,7 @@
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
+import java.util.function.BiFunction;
 
 /** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
  * used by PackageMonitor to improve the broadcast latency. */
@@ -105,8 +106,9 @@
             extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
         }
         extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
-        notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
-                userIds /* userIds */, instantUserIds, broadcastAllowList, handler);
+        notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras,
+                userIds /* userIds */, instantUserIds, broadcastAllowList, handler,
+                null /* filterExtras */);
     }
 
     public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -120,7 +122,8 @@
         String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                 : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
         notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, handler);
+                null /* instantUserIds */, null /* broadcastAllowList */, handler,
+                null /* filterExtras */);
     }
 
     public void notifyPackageChanged(String packageName, boolean dontKillApp,
@@ -137,12 +140,12 @@
             extras.putString(Intent.EXTRA_REASON, reason);
         }
         notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
-                instantUserIds, broadcastAllowList, handler);
+                instantUserIds, broadcastAllowList, handler, null /* filterExtras */);
     }
 
     public void notifyPackageMonitor(String action, String pkg, Bundle extras,
             int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
-            Handler handler) {
+            Handler handler, BiFunction<Integer, Bundle, Bundle> filterExtras) {
         if (!isAllowedCallbackAction(action)) {
             return;
         }
@@ -160,10 +163,11 @@
 
             if (ArrayUtils.isEmpty(instantUserIds)) {
                 doNotifyCallbacksByAction(
-                        action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
+                        action, pkg, extras, resolvedUserIds, broadcastAllowList, handler,
+                        filterExtras);
             } else {
                 doNotifyCallbacksByAction(action, pkg, extras, instantUserIds, broadcastAllowList,
-                        handler);
+                        handler, filterExtras);
             }
         } catch (RemoteException e) {
             // do nothing
@@ -199,11 +203,13 @@
         synchronized (mLock) {
             callbacks = mCallbacks;
         }
-        doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler);
+        doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+                null /* filterExtrasFunction */);
     }
 
     private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
-            SparseArray<int[]> broadcastAllowList, Handler handler) {
+            SparseArray<int[]> broadcastAllowList, Handler handler,
+            BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
         RemoteCallbackList<IRemoteCallback> callbacks;
         synchronized (mLock) {
             callbacks = mCallbacks;
@@ -223,12 +229,13 @@
 
             final int[] allowUids =
                     broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
-            doNotifyCallbacks(callbacks, intent, userId, allowUids, handler);
+            doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
         }
     }
 
     private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
-            Intent intent, int userId, int[] allowUids, Handler handler) {
+            Intent intent, int userId, int[] allowUids, Handler handler,
+            BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
         handler.post(() -> callbacks.broadcast((callback, user) -> {
             RegisterUser registerUser = (RegisterUser) user;
             if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
@@ -236,6 +243,15 @@
                 return;
             }
             int registerUid = registerUser.getUid();
+            if (filterExtrasFunction != null) {
+                final Bundle extras = intent.getExtras();
+                if (extras != null) {
+                    final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
+                    if (filteredExtras != null) {
+                        intent.replaceExtras(filteredExtras);
+                    }
+                }
+            }
             if (allowUids != null && registerUid != Process.SYSTEM_UID
                     && !ArrayUtils.contains(allowUids, registerUid)) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae273..b286b12 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@
 import android.os.Message;
 import android.os.PatternMatcher;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.SELinux;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -3189,6 +3190,9 @@
             pkg.isScannedAsStoppedSystemApp());
         if (!pkg.hasSharedUser()) {
             serializer.attributeInt(null, "userId", pkg.getAppId());
+
+            serializer.attributeBoolean(null, "isSdkLibrary",
+                    pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
         } else {
             serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
         }
@@ -4039,10 +4043,12 @@
         int targetSdkVersion = 0;
         byte[] restrictUpdateHash = null;
         boolean isScannedAsStoppedSystemApp = false;
+        boolean isSdkLibrary = false;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
             appId = parseAppId(parser);
+            isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
             sharedUserAppId = parseSharedUserAppId(parser);
             codePathStr = parser.getAttributeValue(null, "codePath");
 
@@ -4157,7 +4163,8 @@
                 PackageManagerService.reportSettingsProblem(Log.WARN,
                         "Error in package manager settings: <package> has no codePath at "
                                 + parser.getPositionDescription());
-            } else if (appId > 0) {
+            } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+                    && Flags.disallowSdkLibsToBeApps())) {
                 packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                         appId, pkgFlags, pkgPrivateFlags, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d3931a3..10e6edc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
 
@@ -1228,6 +1229,11 @@
                         sPlatformPermissions.put(permission, permissionInfo);
                     }
                 } catch (PackageManager.NameNotFoundException ignored) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as package"
+                                + " not found when retrieving permission info");
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
             }
@@ -1347,17 +1353,34 @@
                 // way we can avoid the datasource creating an attribution context for every call.
                 if (!(fromDatasource && current.equals(attributionSource))
                         && next != null && !current.isTrusted(context)) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as "
+                                + current + " attribution source isn't a data source and "
+                                + current + " isn't trusted");
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 // If we already checked the permission for this one, skip the work
                 if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
                         permission, current)) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as we"
+                                + " aren't skipping permission checks and permission check returns"
+                                + " false for " + current);
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 if (next != null && !checkPermission(context, permissionManagerServiceInt,
                         permission, next)) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " permission check returns false for next source " + next);
+                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
@@ -1402,6 +1425,10 @@
 
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
+                        if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+                            Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
+                                    + " mode is MODE_ERRORED for " + attributionSource);
+                        }
                         return PermissionChecker.PERMISSION_HARD_DENIED;
                     }
                     case AppOpsManager.MODE_IGNORED: {
@@ -1670,6 +1697,12 @@
                 final AttributionSource resolvedAttributionSource = resolveAttributionSource(
                         context, accessorSource);
                 if (resolvedAttributionSource.getPackageName() == null) {
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (op == OP_BLUETOOTH_CONNECT) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as resolved"
+                                + "package name for " + resolvedAttributionSource + " returned"
+                                + " null");
+                    }
                     return AppOpsManager.MODE_ERRORED;
                 }
                 int notedOp = op;
@@ -1683,6 +1716,13 @@
                 if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
                     checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
                     if (checkedOpResult == MODE_ERRORED) {
+                        // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                        if (op == OP_BLUETOOTH_CONNECT) {
+                            Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                    + " checkOp for resolvedAttributionSource "
+                                    + resolvedAttributionSource + " and op " + op
+                                    + " returned MODE_ERRORED");
+                        }
                         return checkedOpResult;
                     }
                     notedOp = attributedOp;
@@ -1722,7 +1762,22 @@
                         throw new SecurityException(msg + ":" + e.getMessage());
                     }
                 }
-                return Math.max(checkedOpResult, notedOpResult);
+                int result = Math.max(checkedOpResult, notedOpResult);
+                // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+                    if (result == checkedOpResult) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " checkOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + op
+                                + " returned MODE_ERRORED");
+                    } else {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " noteOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + notedOp
+                                + " returned MODE_ERRORED");
+                    }
+                }
+                return result;
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3000a1c..5b13d3fe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5201,8 +5201,8 @@
     // TODO(b/117479243): handle it in InputPolicy
     /** {@inheritDoc} */
     @Override
-    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags) {
+    public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags) {
         if ((policyFlags & FLAG_WAKE) != 0) {
             if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
                     PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 03a7bd3..3016b39 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -706,12 +706,14 @@
      * Generally, it's best to keep as little as possible in the queue thread
      * because it's the most fragile.
      * @param displayId The display ID of the motion event.
+     * @param source the {@link InputDevice} source that caused the motion.
+     * @param action the {@link MotionEvent} action for the motion.
      * @param policyFlags The policy flags associated with the motion.
      *
      * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
      */
-    int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags);
+    int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags);
 
     /**
      * Called from the input dispatcher thread before a key is dispatched to a window.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 145eb3b..ca8afe1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -223,7 +223,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -949,7 +948,7 @@
     private int mConfigurationSeq;
 
     /**
-     * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
+     * Temp configs used in {@link #ensureActivityConfiguration()}
      */
     private final Configuration mTmpConfig = new Configuration();
     private final Rect mTmpBounds = new Rect();
@@ -1511,7 +1510,7 @@
                 updatePictureInPictureMode(null, false);
             } else {
                 mLastReportedMultiWindowMode = inMultiWindowMode;
-                ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
+                ensureActivityConfiguration();
             }
         }
     }
@@ -1530,8 +1529,7 @@
             // precede the configuration change from the resize.
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
             mLastReportedMultiWindowMode = inPictureInPictureMode;
-            ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
-                    true /* ignoreVisibility */);
+            ensureActivityConfiguration(true /* ignoreVisibility */);
             if (inPictureInPictureMode && findMainWindow() == null) {
                 // Prevent malicious app entering PiP without valid WindowState, which can in turn
                 // result a non-touchable PiP window since the InputConsumer for PiP requires it.
@@ -3107,7 +3105,7 @@
         // {@link #returningOptions} of the activity under this one can be applied in
         // {@link #handleAlreadyVisible()}.
         if (changed || !occludesParent) {
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
         }
         return changed;
     }
@@ -3747,8 +3745,8 @@
             }
 
             if (ensureVisibility) {
-                mDisplayContent.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        false /* preserveWindows */, true /* notifyClients */);
+                mDisplayContent.ensureActivitiesVisible(null /* starting */,
+                        true /* notifyClients */);
             }
         }
 
@@ -4165,8 +4163,7 @@
         if (rootTask != null && rootTask.shouldSleepOrShutDownActivities()) {
             // Activity is always relaunched to either resumed or paused state. If it was
             // relaunched while hidden (by keyguard or smth else), it should be stopped.
-            rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                    false /* preserveWindows */);
+            rootTask.ensureActivitiesVisible(null /* starting */);
         }
     }
 
@@ -4681,14 +4678,12 @@
 
     void setShowWhenLocked(boolean showWhenLocked) {
         mShowWhenLocked = showWhenLocked;
-        mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */, false /* preserveWindows */);
+        mAtmService.mRootWindowContainer.ensureActivitiesVisible();
     }
 
     void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
         mInheritShownWhenLocked = inheritShowWhenLocked;
-        mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */, false /* preserveWindows */);
+        mAtmService.mRootWindowContainer.ensureActivitiesVisible();
     }
 
     /**
@@ -6413,7 +6408,7 @@
         }
 
         mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
     }
 
     /**
@@ -7894,7 +7889,7 @@
     void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
             Configuration config) {
         super.applyFixedRotationTransform(info, displayFrames, config);
-        ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        ensureActivityConfiguration();
     }
 
     /**
@@ -7989,7 +7984,7 @@
         startFreezingScreen(originalDisplayRotation);
         // This activity may relaunch or perform configuration change so once it has reported drawn,
         // the screen can be unfrozen.
-        ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+        ensureActivityConfiguration();
         if (mTransitionController.isCollecting(this)) {
             // In case the task was changed from PiP but still keeps old transform.
             task.resetSurfaceControlTransforms();
@@ -8017,7 +8012,7 @@
         // the request is handled at task level with letterbox.
         if (!getMergedOverrideConfiguration().equals(
                 mLastReportedConfiguration.getMergedConfiguration())) {
-            ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
+            ensureActivityConfiguration(
                     false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
             if (mTransitionController.inPlayingTransition(this)) {
                 mTransitionController.mValidateActivityCompat.add(this);
@@ -9525,14 +9520,12 @@
         return mLastReportedDisplayId != getDisplayId();
     }
 
-    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
-        return ensureActivityConfiguration(globalChanges, preserveWindow,
-                false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
+    boolean ensureActivityConfiguration() {
+        return ensureActivityConfiguration(false /* ignoreVisibility */);
     }
 
-    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
-            boolean ignoreVisibility) {
-        return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility,
+    boolean ensureActivityConfiguration(boolean ignoreVisibility) {
+        return ensureActivityConfiguration(ignoreVisibility,
                 false /* isRequestedOrientationChanged */);
     }
 
@@ -9540,9 +9533,6 @@
      * Make sure the given activity matches the current configuration. Ensures the HistoryRecord
      * is updated with the correct configuration and all other bookkeeping is handled.
      *
-     * @param globalChanges The changes to the global configuration.
-     * @param preserveWindow If the activity window should be preserved on screen if the activity
-     *                       is relaunched.
      * @param ignoreVisibility If we should try to relaunch the activity even if it is invisible
      *                         (stopped state). This is useful for the case where we know the
      *                         activity will be visible soon and we want to ensure its configuration
@@ -9552,8 +9542,8 @@
      * @return False if the activity was relaunched and true if it wasn't relaunched because we
      *         can't or the app handles the specific configuration that is changing.
      */
-    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
-            boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
+    boolean ensureActivityConfiguration(boolean ignoreVisibility,
+            boolean isRequestedOrientationChanged) {
         final Task rootTask = getRootTask();
         if (rootTask.mConfigWillChange) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9667,10 +9657,21 @@
         if (shouldRelaunchLocked(changes, mTmpConfig)) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
             configChangeFlags |= changes;
-            startFreezingScreenLocked(globalChanges);
+            if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
+                    && !mTransitionController.isShellTransitionsEnabled()) {
+                startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+            }
+            final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
+                    != getWindowConfiguration().getDisplayRotation()
+                    || !mTmpConfig.windowConfiguration.getMaxBounds().equals(
+                            getWindowConfiguration().getMaxBounds());
+            final boolean isAppResizeOnly = !displayMayChange
+                    && (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
+                            | CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
             // Do not preserve window if it is freezing screen because the original window won't be
             // able to update drawn state that causes freeze timeout.
-            preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
+            // TODO(b/258618073): Always preserve if possible.
+            final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
             final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
             if (hasResizeChange) {
                 final boolean isDragResizing = task.isDragResizing();
@@ -9834,11 +9835,6 @@
         return changes;
     }
 
-    private static boolean isResizeOnlyChange(int change) {
-        return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
-                | CONFIG_SCREEN_LAYOUT)) == 0;
-    }
-
     private static boolean hasResizeChange(int change) {
         return (change & (CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
                 | CONFIG_SCREEN_LAYOUT)) != 0;
@@ -9882,8 +9878,6 @@
                     task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
         }
 
-        startFreezingScreenLocked(0);
-
         try {
             ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
                     (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cb2adbc..d90d017 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -73,7 +73,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
@@ -1733,6 +1732,7 @@
             // So disallow the transient hide activity to move itself to front, e.g. trampoline.
             if (!avoidMoveToFront() && (mService.mHomeProcess == null
                     || mService.mHomeProcess.mUid != realCallingUid)
+                    && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents())
                     && r.mTransitionController.isTransientHide(targetTask)) {
                 mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
             }
@@ -1859,8 +1859,7 @@
                 // over is removed.
                 // Passing {@code null} as the start parameter ensures all activities are made
                 // visible.
-                mTargetRootTask.ensureActivitiesVisible(null /* starting */,
-                        0 /* configChanges */, !PRESERVE_WINDOWS);
+                mTargetRootTask.ensureActivitiesVisible(null /* starting */);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetRootTask.mDisplayContent.executeAppTransition();
@@ -2867,7 +2866,7 @@
                 mRootWindowContainer.resumeFocusedTasksTopActivities(mTargetRootTask, null,
                         mOptions, mTransientLaunch);
             } else {
-                mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+                mRootWindowContainer.ensureActivitiesVisible();
             }
         } else {
             ActivityOptions.abort(mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dbae29b..f43c1b0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -118,7 +118,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
@@ -496,16 +495,13 @@
     final UpdateConfigurationResult mTmpUpdateConfigurationResult =
             new UpdateConfigurationResult();
 
+    // TODO(b/258618073): Remove this and make the related methods return whether config is changed.
     static final class UpdateConfigurationResult {
         // Configuration changes that were updated.
         int changes;
         // If the activity was relaunched to match the new configuration.
         boolean activityRelaunched;
-
-        void reset() {
-            changes = 0;
-            activityRelaunched = false;
-        }
+        boolean mIsUpdating;
     }
 
     /** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -3834,8 +3830,7 @@
                     Settings.System.clearConfiguration(values);
                 }
                 updateConfigurationLocked(values, null, false, false /* persistent */,
-                        UserHandle.USER_NULL, false /* deferResume */,
-                        mTmpUpdateConfigurationResult);
+                        UserHandle.USER_NULL, false /* deferResume */);
                 return mTmpUpdateConfigurationResult.changes != 0;
             } finally {
                 Binder.restoreCallingIdentity(origId);
@@ -4507,12 +4502,6 @@
         }
     }
 
-    private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
-            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
-        return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
-                deferResume, null /* result */);
-    }
-
     /**
      * Do either or both things: (1) change the current configuration, and (2)
      * make sure the given activity is running with the (now) current
@@ -4524,8 +4513,7 @@
      *               for that particular user
      */
     boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
-            boolean initLocale, boolean persistent, int userId, boolean deferResume,
-            ActivityTaskManagerService.UpdateConfigurationResult result) {
+            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
         int changes = 0;
         boolean kept = true;
 
@@ -4533,19 +4521,18 @@
         try {
             if (values != null) {
                 changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
+                mTmpUpdateConfigurationResult.changes = changes;
+                mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
+            mTmpUpdateConfigurationResult.mIsUpdating = false;
             continueWindowLayout();
         }
-
-        if (result != null) {
-            result.changes = changes;
-            result.activityRelaunched = !kept;
-        }
+        mTmpUpdateConfigurationResult.activityRelaunched = !kept;
         return kept;
     }
 
@@ -5325,12 +5312,10 @@
             }
 
             if (starting != null) {
-                kept = starting.ensureActivityConfiguration(changes,
-                        false /* preserveWindow */);
+                kept = starting.ensureActivityConfiguration();
                 // And we need to make sure at this point that all other activities
                 // are made visible with the correct configuration.
-                mRootWindowContainer.ensureActivitiesVisible(starting, changes,
-                        !PRESERVE_WINDOWS);
+                mRootWindowContainer.ensureActivitiesVisible(starting);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 10efb94..1872f6e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1462,7 +1462,7 @@
                 }
                 mLaunchingActivityWakeLock.release();
             }
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
         }
 
         // Atomically retrieve all of the other things to do.
@@ -1603,7 +1603,7 @@
          */
         rootTask.cancelAnimation();
         rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
-        rootTask.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+        rootTask.ensureActivitiesVisible(null /* starting */);
         activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
                 true /* processPausingActivities */, null /* configuration */);
 
@@ -1622,7 +1622,7 @@
             // Follow on the workaround: activities are kept force hidden till the new windowing
             // mode is set.
             rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         } finally {
             mService.continueWindowLayout();
@@ -2026,7 +2026,7 @@
 
         final Task rootTask = r.getRootTask();
         if (rootTask.getDisplayArea().allResumedActivitiesComplete()) {
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             // Make sure activity & window visibility should be identical
             // for all displays in this stage.
             mRootWindowContainer.executeAppTransitionForAllDisplay();
@@ -2042,7 +2042,7 @@
 
         mRecentTasks.add(task);
         mService.getTaskChangeNotificationController().notifyTaskStackChanged();
-        rootTask.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        rootTask.ensureActivitiesVisible(null /* starting */);
 
         // When launching tasks behind, update the last active time of the top task after the new
         // task has been shown briefly
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 68d13cd..6ed8967 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -91,6 +91,13 @@
     /** Non-zero if this controller is triggered by shell transition. */
     private final @TransitionOp int mTransitionOp;
 
+    /**
+     * Whether {@link #setupStartTransaction} is called when the transition is ready.
+     * If this is never set for {@link #OP_CHANGE}, the display may be changed to original state
+     * before the transition is ready, then this controller should be finished.
+     */
+    private boolean mIsStartTransactionPrepared;
+
     /** Whether the start transaction of the transition is committed (by shell). */
     private boolean mIsStartTransactionCommitted;
 
@@ -226,7 +233,8 @@
     void updateTargetWindows() {
         if (mTransitionOp == OP_LEGACY) return;
         if (!mIsStartTransactionCommitted) {
-            if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+            if ((mTimeoutRunnable == null || !mIsStartTransactionPrepared)
+                    && !mDisplayContent.hasTopFixedRotationLaunchingApp()
                     && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
                 Slog.d(TAG, "Cancel for no change");
                 mDisplayContent.finishAsyncRotationIfPossible();
@@ -401,9 +409,18 @@
         if (mTimeoutRunnable == null) {
             mTimeoutRunnable = () -> {
                 synchronized (mService.mGlobalLock) {
-                    Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
-                            ? " start transaction is not committed" : mTargetWindowTokens));
+                    final String reason;
                     if (!mIsStartTransactionCommitted) {
+                        if (!mIsStartTransactionPrepared) {
+                            reason = "setupStartTransaction is not called";
+                        } else {
+                            reason = "start transaction is not committed";
+                        }
+                    } else {
+                        reason = "unfinished windows " + mTargetWindowTokens;
+                    }
+                    Slog.i(TAG, "Async rotation timeout: " + reason);
+                    if (!mIsStartTransactionCommitted && mIsStartTransactionPrepared) {
                         // The transaction commit timeout will be handled by:
                         // 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then
                         //    apply the start transaction of transition.
@@ -558,6 +575,7 @@
                 }
             }
         });
+        mIsStartTransactionPrepared = true;
     }
 
     /** Called when the start transition is ready, but it is not applied in time. */
@@ -577,6 +595,10 @@
     /** Called when the transition by shell is done. */
     void onTransitionFinished() {
         if (mTransitionOp == OP_CHANGE) {
+            if (mTargetWindowTokens.isEmpty()) {
+                // If nothing was handled, then complete with the transition.
+                mDisplayContent.finishAsyncRotationIfPossible();
+            }
             // With screen rotation animation, the windows are always faded in when they are drawn.
             // Because if they are drawn fast enough, the fade animation should not be observable.
             return;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c3f1e41..22d17b5 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1614,7 +1614,7 @@
                 "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
         activity.mTaskSupervisor.mStoppingActivities.remove(activity);
         activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */, false /* preserveWindows */, true);
+                true /* notifyClients */);
     }
 
     private static void restoreLaunchBehind(@NonNull ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 2e47677..e4eb7b3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -74,13 +74,13 @@
     }
 
     /**
-     * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+     * Similar to {@link #scheduleTransactionItem}, but it sends the transaction immediately and
+     * it can be called without WM lock.
      *
      * @see WindowProcessController#setReportedProcState(int)
      */
-    void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+    void scheduleTransactionItemNow(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem) throws RemoteException {
-        // Immediately dispatching to client, and must not access WMS.
         final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
         if (transactionItem.isActivityLifecycleItem()) {
             clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f8dc9c7..e7ecf52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -779,7 +779,7 @@
 
     /**
      * Used to prevent recursions when calling
-     * {@link #ensureActivitiesVisible(ActivityRecord, int, boolean, boolean)}
+     * {@link #ensureActivitiesVisible(ActivityRecord, boolean)}
      */
     private boolean mInEnsureActivitiesVisible = false;
 
@@ -1713,7 +1713,7 @@
         if (handled && requestingContainer instanceof ActivityRecord) {
             final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
             final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
-                    false /* deferResume */, null /* result */);
+                    false /* deferResume */);
             if (!kept) {
                 mRootWindowContainer.resumeFocusedTasksTopActivities();
             }
@@ -1721,7 +1721,7 @@
             // We have a new configuration to push so we need to update ATMS for now.
             // TODO: Clean up display configuration push between ATMS and WMS after unification.
             updateDisplayOverrideConfigurationLocked(config, null /* starting */,
-                    false /* deferResume */, null);
+                    false /* deferResume */);
         }
         return handled;
     }
@@ -6333,7 +6333,7 @@
 
         Settings.System.clearConfiguration(values);
         updateDisplayOverrideConfigurationLocked(values, null /* starting */,
-                false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
+                false /* deferResume */);
         return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
     }
 
@@ -6342,8 +6342,7 @@
      * new one will be computed in WM based on current display info.
      */
     boolean updateDisplayOverrideConfigurationLocked(Configuration values,
-            ActivityRecord starting, boolean deferResume,
-            ActivityTaskManagerService.UpdateConfigurationResult result) {
+            ActivityRecord starting, boolean deferResume) {
 
         int changes = 0;
         boolean kept = true;
@@ -6361,19 +6360,19 @@
                 } else {
                     changes = performDisplayOverrideConfigUpdate(values);
                 }
+                mAtmService.mTmpUpdateConfigurationResult.changes = changes;
+                mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
+            mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
             mAtmService.continueWindowLayout();
         }
 
-        if (result != null) {
-            result.changes = changes;
-            result.activityRelaunched = !kept;
-        }
+        mAtmService.mTmpUpdateConfigurationResult.activityRelaunched = !kept;
         return kept;
     }
 
@@ -6569,8 +6568,7 @@
     }
 
 
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         if (mInEnsureActivitiesVisible) {
             // Don't do recursive work.
             return;
@@ -6579,8 +6577,7 @@
         try {
             mInEnsureActivitiesVisible = true;
             forAllRootTasks(rootTask -> {
-                rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                        notifyClients);
+                rootTask.ensureActivitiesVisible(starting, notifyClients);
             });
             if (mTransitionController.useShellTransitionsRotation()
                     && mTransitionController.isCollecting()
@@ -6619,7 +6616,7 @@
         if (!wasTransitionSet) {
             prepareAppTransition(TRANSIT_NONE);
         }
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+        mRootWindowContainer.ensureActivitiesVisible();
 
         // If there was a transition set already we don't want to interfere with it as we might be
         // starting it too early.
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 9cc311d..f40eb24 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -33,8 +33,6 @@
     private boolean mAboveTop;
     private boolean mContainerShouldBeVisible;
     private boolean mBehindFullyOccludedContainer;
-    private int mConfigChanges;
-    private boolean mPreserveWindows;
     private boolean mNotifyClients;
 
     EnsureActivitiesVisibleHelper(TaskFragment container) {
@@ -45,14 +43,10 @@
      * Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
      *
      * @param starting The activity that is being started
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
      * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
      *                      be sent to the clients.
      */
-    void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
-            boolean notifyClients) {
+    void reset(ActivityRecord starting, boolean notifyClients) {
         mStarting = starting;
         mTopRunningActivity = mTaskFragment.topRunningActivity();
         // If the top activity is not fullscreen, then we need to make sure any activities under it
@@ -60,33 +54,26 @@
         mAboveTop = mTopRunningActivity != null;
         mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
         mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
-        mConfigChanges = configChanges;
-        mPreserveWindows = preserveWindows;
         mNotifyClients = notifyClients;
     }
 
     /**
      * Update and commit visibility with an option to also update the configuration of visible
      * activities.
-     * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
-     * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @see Task#ensureActivitiesVisible(ActivityRecord)
+     * @see RootWindowContainer#ensureActivitiesVisible()
      * @param starting The top most activity in the task.
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
      *
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
      * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
      *                      be sent to the clients.
      */
-    void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
-            boolean notifyClients) {
-        reset(starting, configChanges, preserveWindows, notifyClients);
+    void process(@Nullable ActivityRecord starting, boolean notifyClients) {
+        reset(starting, notifyClients);
 
         if (DEBUG_VISIBILITY) {
-            Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity
-                    + " configChanges=0x" + Integer.toHexString(configChanges));
+            Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity);
         }
         if (mTopRunningActivity != null && mTaskFragment.asTask() != null) {
             // TODO(14709632): Check if this needed to be implemented in TaskFragment.
@@ -107,8 +94,7 @@
             final TaskFragment childTaskFragment = child.asTaskFragment();
             if (childTaskFragment != null
                     && childTaskFragment.getTopNonFinishingActivity() != null) {
-                childTaskFragment.updateActivityVisibilities(starting, configChanges,
-                        preserveWindows, notifyClients);
+                childTaskFragment.updateActivityVisibilities(starting, notifyClients);
                 // The TaskFragment should fully occlude the activities below if the bounds
                 // equals to its parent task, unless it is translucent.
                 mBehindFullyOccludedContainer |=
@@ -188,13 +174,11 @@
             // First: if this is not the current activity being started, make
             // sure it matches the current configuration.
             if (r != mStarting && mNotifyClients) {
-                r.ensureActivityConfiguration(0 /* globalChanges */, mPreserveWindows,
-                        true /* ignoreVisibility */);
+                r.ensureActivityConfiguration(true /* ignoreVisibility */);
             }
 
             if (!r.attachedToProcess()) {
-                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
-                        resumeTopActivity && isTop, r);
+                makeVisibleAndRestartIfNeeded(mStarting, resumeTopActivity && isTop, r);
             } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
                 if (DEBUG_VISIBILITY) {
@@ -213,8 +197,6 @@
             } else {
                 r.makeVisibleIfNeeded(mStarting, mNotifyClients);
             }
-            // Aggregate current change flags.
-            mConfigChanges |= r.configChangeFlags;
         } else {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Make invisible? " + r
@@ -242,16 +224,13 @@
         }
     }
 
-    private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+    private void makeVisibleAndRestartIfNeeded(ActivityRecord starting,
             boolean andResume, ActivityRecord r) {
         // This activity needs to be visible, but isn't even running...
         // get it started and resume if no other root task in this root task is resumed.
         if (DEBUG_VISIBILITY) {
             Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
         }
-        if (r != starting) {
-            r.startFreezingScreenLocked(configChanges);
-        }
         if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8cf4713..a84ebd9 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -167,10 +167,10 @@
 
     /** {@inheritDoc} */
     @Override
-    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags) {
+    public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags) {
         return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
-                displayId, whenNanos, policyFlags);
+                displayId, source, action, whenNanos, policyFlags);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cbc7b83..6d11804 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -43,7 +43,6 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_GOING_AWAY;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_PER_DISPLAY;
@@ -239,7 +238,7 @@
         // Update the sleep token first such that ensureActivitiesVisible has correct sleep token
         // state when evaluating visibilities.
         updateKeyguardSleepToken();
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
         InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */,
                 displayId);
         setWakeTransitionReady();
@@ -291,7 +290,7 @@
 
             // Some stack visibility might change (e.g. docked stack)
             mRootWindowContainer.resumeFocusedTasksTopActivities();
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.addStartingWindowsForVisibleActivities();
             mWindowManager.executeAppTransition();
         } finally {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9305396..97cc982 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -46,6 +46,7 @@
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
@@ -1185,6 +1186,7 @@
         mUserAspectRatio = getUserMinAspectRatioOverrideCode();
 
         return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+                && mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
                 && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 5269d35..7b23004 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -28,7 +28,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -126,8 +125,7 @@
                 // The activity may be relaunched if it cannot handle the current configuration
                 // changes. The activity will be paused state if it is relaunched, otherwise it
                 // keeps the original stopped state.
-                targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
-                        false /* preserveWindow */, true /* ignoreVisibility */);
+                targetActivity.ensureActivityConfiguration(true /* ignoreVisibility */);
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s",
                         targetActivity.getConfiguration());
             }
@@ -261,7 +259,7 @@
 
             // If we updated the launch-behind state, update the visibility of the activities after
             // we fetch the visible tasks to be controlled by the animation
-            mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mService.mRootWindowContainer.ensureActivitiesVisible();
 
             ActivityOptions options = null;
             if (eventTime > 0) {
@@ -380,8 +378,7 @@
                         // transition (the target activity will be one of closing apps).
                         if (!controller.shouldDeferCancelWithScreenshot()
                                 && !targetRootTask.isFocusedRootTaskOnDisplay()) {
-                            targetRootTask.ensureActivitiesVisible(null /* starting */,
-                                    0 /* starting */, false /* preserveWindows */);
+                            targetRootTask.ensureActivitiesVisible(null /* starting */);
                         }
                         // Keep target root task in place, nothing changes, so ignore the transition
                         // logic below
@@ -389,7 +386,7 @@
                     }
 
                     mWindowManager.prepareAppTransitionNone();
-                    mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, false);
+                    mService.mRootWindowContainer.ensureActivitiesVisible();
                     mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
 
                     // No reason to wait for the pausing activity in this case, as the hiding of
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a75dae..ca66a66 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -63,7 +63,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
@@ -1753,8 +1752,7 @@
         // activities are affecting configuration now.
         // Passing null here for 'starting' param value, so that visibility of actual starting
         // activity will be properly updated.
-        ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */, false /* notifyClients */);
+        ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
 
         if (displayId == INVALID_DISPLAY) {
             // The caller didn't provide a valid display id, skip updating config.
@@ -1778,7 +1776,7 @@
         if (displayContent != null) {
             // Update the configuration of the activities on the display.
             return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
-                    deferResume, null /* result */);
+                    deferResume);
         } else {
             return true;
         }
@@ -1865,16 +1863,18 @@
      * Make sure that all activities that need to be visible in the system actually are and update
      * their configuration.
      */
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows) {
-        ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+    void ensureActivitiesVisible() {
+        ensureActivitiesVisible(null /* starting */);
+    }
+
+    void ensureActivitiesVisible(ActivityRecord starting) {
+        ensureActivitiesVisible(starting, true /* notifyClients */);
     }
 
     /**
-     * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @see #ensureActivitiesVisible()
      */
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         if (mTaskSupervisor.inActivityVisibilityUpdate()
                 || mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
             // Don't do recursive work.
@@ -1885,8 +1885,7 @@
             // First the front root tasks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
                 final DisplayContent display = getChildAt(displayNdx);
-                display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                        notifyClients);
+                display.ensureActivitiesVisible(starting, notifyClients);
             }
         } finally {
             mTaskSupervisor.endActivityVisibilityUpdate();
@@ -2237,7 +2236,7 @@
             try {
                 if (localVisibilityDeferred) {
                     mTaskSupervisor.setDeferRootVisibilityUpdate(false);
-                    ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+                    ensureActivitiesVisible();
                 }
             } finally {
                 transitionController.continueTransitionReady();
@@ -2370,7 +2369,7 @@
             // It may be nothing to resume because there are pausing activities or all the top
             // activities are resumed. Then it still needs to make sure all visible activities are
             // running in case the tasks were reordered or there are non-top visible activities.
-            ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS);
+            ensureActivitiesVisible();
         }
     }
 
@@ -2542,8 +2541,7 @@
                     // display orientation can be updated first if needed. Otherwise there may
                     // have redundant configuration changes due to apply outdated display
                     // orientation (from keyguard) to activity.
-                    rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                            false /* preserveWindows */);
+                    rootTask.ensureActivitiesVisible(null /* starting */);
                 }
             });
         }
@@ -2885,8 +2883,7 @@
             if (allowDelay) {
                 result[0] &= task.goToSleepIfPossible(shuttingDown);
             } else {
-                task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        !PRESERVE_WINDOWS);
+                task.ensureActivitiesVisible(null /* starting */);
             }
         });
         return result[0];
@@ -3774,8 +3771,7 @@
                 }
             }
             if (!mHasActivityStarted) {
-                ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        false /* preserveWindows */);
+                ensureActivitiesVisible();
             }
             return mHasActivityStarted;
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7995028..ed54ea8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -771,6 +771,7 @@
             if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                 mService.dispatchNewAnimatorScaleLocked(this);
             }
+            mProcess.mWindowSession = this;
         }
         mAddedWindows.add(w);
     }
@@ -782,6 +783,9 @@
         }
     }
 
+    boolean hasWindow() {
+        return !mAddedWindows.isEmpty();
+    }
 
     void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
             boolean visible, int type) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dbfcc22..c674176 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -90,7 +90,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_TASK_MSG;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -760,7 +759,7 @@
             return;
         }
         mResizeMode = resizeMode;
-        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
         mRootWindowContainer.resumeFocusedTasksTopActivities();
         updateTaskDescription();
     }
@@ -801,15 +800,14 @@
             if (setBounds(bounds, forced) != BOUNDS_CHANGE_NONE) {
                 final ActivityRecord r = topRunningActivityLocked();
                 if (r != null) {
-                    kept = r.ensureActivityConfiguration(0 /* globalChanges */,
-                            preserveWindow);
+                    kept = r.ensureActivityConfiguration();
                     // Preserve other windows for resizing because if resizing happens when there
                     // is a dialog activity in the front, the activity that still shows some
                     // content to the user will become black and cause flickers. Note in most cases
                     // this won't cause tons of irrelevant windows being preserved because only
                     // activities in this task may experience a bounds change. Configs for other
                     // activities stay the same.
-                    mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow);
+                    mRootWindowContainer.ensureActivitiesVisible(r);
                     if (!kept) {
                         mRootWindowContainer.resumeFocusedTasksTopActivities();
                     }
@@ -915,7 +913,7 @@
         if (!deferResume) {
             // The task might have already been running and its visibility needs to be synchronized
             // with the visibility of the root task / windows.
-            root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            root.ensureActivitiesVisible();
             root.resumeFocusedTasksTopActivities();
         }
 
@@ -4752,7 +4750,7 @@
         }
 
         if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
-            mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+            mRootWindowContainer.ensureActivitiesVisible();
             mRootWindowContainer.resumeFocusedTasksTopActivities();
         }
     }
@@ -4793,8 +4791,7 @@
         mRootWindowContainer.resumeFocusedTasksTopActivities();
         // Update visibility of activities before notifying WM. This way it won't try to resize
         // windows that are no longer visible.
-        mRootWindowContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible();
     }
 
     final boolean isOnHomeDisplay() {
@@ -4938,41 +4935,27 @@
      * @param starting The top most activity in the task.
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link EnsureActivitiesVisibleHelper}.
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen as part of
-     *                      {@link EnsureActivitiesVisibleHelper}.
-     *
      */
-    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
-            boolean preserveWindows) {
-        ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+    void ensureActivitiesVisible(@Nullable ActivityRecord starting) {
+        ensureActivitiesVisible(starting, true /* notifyClients */);
     }
 
     /**
      * Ensure visibility with an option to also update the configuration of visible activities.
-     * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
-     * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+     * @see #ensureActivitiesVisible(ActivityRecord)
+     * @see RootWindowContainer#ensureActivitiesVisible()
      * @param starting The top most activity in the task.
      *                 The activity is either starting or resuming.
      *                 Caller should ensure starting activity is visible.
      * @param notifyClients Flag indicating whether the visibility updates should be sent to the
      *                      clients in {@link EnsureActivitiesVisibleHelper}.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link EnsureActivitiesVisibleHelper}.
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen as part of
-     *                      {@link EnsureActivitiesVisibleHelper}.
      */
     // TODO: Should be re-worked based on the fact that each task as a root task in most cases.
-    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(@Nullable ActivityRecord starting, boolean notifyClients) {
         mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
             forAllLeafTasks(task -> {
-                task.updateActivityVisibilities(starting, configChanges, preserveWindows,
-                        notifyClients);
+                task.updateActivityVisibilities(starting, notifyClients);
             }, true /* traverseTopToBottom */);
 
             if (mTranslucentActivityWaiting != null &&
@@ -5273,7 +5256,7 @@
             // tell WindowManager that r is visible even though it is at the back of the root
             // task.
             r.setVisibility(true);
-            ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            ensureActivitiesVisible(null /* starting */);
             // If launching behind, the app will start regardless of what's above it, so mark it
             // as unknown even before prior `pause`. This also prevents a race between set-ready
             // and activityPause. Launch-behind is basically only used for dream now.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c57983c..90a3b253 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1777,13 +1777,11 @@
         void onRootTaskOrderChanged(Task rootTask);
     }
 
-    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
             forAllRootTasks(rootTask -> {
-                rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
-                        notifyClients);
+                rootTask.ensureActivitiesVisible(starting, notifyClients);
             });
         } finally {
             mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d01912..d425bdf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -57,7 +57,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
@@ -950,8 +949,7 @@
         }
 
         if (shouldSleep) {
-            updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
-                    !PRESERVE_WINDOWS, true /* notifyClients */);
+            updateActivityVisibilities(null /* starting */, true /* notifyClients */);
         }
 
         return shouldSleep;
@@ -1218,12 +1216,11 @@
         return top != null && top.mLaunchTaskBehind;
     }
 
-    final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
-            boolean preserveWindows, boolean notifyClients) {
+    final void updateActivityVisibilities(@Nullable ActivityRecord starting,
+            boolean notifyClients) {
         mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
-            mEnsureActivitiesVisibleHelper.process(
-                    starting, configChanges, preserveWindows, notifyClients);
+            mEnsureActivitiesVisibleHelper.process(starting, notifyClients);
         } finally {
             mTaskSupervisor.endActivityVisibilityUpdate();
         }
@@ -1249,8 +1246,7 @@
         if (mResumedActivity == next && next.isState(RESUMED)
                 && taskDisplayArea.allResumedActivitiesComplete()) {
             // Ensure the visibility gets updated before execute app transition.
-            taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                    false /* preserveWindows */, true /* notifyClients */);
+            taskDisplayArea.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
             // Make sure we have executed any pending transitions, since there
             // should be nothing left to do at this point.
             executeAppTransition(options);
@@ -1907,7 +1903,7 @@
             prev.resumeKeyDispatchingLocked();
         }
 
-        mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.ensureActivitiesVisible(resuming);
 
         // Notify when the task stack has changed, but only if visibilities changed (not just
         // focus). Also if there is an active root pinned task - we always want to notify it about
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3117db5..b12855e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1136,8 +1136,7 @@
             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
             // the update to make the activities in the tasks invisible-requested, then the next
             // step can continue to commit the visibility.
-            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
-                    0 /* configChanges */, true /* preserveWindows */);
+            mController.mAtm.mRootWindowContainer.ensureActivitiesVisible();
             // Record all the now-hiding activities so that they are committed. Just use
             // mParticipants because we can avoid a new list this way.
             for (int i = 0; i < mTransientHideTasks.size(); ++i) {
@@ -2863,8 +2862,7 @@
      * check whether to deliver the new configuration to clients.
      */
     @Nullable
-    ArrayList<ActivityRecord> applyDisplayChangeIfNeeded() {
-        ArrayList<ActivityRecord> activitiesMayChange = null;
+    void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WindowContainer<?> wc = mParticipants.valueAt(i);
             final DisplayContent dc = wc.asDisplayContent();
@@ -2881,18 +2879,13 @@
             // If the update is deferred, sendNewConfiguration won't deliver new configuration to
             // clients, then it is the caller's responsibility to deliver the changes.
             if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
-                if (activitiesMayChange == null) {
-                    activitiesMayChange = new ArrayList<>();
-                }
-                final ArrayList<ActivityRecord> visibleActivities = activitiesMayChange;
                 dc.forAllActivities(r -> {
                     if (r.isVisibleRequested()) {
-                        visibleActivities.add(r);
+                        activitiesMayChange.add(r);
                     }
                 });
             }
         }
-        return activitiesMayChange;
     }
 
     boolean getLegacyIsReady() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 89a70e5..7b0d931 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@
 
     /* Start Available Flags */
 
-    final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
-
     final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
 
     final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c1310a6..dda33f3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3105,7 +3105,7 @@
         try {
             synchronized (mGlobalLock) {
                 if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
-                    mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+                    mRoot.ensureActivitiesVisible();
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4b99432..9e4a31c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -65,7 +65,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -571,14 +570,15 @@
         mService.deferWindowLayout();
         mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
         try {
-            final ArrayList<ActivityRecord> activitiesMayChange =
-                    transition != null ? transition.applyDisplayChangeIfNeeded() : null;
-            if (activitiesMayChange != null) {
-                effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+            final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
+            if (transition != null) {
+                transition.applyDisplayChangeIfNeeded(haveConfigChanges);
+                if (!haveConfigChanges.isEmpty()) {
+                    effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+                }
             }
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
-            final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
             Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
                     t.getChanges().entrySet().iterator();
             while (entries.hasNext()) {
@@ -626,7 +626,7 @@
                     // When removing pip, make sure that onStop is sent to the app ahead of
                     // onPictureInPictureModeChanged.
                     // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
-                    wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                    wc.asTask().ensureActivitiesVisible(null /* starting */);
                     wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(
                             null /* launchedActivity */, false /* processPausingActivities */,
                             "force-stop-on-removing-pip");
@@ -692,29 +692,16 @@
             if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
                 mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
                 // Already calls ensureActivityConfig
-                mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+                mService.mRootWindowContainer.ensureActivitiesVisible();
                 mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
             } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
                 for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
                     haveConfigChanges.valueAt(i).forAllActivities(r -> {
-                        r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
-                        if (activitiesMayChange != null) {
-                            activitiesMayChange.remove(r);
+                        if (r.isVisibleRequested()) {
+                            r.ensureActivityConfiguration(true /* ignoreVisibility */);
                         }
                     });
                 }
-                // TODO(b/258618073): Combine with haveConfigChanges after confirming that there
-                //  is no problem to always preserve window. Currently this uses the parameters
-                //  as ATMS#ensureConfigAndVisibilityAfterUpdate.
-                if (activitiesMayChange != null) {
-                    for (int i = activitiesMayChange.size() - 1; i >= 0; --i) {
-                        final ActivityRecord ar = activitiesMayChange.get(i);
-                        if (!ar.isVisibleRequested()) continue;
-                        ar.ensureActivityConfiguration(0 /* globalChanges */,
-                                !PRESERVE_WINDOWS, true /* ignoreVisibility */,
-                                false /* isRequestedOrientationChanged */);
-                    }
-                }
             }
 
             if (effects != 0) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 5721750..b8fa5e5 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -188,6 +188,10 @@
     // Set to true when process was launched with a wrapper attached
     private volatile boolean mUsingWrapper;
 
+    /** Non-null if this process may have a window. */
+    @Nullable
+    Session mWindowSession;
+
     // Thread currently set for VR scheduling
     int mVrThreadTid;
 
@@ -399,7 +403,7 @@
             // the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
             try {
                 // No WM lock here.
-                mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+                mAtm.getLifecycleManager().scheduleTransactionItemNow(
                         thread, configurationChangeItem);
             } catch (Exception e) {
                 Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
@@ -989,7 +993,7 @@
             if (packageName.equals(r.packageName)
                     && r.applyAppSpecificConfig(nightMode, localesOverride, gender)
                     && r.isVisibleRequested()) {
-                r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
+                r.ensureActivityConfiguration();
             }
         }
     }
@@ -1675,7 +1679,12 @@
     private void scheduleClientTransactionItem(@NonNull IApplicationThread thread,
             @NonNull ClientTransactionItem transactionItem) {
         try {
-            mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+            if (mWindowSession != null && mWindowSession.hasWindow()) {
+                mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+            } else {
+                // Non-UI process can handle the change directly.
+                mAtm.getLifecycleManager().scheduleTransactionItemNow(thread, transactionItem);
+            }
         } catch (DeadObjectException e) {
             // Expected if the process has been killed.
             Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
@@ -1723,7 +1732,7 @@
             overrideConfig.assetsSeq = assetSeq;
             r.onRequestedOverrideConfigurationChanged(overrideConfig);
             if (r.isVisibleRequested()) {
-                r.ensureActivityConfiguration(0, true);
+                r.ensureActivityConfiguration();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 32638e0..58ade1b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -252,6 +252,7 @@
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -3675,7 +3676,7 @@
 
         markRedrawForSyncReported();
 
-        if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+        if (Flags.bundleClientTransactionFlag()) {
             getProcess().scheduleClientTransactionItem(
                     WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
                             mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0dd0564..9ba0a2a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -358,8 +358,8 @@
     void notifyVibratorState(int32_t deviceId, bool isOn) override;
     bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
     void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
-    void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
-                                       uint32_t& policyFlags) override;
+    void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
+                                       nsecs_t when, uint32_t& policyFlags) override;
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
                                           uint32_t policyFlags) override;
     std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
@@ -1496,7 +1496,8 @@
     handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
 }
 
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
+                                                       int32_t action, nsecs_t when,
                                                        uint32_t& policyFlags) {
     ATRACE_CALL();
     // Policy:
@@ -1525,7 +1526,7 @@
     const jint wmActions =
             env->CallIntMethod(mServiceObj,
                                gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
-                               displayId, when, policyFlags);
+                               displayId, source, action, when, policyFlags);
     if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
         return;
     }
@@ -2943,7 +2944,7 @@
             "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");
 
     GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, clazz,
-            "interceptMotionBeforeQueueingNonInteractive", "(IJI)I");
+            "interceptMotionBeforeQueueingNonInteractive", "(IIIJI)I");
 
     GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
             "interceptKeyBeforeDispatching",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7..1185a4e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@
 
         // AdServicesManagerService (PP API service)
         t.traceBegin("StartAdServicesManagerService");
-        SystemService adServices = mSystemServiceManager
-                .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
-        if (adServices instanceof Dumpable) {
-            mDumper.addDumpable((Dumpable) adServices);
-        }
+        mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
         // OnDevicePersonalizationSystemService
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 8d8274c..87fc7a4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -122,6 +122,16 @@
     }
 
     @Test
+    public void testRegisterHdrListener_ZeroMinHdrPercent() {
+        IBinder otherBinder = mock(IBinder.class);
+        mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT,
+            /* minimumHdrPercentOfScreen= */ 0, otherBinder);
+
+        verify(mMockHdrInfoListener).unregister(mMockBinder);
+        verify(mMockHdrInfoListener).register(otherBinder);
+    }
+
+    @Test
     public void testRegisterNotCalledIfHbmConfigIsMissing() {
         IBinder otherBinder = mock(IBinder.class);
         mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003d..32878b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
+import android.location.flags.Flags;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.Log;
@@ -97,6 +100,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -140,6 +144,9 @@
     private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
     private static final String MISSING_PERMISSION = "missing_permission";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private Random mRandom;
 
     @Mock
@@ -1347,6 +1354,24 @@
         assertThat(mManager.isVisibleToCaller()).isFalse();
     }
 
+    @Test
+    public void testValidateLocation_futureLocation() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+        Location location = createLocation(NAME, mRandom);
+        mProvider.setProviderLocation(location);
+
+        assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isEqualTo(location);
+
+        Location futureLocation = createLocation(NAME, mRandom);
+        futureLocation.setElapsedRealtimeNanos(
+                SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+        mProvider.setProviderLocation(futureLocation);
+
+        assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+                PERMISSION_FINE)).isEqualTo(location);
+    }
+
     @MediumTest
     @Test
     public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 6c44fd0..60cedcf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -44,6 +44,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.function.BiFunction;
 
 /**
  * A unit test for PackageMonitorCallbackHelper implementation.
@@ -78,7 +79,8 @@
 
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -91,7 +93,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
-                null /* broadcastAllowList */, mHandler);
+                null /* broadcastAllowList */, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
 
@@ -99,12 +101,41 @@
         mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
 
     @Test
+    public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
+        Bundle result = new Bundle();
+        result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+        result.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[]{FAKE_PACKAGE_NAME});
+        BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> result;
+
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+                null /* broadcastAllowList */, mHandler, filterExtras);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+                bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
+        Intent intent = bundle.getParcelable(
+                PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGES_SUSPENDED);
+        String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+        assertThat(pkgList).isNotNull();
+        assertThat(pkgList.length).isEqualTo(1);
+        assertThat(pkgList[0]).isEqualTo(FAKE_PACKAGE_NAME);
+    }
+
+    @Test
     public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception {
         IRemoteCallback callback = createMockPackageMonitorCallback();
 
@@ -112,7 +143,8 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -136,7 +168,8 @@
         // Notify for user 10
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -239,7 +272,8 @@
         mPackageMonitorCallbackHelper.onUserRemoved(10);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
-                null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+                null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+                null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -255,7 +289,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList, mHandler);
+                null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
     }
@@ -271,7 +305,7 @@
                 Binder.getCallingUid());
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList, mHandler);
+                null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
     }
@@ -287,7 +321,7 @@
                 Process.SYSTEM_UID);
         mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
                 FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
-                null /* instantUserIds */, broadcastAllowList, mHandler);
+                null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
 
         verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c9e1c4a..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,21 +16,30 @@
 
 package com.android.server.biometrics.sensors.face;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -42,6 +51,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.biometrics.Flags;
@@ -66,6 +76,7 @@
     private static final int ID_VIRTUAL = 6;
     private static final String NAME_DEFAULT = "default";
     private static final String NAME_VIRTUAL = "virtual";
+    private static final String OP_PACKAGE_NAME = "FaceServiceTest/SystemUi";
 
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -78,15 +89,19 @@
     @Rule
     public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
     @Mock
-    FaceProvider mFaceProviderDefault;
+    private FaceProvider mFaceProviderDefault;
     @Mock
-    FaceProvider mFaceProviderVirtual;
+    private FaceProvider mFaceProviderVirtual;
     @Mock
-    IFace mDefaultFaceDaemon;
+    private IFace mDefaultFaceDaemon;
     @Mock
-    IFace mVirtualFaceDaemon;
+    private IFace mVirtualFaceDaemon;
     @Mock
-    IBiometricService mIBiometricService;
+    private IBiometricService mIBiometricService;
+    @Mock
+    private IBinder mToken;
+    @Mock
+    private IFaceServiceReceiver mFaceServiceReceiver;
 
     private final SensorProps mDefaultSensorProps = new SensorProps();
     private final SensorProps mVirtualSensorProps = new SensorProps();
@@ -117,7 +132,13 @@
                 new SensorProps[]{mVirtualSensorProps});
         when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
         when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+        when(mFaceProviderDefault.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+        when(mFaceProviderVirtual.containsSensor(anyInt()))
+                .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
 
+        mContext.getTestablePermissions().setPermission(
+                USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
         mFaceSensorConfigurations = new FaceSensorConfigurations(false);
         mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
                 (name) -> {
@@ -136,7 +157,13 @@
                     if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
                     if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
                     return null;
-                }, () -> mIBiometricService);
+                }, () -> mIBiometricService,
+                (name) -> {
+                    if (NAME_DEFAULT.equals(name)) return mFaceProviderDefault;
+                    if (NAME_VIRTUAL.equals(name)) return mFaceProviderVirtual;
+                    return null;
+                },
+                () -> new String[]{NAME_DEFAULT, NAME_VIRTUAL});
     }
 
     @Test
@@ -191,6 +218,39 @@
                 eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
     }
 
+    @Test
+    public void testOptionsForAuthentication() throws Exception {
+        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+                .build();
+        initService();
+        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
+
+        final long operationId = 5;
+        mFaceService.mServiceWrapper.authenticate(mToken, operationId, mFaceServiceReceiver,
+                faceAuthenticateOptions);
+
+        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
+    @Test
+    public void testOptionsForDetect() throws Exception {
+        FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+                .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
+                        .getPackageName())
+                .build();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.string.config_keyguardComponent,
+                OP_PACKAGE_NAME);
+        initService();
+        mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
+        mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
+                faceAuthenticateOptions);
+
+        assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
     private void waitForRegistration() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index f570ba2..88956b6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -61,6 +62,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
@@ -92,6 +94,7 @@
     private static final String NAME_VIRTUAL = "virtual";
     private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
             List.of();
+    private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
 
     @Rule
     public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -343,6 +346,24 @@
         assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid());
     }
 
+    @Test
+    public void testOptionsForDetect() throws Exception {
+        FingerprintAuthenticateOptions fingerprintAuthenticateOptions =
+                new FingerprintAuthenticateOptions.Builder()
+                        .setOpPackageName(ComponentName.unflattenFromString(
+                                OP_PACKAGE_NAME).getPackageName())
+                        .build();
+
+        mContext.getOrCreateTestableResources().addOverride(
+                R.string.config_keyguardComponent,
+                OP_PACKAGE_NAME);
+        initServiceWithAndWait(NAME_DEFAULT);
+        mService.mServiceWrapper.detectFingerprint(mToken, mServiceReceiver,
+                fingerprintAuthenticateOptions);
+
+        assertThat(fingerprintAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+    }
+
 
     private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
             FingerprintProvider provider, long operationId) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 071d571..9b28b81 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -53,11 +53,11 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class VirtualCameraControllerTest {
 
-    private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+    private static final String CAMERA_NAME_1 = "Virtual camera 1";
     private static final int CAMERA_WIDTH_1 = 100;
     private static final int CAMERA_HEIGHT_1 = 200;
 
-    private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+    private static final String CAMERA_NAME_2 = "Virtual camera 2";
     private static final int CAMERA_WIDTH_2 = 400;
     private static final int CAMERA_HEIGHT_2 = 600;
     private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
@@ -84,7 +84,7 @@
     @Test
     public void registerCamera_registersCamera() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
 
         ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
                 ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -98,7 +98,7 @@
     @Test
     public void unregisterCamera_unregistersCamera() throws Exception {
         VirtualCameraConfig config = createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
         mVirtualCameraController.registerCamera(config);
 
         mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +109,9 @@
     @Test
     public void close_unregistersAllCameras() throws Exception {
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+                CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
         mVirtualCameraController.registerCamera(createVirtualCameraConfig(
-                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
+                CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
 
         mVirtualCameraController.close();
 
@@ -129,10 +129,10 @@
     }
 
     private VirtualCameraConfig createVirtualCameraConfig(
-            int width, int height, int format, int displayNameResId) {
+            int width, int height, int format, String displayName) {
         return new VirtualCameraConfig.Builder()
                 .addStreamConfig(width, height, format)
-                .setDisplayNameStringRes(displayNameResId)
+                .setName(displayName)
                 .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
                 .build();
     }
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index dc1d2c5..21b8a94 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,23 +16,26 @@
 
 package com.android.server.os;
 
-import android.app.admin.flags.Flags;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
-
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,6 +51,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
 import java.util.concurrent.CompletableFuture;
@@ -66,6 +71,11 @@
     private BugreportManagerServiceImpl mService;
     private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
+    @Mock
+    private UserManager mMockUserManager;
+    @Mock
+    private DevicePolicyManager mMockDevicePolicyManager;
+
     private int mCallingUid = 1234;
     private String mCallingPackage  = "test.package";
     private AtomicFile mMappingFile;
@@ -75,14 +85,17 @@
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
         mAllowlistedPackages.add(mContext.getPackageName());
         mService = new BugreportManagerServiceImpl(
-                new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
-                        mMappingFile));
+                new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+                        mMockUserManager, mMockDevicePolicyManager));
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
+        // The calling user is an admin user by default.
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
     }
 
     @After
@@ -165,6 +178,36 @@
     }
 
     @Test
+    public void testStartBugreport_throwsForNonAdminUser() throws Exception {
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+
+        Exception thrown = assertThrows(Exception.class,
+                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                        new FileDescriptor(), /* screenshotFd= */ null,
+                        BugreportParams.BUGREPORT_MODE_FULL,
+                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                        /* isScreenshotRequested= */ false));
+
+        assertThat(thrown.getMessage()).contains("not an admin user");
+    }
+
+    @Test
+    public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception {
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+        when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1);
+        when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false);
+
+        Exception thrown = assertThrows(Exception.class,
+                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                        new FileDescriptor(), /* screenshotFd= */ null,
+                        BugreportParams.BUGREPORT_MODE_REMOTE,
+                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                        /* isScreenshotRequested= */ false));
+
+        assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
+    }
+
+    @Test
     public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         Listener listener = new Listener(latch);
@@ -207,7 +250,8 @@
 
     private void clearAllowlist() {
         mService = new BugreportManagerServiceImpl(
-                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile));
+                new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+                        mMockUserManager, mMockDevicePolicyManager));
     }
 
     private static class Listener implements IDumpstateListener {
@@ -258,4 +302,27 @@
             complete(successful);
         }
     }
+
+    private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+
+        private final UserManager mUserManager;
+        private final DevicePolicyManager mDevicePolicyManager;
+
+        TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
+                UserManager um, DevicePolicyManager dpm) {
+            super(context, allowlistedPackages, mappingFile);
+            mUserManager = um;
+            mDevicePolicyManager = dpm;
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public DevicePolicyManager getDevicePolicyManager() {
+            return mDevicePolicyManager;
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9408a8b..3ab7496 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -9258,41 +9258,46 @@
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+    public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.setCallerIsNormalPackage();
 
-        Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
-                SOURCE_CONTEXT);
-        mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
-        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
-
         Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
                 SOURCE_USER_ACTION);
-        mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+        mBinderService.setAutomaticZenRuleState("id", withSourceUser);
+
         verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
                 eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_MODES_API)
-    public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+    public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
             throws Exception {
         ZenModeHelper zenModeHelper = setUpMockZenTest();
         mService.setCallerIsNormalPackage();
 
         Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
                 SOURCE_CONTEXT);
-        assertThrows(IllegalArgumentException.class,
-                () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
-                        /* fromUser= */ true));
+        mBinderService.setAutomaticZenRuleState("id", withSourceContext);
 
-        Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
-                SOURCE_USER_ACTION);
-        assertThrows(IllegalArgumentException.class,
-                () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
-                        /* fromUser= */ false));
+        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+            throws Exception {
+        ZenModeHelper zenModeHelper = setUpMockZenTest();
+        mService.isSystemUid = true;
+
+        Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+                SOURCE_CONTEXT);
+        mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+        verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+                eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
     }
 
     private ZenModeHelper setUpMockZenTest() {
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 718c598..e88a00b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -161,6 +161,7 @@
 
 import com.android.internal.R;
 import com.android.server.wm.ActivityRecord.State;
+import com.android.window.flags.Flags;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -320,7 +321,7 @@
     }
 
     private void ensureActivityConfiguration(ActivityRecord activity) {
-        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        activity.ensureActivityConfiguration();
     }
 
     @Test
@@ -718,7 +719,7 @@
 
         // Clear size compat.
         activity.clearSizeCompatMode();
-        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        activity.ensureActivityConfiguration();
         mDisplayContent.sendNewConfiguration();
 
         // Relaunching the app should still respect the orientation request.
@@ -819,8 +820,7 @@
 
             task.onConfigurationChanged(newConfig);
 
-            activity.ensureActivityConfiguration(0 /* globalChanges */,
-                    false /* preserveWindow */, true /* ignoreVisibility */);
+            activity.ensureActivityConfiguration(true /* ignoreVisibility */);
 
             final ActivityConfigurationChangeItem expected =
                     ActivityConfigurationChangeItem.obtain(activity.token,
@@ -1563,8 +1563,7 @@
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
         topActivity.setShowWhenLocked(true);
 
         // Verify the stack-top activity is occluded keyguard.
@@ -1624,7 +1623,6 @@
         secondActivity.finishing = true;
         secondActivity.completeFinishing("test");
         verify(secondActivity.mDisplayContent).ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */ , false /* preserveWindows */,
                 true /* notifyClients */);
 
         // Finish the first activity
@@ -1632,7 +1630,6 @@
         firstActivity.setVisibleRequested(true);
         firstActivity.completeFinishing("test");
         verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
-                0 /* configChanges */ , false /* preserveWindows */,
                 true /* notifyClients */);
 
         // Remove the translucent activity and clear invocations for next test
@@ -1960,6 +1957,7 @@
         display.continueUpdateOrientationForDiffOrienLaunchingApp();
         assertTrue(display.isFixedRotationLaunchingApp(activity));
 
+        activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
         // Simulate the rotation has been updated to previous one, e.g. sensor updates before the
         // remote rotation is completed.
         doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -1970,14 +1968,12 @@
         activity.finishFixedRotationTransform();
         final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
         assertNotNull(rotationAnim);
-        rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
 
         // Because the display doesn't rotate, the rotated activity needs to cancel the fixed
         // rotation. There should be a rotation animation to cover the change of activity.
         verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
         assertTrue(activity.isFreezingScreen());
         assertFalse(displayRotation.isRotatingSeamlessly());
-        assertTrue(rotationAnim.isRotating());
 
         // Simulate the remote rotation has completed and the configuration doesn't change, then
         // the rotated activity should also be restored by clearing the transform.
@@ -3370,7 +3366,7 @@
         // to client if the app didn't request IME visible.
         assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
-        if (mWm.mFlags.mWindowStateResizeItemFlag) {
+        if (Flags.bundleClientTransactionFlag()) {
             verify(app2.getProcess()).scheduleClientTransactionItem(
                     isA(WindowStateResizeItem.class));
         } else {
@@ -3697,7 +3693,7 @@
         doReturn(false).when(activity).showToCurrentUser();
         spyOn(taskFragment);
         doReturn(false).when(taskFragment).shouldBeVisible(any());
-        display.ensureActivitiesVisible(null, 0, false, false);
+        display.ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
         assertFalse(activity.isVisibleRequested());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index c757457..09f677e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -172,7 +172,7 @@
     @Test
     public void testScheduleTransactionItemUnlocked() throws RemoteException {
         // Use non binder client to get non-recycled ClientTransaction.
-        mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem);
+        mLifecycleManager.scheduleTransactionItemNow(mNonBinderClient, mTransactionItem);
 
         // Dispatch immediately.
         assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
@@ -217,6 +217,8 @@
 
     @Test
     public void testDispatchPendingTransactions() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
         mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction);
 
         mLifecycleManager.dispatchPendingTransactions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfe79bf..6497ee9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -75,7 +75,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
@@ -490,7 +489,7 @@
         newOverrideConfig.fontScale += 0.3;
 
         defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig,
-                null /* starting */, false /* deferResume */, null /* result */);
+                null /* starting */, false /* deferResume */);
 
         // Check that global configuration is updated, as we've updated default display's config.
         Configuration globalConfig = mWm.mRoot.getConfiguration();
@@ -499,7 +498,7 @@
 
         // Return back to original values.
         defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig,
-                null /* starting */, false /* deferResume */, null /* result */);
+                null /* starting */, false /* deferResume */);
         globalConfig = mWm.mRoot.getConfiguration();
         assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
         assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
@@ -1176,7 +1175,7 @@
         activity.setRequestedOrientation(newOrientation);
 
         verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
-                anyBoolean(), same(null));
+                anyBoolean());
         assertEquals(ROTATION_180, dc.getRotation());
     }
 
@@ -2123,10 +2122,8 @@
         // Once transition starts, rotation is applied and transition shows DC rotating.
         testPlayer.startTransition();
         waitUntilHandlersIdle();
-        verify(activity1).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
-                anyBoolean());
-        verify(activity2).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
-                anyBoolean());
+        verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean());
+        verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean());
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
         assertTrue(testPlayer.mController.isPlaying());
@@ -2248,11 +2245,11 @@
             // The assertion will fail if DisplayArea#ensureActivitiesVisible is called twice.
             assertFalse(called[0]);
             called[0] = true;
-            mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+            mDisplayContent.ensureActivitiesVisible(null, false);
             return null;
-        }).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean());
+        }).when(mockTda).ensureActivitiesVisible(any(), anyBoolean());
 
-        mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+        mDisplayContent.ensureActivitiesVisible(null, false);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 8de45b0..32b3558 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -106,8 +106,7 @@
         topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
 
         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
                 mRecentsComponent, true /* getRecentsAnimation */);
@@ -178,8 +177,7 @@
         mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
                 null /* recentsAnimationRunner */);
 
-        verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
-                anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */);
+        verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
     }
 
@@ -199,8 +197,7 @@
                 "testRestartRecentsActivity");
 
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
         doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
         doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
 
@@ -354,8 +351,7 @@
 
         doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId();
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
-                any() /* starting */, anyInt() /* configChanges */,
-                anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+                any() /* starting */, anyBoolean() /* notifyClients */);
 
         startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(),
                 true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 89cd726..b5883b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -823,8 +823,7 @@
                 .build();
 
         doReturn(false).when(secondActivity).occludesParent();
-        homeRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        homeRootTask.ensureActivitiesVisible(null /* starting */);
 
         assertTrue(firstActivity.shouldBeVisible());
     }
@@ -1419,8 +1418,7 @@
 
         // Any common path that updates activity visibility should clear the unknown visibility
         // records that are no longer visible according to hierarchy.
-        task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        task.ensureActivitiesVisible(null /* starting */);
         // Assume the top activity relayouted, just remove it directly.
         unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
         // All unresolved records should be removed.
@@ -1441,8 +1439,7 @@
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
                 anyBoolean());
 
-        task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        task.ensureActivitiesVisible(null /* starting */);
         verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
                 anyBoolean());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c3102e0..5518c60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -947,7 +947,7 @@
 
         // Recompute the natural configuration in the new display.
         mActivity.clearSizeCompatMode();
-        mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        mActivity.ensureActivityConfiguration();
         // Because the display cannot rotate, the portrait activity will fit the short side of
         // display with keeping portrait bounds [200, 0 - 700, 1000] in center.
         assertEquals(newDisplayBounds.height(), currentBounds.height());
@@ -4858,7 +4858,7 @@
         }
         // Make sure to use the provided configuration to construct the size compat fields.
         activity.clearSizeCompatMode();
-        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+        activity.ensureActivityConfiguration();
         // Make sure the display configuration reflects the change of activity.
         if (activity.mDisplayContent.updateOrientation()) {
             activity.mDisplayContent.sendNewConfiguration();
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 8cd9ff3..90493d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -375,8 +375,7 @@
         // Always keep things awake.
         doReturn(true).when(mWmService.mRoot).hasAwakeDisplay();
         // Called when moving activity to pinned stack.
-        doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(),
-                anyInt(), anyBoolean(), anyBoolean());
+        doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(), anyBoolean());
         spyOn(mWmService.mDisplayWindowSettings);
         spyOn(mWmService.mDisplayWindowSettingsProvider);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index ec068be..00e22fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -269,8 +269,7 @@
         mTaskFragment.getTask().addChild(activityBelow, 0);
 
         // Ensure the activity below is visible
-        mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                false /* preserveWindows */);
+        mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */);
         assertEquals(true, activityBelow.isVisibleRequested());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index da7612b..45e1e95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -339,16 +339,14 @@
         // Check visibility of occluded tasks
         doReturn(false).when(leafTask1).shouldBeVisible(any());
         doReturn(true).when(leafTask2).shouldBeVisible(any());
-        rootTask.ensureActivitiesVisible(
-                null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+        rootTask.ensureActivitiesVisible(null /* starting */);
         assertFalse(activity1.isVisible());
         assertTrue(activity2.isVisible());
 
         // Check visibility of not occluded tasks
         doReturn(true).when(leafTask1).shouldBeVisible(any());
         doReturn(true).when(leafTask2).shouldBeVisible(any());
-        rootTask.ensureActivitiesVisible(
-                null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+        rootTask.ensureActivitiesVisible(null /* starting */);
         assertTrue(activity1.isVisible());
         assertTrue(activity2.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index bd111ad..52e2d8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -89,8 +89,8 @@
     }
 
     @Override
-    public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
-            int policyFlags) {
+    public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+            long whenNanos, int policyFlags) {
         return 0;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 71447e7..fddd771 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1495,8 +1495,7 @@
         verify(taskSnapshotController, times(0)).recordSnapshot(eq(task1));
 
         enteringAnimReports.clear();
-        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
-                anyInt(), anyBoolean(), anyBoolean());
+        doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean());
         final boolean[] wasInFinishingTransition = { false };
         controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e31ee11..400e4b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -38,7 +38,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -306,10 +305,12 @@
 
     @Test
     public void testCachedStateConfigurationChange() throws RemoteException {
-        doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
+        doNothing().when(mClientLifecycleManager).scheduleTransactionItemNow(any(), any());
         final IApplicationThread thread = mWpc.getThread();
         final Configuration newConfig = new Configuration(mWpc.getConfiguration());
         newConfig.densityDpi += 100;
+        mWpc.mWindowSession = getTestSession();
+        mWpc.mWindowSession.onWindowAdded(mock(WindowState.class));
         // Non-cached state will send the change directly.
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         clearInvocations(mClientLifecycleManager);
@@ -322,13 +323,13 @@
         newConfig.densityDpi += 100;
         mWpc.onConfigurationChanged(newConfig);
         verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
-        verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
+        verify(mClientLifecycleManager, never()).scheduleTransactionItemNow(eq(thread), any());
 
         // Cached -> non-cached will send the previous deferred config immediately.
         mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
         final ArgumentCaptor<ConfigurationChangeItem> captor =
                 ArgumentCaptor.forClass(ConfigurationChangeItem.class);
-        verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+        verify(mClientLifecycleManager).scheduleTransactionItemNow(
                 eq(thread), captor.capture());
         final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
         captor.getValue().preExecute(client);
@@ -432,7 +433,7 @@
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
                 GRAMMATICAL_GENDER_NOT_SPECIFIED);
-        verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
+        verify(activity).ensureActivityConfiguration();
     }
 
     @Test
@@ -443,7 +444,7 @@
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
                 GRAMMATICAL_GENDER_NOT_SPECIFIED);
         verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
-        verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
+        verify(activity, never()).ensureActivityConfiguration();
     }
 
     @Test
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 1dc5dcf..3a0a6ab 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -35,4 +35,7 @@
         "android.hardware.usb-V1.3-java",
         "android.hardware.usb-V3-java",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 22a5cd7..9e292be 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2115,6 +2115,20 @@
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
             "com.android.omadm.service.CONFIGURATION_UPDATE";
 
+    /**
+     * Activity action: Show setting to reset mobile networks.
+     *
+     * <p>On devices with a settings activity to reset mobile networks, the activity should be
+     * launched without additional permissions.
+     *
+     * <p>On some devices, this settings activity may not exist. Callers should ensure that this
+     * case is appropriately handled.
+     */
+    @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS)
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS =
+            "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
+
     //
     //
     // Device Info
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208d..5cbb1aa 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@
             .autoFix()
             .build()
 
-        return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+        return LintFix.create()
+            .name(annotateFix.getDisplayName())
+            .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
     }
 
     private val annotation: String