Merge "Manual brightness working as expected with refactoring flag enabled" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index eac416a..24cd610 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -641,6 +641,19 @@
     ],
 }
 
+java_aconfig_library {
+    name: "android.permission.flags-aconfig-java-host",
+    aconfig_declarations: "android.permission.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+        "com.android.nfcservices",
+    ],
+}
+
 // SQLite
 aconfig_declarations {
     name: "android.database.sqlite-aconfig",
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 613678b..410074e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1989,6 +1989,9 @@
                 mAdminProtectedPackages.put(userId, packageNames);
             }
         }
+        if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+            postCheckIdleStates(userId);
+        }
     }
 
     @Override
diff --git a/config/Android.bp b/config/Android.bp
index 6a6f848..dd681ca 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,3 +33,9 @@
     name: "preloaded-classes-denylist",
     srcs: ["preloaded-classes-denylist"],
 }
+
+prebuilt_etc {
+    name: "dirty-image-objects",
+    src: "dirty-image-objects",
+    filename: "dirty-image-objects",
+}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0dab3de..5e9fdfb 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1349,12 +1349,26 @@
     public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
 
     /**
-     * The most restricted level where the apps are considered "in-hibernation",
-     * its package visibility to the rest of the system is limited.
+     * The restricted level where the apps are in a force-stopped state.
      *
      * @hide
      */
-    public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+    public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60;
+
+    /**
+     * The heavily background restricted level, where apps cannot start without an explicit
+     * launch by the user.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70;
+
+    /**
+     * A reserved restriction level that is not well-defined.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_LEVEL_CUSTOM = 90;
 
     /**
      * Not a valid restriction level, it defines the maximum numerical value of restriction level.
@@ -1371,12 +1385,116 @@
             RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
             RESTRICTION_LEVEL_RESTRICTED_BUCKET,
             RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
-            RESTRICTION_LEVEL_HIBERNATION,
+            RESTRICTION_LEVEL_FORCE_STOPPED,
+            RESTRICTION_LEVEL_USER_LAUNCH_ONLY,
+            RESTRICTION_LEVEL_CUSTOM,
             RESTRICTION_LEVEL_MAX,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface RestrictionLevel{}
 
+    /**
+     * Maximum string length for sub reason for restriction.
+     *
+     * @hide
+     */
+    public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;
+
+    /**
+     * Restriction reason unknown - do not use directly.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_UNKNOWN = 0;
+
+    /**
+     * Restriction reason to be used when this is normal behavior for the state.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_DEFAULT = 1;
+
+    /**
+     * Restriction reason is some kind of timeout that moves the app to a more restricted state.
+     * The threshold should specify how long the app was dormant, in milliseconds.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_DORMANT = 2;
+
+    /**
+     * Restriction reason to be used when removing a restriction due to direct or indirect usage
+     * of the app, especially to undo any automatic restrictions.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_USAGE = 3;
+
+    /**
+     * Restriction reason to be used when the user chooses to manually restrict the app, through
+     * UI or command line interface.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_USER = 4;
+
+    /**
+     * Restriction reason to be used when the user chooses to manually restrict the app on being
+     * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
+     * high battery drain or affecting system performance and the OS recommends that the user
+     * restrict the app.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_USER_NUDGED = 5;
+
+    /**
+     * Restriction reason to be used when the OS automatically detects that the app is causing
+     * system health issues such as performance degradation, battery drain, high memory usage, etc.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;
+
+    /**
+     * Restriction reason to be used when there is a server-side decision made to restrict an app
+     * that is showing widespread problems on user devices, or violating policy in some way.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;
+
+    /**
+     * Restriction reason to be used when some other problem requires restricting the app.
+     *
+     * For use with noteAppRestrictionEnabled()
+     * @hide
+     */
+    public static final int RESTRICTION_REASON_OTHER = 8;
+
+    /** @hide */
+    @IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
+            RESTRICTION_REASON_UNKNOWN,
+            RESTRICTION_REASON_DEFAULT,
+            RESTRICTION_REASON_DORMANT,
+            RESTRICTION_REASON_USAGE,
+            RESTRICTION_REASON_USER,
+            RESTRICTION_REASON_USER_NUDGED,
+            RESTRICTION_REASON_SYSTEM_HEALTH,
+            RESTRICTION_REASON_REMOTE_TRIGGER,
+            RESTRICTION_REASON_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RestrictionReason{}
+
     /** @hide */
     @android.ravenwood.annotation.RavenwoodKeep
     public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -1393,12 +1511,16 @@
                 return "restricted_bucket";
             case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
                 return "background_restricted";
-            case RESTRICTION_LEVEL_HIBERNATION:
-                return "hibernation";
+            case RESTRICTION_LEVEL_FORCE_STOPPED:
+                return "stopped";
+            case RESTRICTION_LEVEL_USER_LAUNCH_ONLY:
+                return "user_only";
+            case RESTRICTION_LEVEL_CUSTOM:
+                return "custom";
             case RESTRICTION_LEVEL_MAX:
                 return "max";
             default:
-                return "";
+                return String.valueOf(level);
         }
     }
 
@@ -6127,6 +6249,43 @@
     }
 
     /**
+     * Requests the system to log the reason for restricting/unrestricting an app. This API
+     * should be called before applying any change to the restriction level.
+     * <p>
+     * The {@code enabled} value determines whether the state is being applied or removed.
+     * Not all restrictions are actual restrictions. For example,
+     * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
+     * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
+     * app is being put in a power-save allowlist.
+     *
+     * @param packageName the package name of the app
+     * @param uid the uid of the app
+     * @param restrictionLevel the restriction level specified in {@code RestrictionLevel}
+     * @param enabled whether the state is being applied or removed
+     * @param reason the reason for the restriction state change, from {@code RestrictionReason}
+     * @param subReason a string sub reason limited to 16 characters that specifies additional
+     *                  information about the reason for restriction.
+     * @param threshold for reasons that are due to exceeding some threshold, the threshold value
+     *                  must be specified. The unit of the threshold depends on the reason and/or
+     *                  subReason. For time, use milliseconds. For memory, use KB. For count, use
+     *                  the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.DEVICE_POWER)
+    public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
+            @RestrictionLevel int restrictionLevel, boolean enabled,
+            @RestrictionReason int reason,
+            @Nullable String subReason, long threshold) {
+        try {
+            getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
+                    reason, subReason, threshold);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Notifies {@link #getRunningAppProcesses app processes} that the system properties
      * have changed.
      *
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index dca164d..3765c81 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1009,4 +1009,12 @@
      * @param originatingUid The UID of the instrumented app that initialized the override
      */
     void clearAllOverridePermissionStates(int originatingUid);
+
+    /**
+     * Request the system to log the reason for restricting / unrestricting an app.
+     * @see ActivityManager#noteAppRestrictionEnabled
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
+    void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
+            boolean enabled, int reason, in String subReason, long threshold);
 }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 1aee9fe..a9f2d74 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -317,11 +317,6 @@
     public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
 
     /**
-     * Returns whether the application exemptions feature flag is enabled.
-     */
-    public abstract boolean isApplicationExemptionsFlagEnabled();
-
-    /**
      * Returns a map of admin to {@link Bundle} map of restrictions set by the admins for the
      * provided {@code packageName} in the provided {@code userId}
      */
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6a07484..ac843cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -195,6 +195,25 @@
   }
 }
 
+flag {
+  name: "power_exemption_bg_usage_fix"
+  namespace: "enterprise"
+  description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background"
+  bug: "333379020"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "disallow_user_control_bg_usage_fix"
+  namespace: "enterprise"
+  description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed"
+  bug: "326031059"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
 
 flag {
   name: "esim_management_ux_enabled"
@@ -228,6 +247,16 @@
 }
 
 flag {
+  name: "always_persist_do"
+  namespace: "enterprise"
+  description: "Always write device_owners2.xml so that migration flags aren't lost"
+  bug: "335232744"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "is_recursive_required_app_merging_enabled"
   namespace: "enterprise"
   description: "Guards a new flow for recursive required enterprise app list merging"
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f5bff9d..4c0da7c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,16 +4844,6 @@
     public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
             "android.hardware.rotaryencoder.lowres";
 
-  /**
-   * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
-   * support for contextual search helper.
-   *
-   * @hide
-   */
-  @SdkConstant(SdkConstantType.FEATURE)
-  public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
-      "android.software.contextualsearch";
-
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 4963a4f..321e539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -171,6 +171,13 @@
 }
 
 flag {
+    name: "schedule_stop_of_background_user"
+    namespace: "multiuser"
+    description: "Schedule background users to be stopped at a future point."
+    bug: "330351042"
+}
+
+flag {
     name: "disable_private_space_items_on_home"
     namespace: "profile_experiences"
     description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 4a4d451..7b452a8 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -74,7 +74,7 @@
      * and {@link HardwareBuffer.Format} is supported on the device.
      *
      * @return True if the device can support efficiently compositing the content described by the
-     *         dataspace and format. False if GPOU composition fallback is otherwise required.
+     *         dataspace and format. False if GPU composition fallback is otherwise required.
      */
     @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
     public boolean isCombinationSupported(@DataSpace.ColorDataSpace int dataspace,
@@ -135,7 +135,6 @@
 
     private static native long nGetDestructor();
     private static native long nCreateDefault();
-    private static native boolean nSupportFp16ForHdr(long nativeObject);
     private static native boolean nSupportMixedColorSpaces(long nativeObject);
     private static native boolean nIsCombinationSupported(
             long nativeObject, int dataspace, int format);
diff --git a/core/java/android/hardware/face/FaceCallback.java b/core/java/android/hardware/face/FaceCallback.java
new file mode 100644
index 0000000..b69024f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceCallback.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2024 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.hardware.face;
+
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR_BASE;
+import static android.hardware.face.FaceManager.getAuthHelpMessage;
+import static android.hardware.face.FaceManager.getEnrollHelpMessage;
+import static android.hardware.face.FaceManager.getErrorString;
+
+import android.content.Context;
+import android.hardware.biometrics.CryptoObject;
+import android.hardware.face.FaceManager.AuthenticationCallback;
+import android.hardware.face.FaceManager.EnrollmentCallback;
+import android.hardware.face.FaceManager.FaceDetectionCallback;
+import android.hardware.face.FaceManager.GenerateChallengeCallback;
+import android.hardware.face.FaceManager.GetFeatureCallback;
+import android.hardware.face.FaceManager.RemovalCallback;
+import android.hardware.face.FaceManager.SetFeatureCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each face related request.
+ * @hide
+ */
+public class FaceCallback {
+    private static final String TAG = " FaceCallback";
+
+    @Nullable
+    private AuthenticationCallback mAuthenticationCallback;
+    @Nullable
+    private EnrollmentCallback mEnrollmentCallback;
+    @Nullable
+    private RemovalCallback mRemovalCallback;
+    @Nullable
+    private GenerateChallengeCallback mGenerateChallengeCallback;
+    @Nullable
+    private FaceDetectionCallback mFaceDetectionCallback;
+    @Nullable
+    private SetFeatureCallback mSetFeatureCallback;
+    @Nullable
+    private GetFeatureCallback mGetFeatureCallback;
+    @Nullable
+    private Face mRemovalFace;
+    @Nullable
+    private CryptoObject mCryptoObject;
+
+    /**
+     * Construction for face authentication client callback.
+     */
+    FaceCallback(AuthenticationCallback authenticationCallback, CryptoObject cryptoObject) {
+        mAuthenticationCallback = authenticationCallback;
+        mCryptoObject = cryptoObject;
+    }
+
+    /**
+     * Construction for face detect client callback.
+     */
+    FaceCallback(FaceDetectionCallback faceDetectionCallback) {
+        mFaceDetectionCallback = faceDetectionCallback;
+    }
+
+    /**
+     * Construction for face enroll client callback.
+     */
+    FaceCallback(EnrollmentCallback enrollmentCallback) {
+        mEnrollmentCallback = enrollmentCallback;
+    }
+
+    /**
+     * Construction for face generate challenge client callback.
+     */
+    FaceCallback(GenerateChallengeCallback generateChallengeCallback) {
+        mGenerateChallengeCallback = generateChallengeCallback;
+    }
+
+    /**
+     * Construction for face set feature client callback.
+     */
+    FaceCallback(SetFeatureCallback setFeatureCallback) {
+        mSetFeatureCallback = setFeatureCallback;
+    }
+
+    /**
+     * Construction for face get feature client callback.
+     */
+    FaceCallback(GetFeatureCallback getFeatureCallback) {
+        mGetFeatureCallback = getFeatureCallback;
+    }
+
+    /**
+     * Construction for single face removal client callback.
+     */
+    FaceCallback(RemovalCallback removalCallback, Face removalFace) {
+        mRemovalCallback = removalCallback;
+        mRemovalFace = removalFace;
+    }
+
+    /**
+     * Construction for all face removal client callback.
+     */
+    FaceCallback(RemovalCallback removalCallback) {
+        mRemovalCallback = removalCallback;
+    }
+
+    /**
+     * Propagate set feature completed via the callback.
+     * @param success if the operation was completed successfully
+     * @param feature the feature that was set
+     */
+    public void sendSetFeatureCompleted(boolean success, int feature) {
+        if (mSetFeatureCallback == null) {
+            return;
+        }
+        mSetFeatureCallback.onCompleted(success, feature);
+    }
+
+    /**
+     * Propagate get feature completed via the callback.
+     * @param success if the operation was completed successfully
+     * @param features list of features available
+     * @param featureState status of the features corresponding to the previous parameter
+     */
+    public void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
+        if (mGetFeatureCallback == null) {
+            return;
+        }
+        mGetFeatureCallback.onCompleted(success, features, featureState);
+    }
+
+    /**
+     * Propagate challenge generated completed via the callback.
+     * @param sensorId id of the corresponding sensor
+     * @param userId id of the corresponding sensor
+     * @param challenge value of the challenge generated
+     */
+    public void sendChallengeGenerated(int sensorId, int userId, long challenge) {
+        if (mGenerateChallengeCallback == null) {
+            return;
+        }
+        mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
+    }
+
+    /**
+     * Propagate face detected completed via the callback.
+     * @param sensorId id of the corresponding sensor
+     * @param userId id of the corresponding user
+     * @param isStrongBiometric if the sensor is strong or not
+     */
+    public void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
+        if (mFaceDetectionCallback == null) {
+            Slog.e(TAG, "sendFaceDetected, callback null");
+            return;
+        }
+        mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
+    }
+
+    /**
+     * Propagate remove face completed via the callback.
+     * @param face removed identifier
+     * @param remaining number of face enrollments remaining
+     */
+    public void sendRemovedResult(Face face, int remaining) {
+        if (mRemovalCallback == null) {
+            return;
+        }
+        mRemovalCallback.onRemovalSucceeded(face, remaining);
+    }
+
+    /**
+     * Propagate errors via the callback.
+     * @param context corresponding context
+     * @param errMsgId represents the framework error id
+     * @param vendorCode represents the vendor error code
+     */
+    public void sendErrorResult(Context context, int errMsgId, int vendorCode) {
+        // emulate HAL 2.1 behavior and send real errMsgId
+        final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
+                ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+                    getErrorString(context, errMsgId, vendorCode));
+        } else if (mAuthenticationCallback != null) {
+            mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+                    getErrorString(context, errMsgId, vendorCode));
+        } else if (mRemovalCallback != null) {
+            mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
+                    getErrorString(context, errMsgId, vendorCode));
+        } else if (mFaceDetectionCallback != null) {
+            mFaceDetectionCallback.onDetectionError(errMsgId);
+            mFaceDetectionCallback = null;
+        }
+    }
+
+    /**
+     * Propagate enroll progress via the callback.
+     * @param remaining number of enrollment steps remaining
+     */
+    public void sendEnrollResult(int remaining) {
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onEnrollmentProgress(remaining);
+        }
+    }
+
+    /**
+     * Propagate authentication succeeded via the callback.
+     * @param face matched identifier
+     * @param userId id of the corresponding user
+     * @param isStrongBiometric if the sensor is strong or not
+     */
+    public void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
+        if (mAuthenticationCallback != null) {
+            final FaceManager.AuthenticationResult result = new FaceManager.AuthenticationResult(
+                    mCryptoObject, face, userId, isStrongBiometric);
+            mAuthenticationCallback.onAuthenticationSucceeded(result);
+        }
+    }
+
+    /**
+     * Propagate authentication failed via the callback.
+     */
+    public void sendAuthenticatedFailed() {
+        if (mAuthenticationCallback != null) {
+            mAuthenticationCallback.onAuthenticationFailed();
+        }
+    }
+
+    /**
+     * Propagate acquired result via the callback.
+     * @param context corresponding context
+     * @param acquireInfo represents the framework acquired id
+     * @param vendorCode represents the vendor acquired code
+     */
+    public void sendAcquiredResult(Context context, int acquireInfo, int vendorCode) {
+        if (mAuthenticationCallback != null) {
+            final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+                    new FaceDataFrame(acquireInfo, vendorCode));
+            sendAuthenticationFrame(context, frame);
+        } else if (mEnrollmentCallback != null) {
+            final FaceEnrollFrame frame = new FaceEnrollFrame(
+                    null /* cell */,
+                    FaceEnrollStages.UNKNOWN,
+                    new FaceDataFrame(acquireInfo, vendorCode));
+            sendEnrollmentFrame(context, frame);
+        }
+    }
+
+    /**
+     * Propagate authentication frame via the callback.
+     * @param context corresponding context
+     * @param frame authentication frame to be sent
+     */
+    public void sendAuthenticationFrame(@NonNull Context context,
+            @Nullable FaceAuthenticationFrame frame) {
+        if (frame == null) {
+            Slog.w(TAG, "Received null authentication frame");
+        } else if (mAuthenticationCallback != null) {
+            // TODO(b/178414967): Send additional frame data to callback
+            final int acquireInfo = frame.getData().getAcquiredInfo();
+            final int vendorCode = frame.getData().getVendorCode();
+            final int helpCode = getHelpCode(acquireInfo, vendorCode);
+            final String helpMessage = getAuthHelpMessage(context, acquireInfo, vendorCode);
+            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+            // Ensure that only non-null help messages are sent.
+            if (helpMessage != null) {
+                mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+            }
+        }
+    }
+
+    /**
+     * Propagate enrollment via the callback.
+     * @param context corresponding context
+     * @param frame enrollment frame to be sent
+     */
+    public void sendEnrollmentFrame(Context context, @Nullable FaceEnrollFrame frame) {
+        if (frame == null) {
+            Slog.w(TAG, "Received null enrollment frame");
+        } else if (mEnrollmentCallback != null) {
+            final FaceDataFrame data = frame.getData();
+            final int acquireInfo = data.getAcquiredInfo();
+            final int vendorCode = data.getVendorCode();
+            final int helpCode = getHelpCode(acquireInfo, vendorCode);
+            final String helpMessage = getEnrollHelpMessage(context, acquireInfo, vendorCode);
+            mEnrollmentCallback.onEnrollmentFrame(
+                    helpCode,
+                    helpMessage,
+                    frame.getCell(),
+                    frame.getStage(),
+                    data.getPan(),
+                    data.getTilt(),
+                    data.getDistance());
+        }
+    }
+
+    private static int getHelpCode(int acquireInfo, int vendorCode) {
+        return acquireInfo == FACE_ACQUIRED_VENDOR
+                ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+                : acquireInfo;
+    }
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 210ce2b..2592630 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -37,9 +37,9 @@
 import android.os.CancellationSignal;
 import android.os.CancellationSignal.OnCancelListener;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -49,7 +49,6 @@
 import android.view.Surface;
 
 import com.android.internal.R;
-import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -63,71 +62,56 @@
 
     private static final String TAG = "FaceManager";
 
-    private static final int MSG_ENROLL_RESULT = 100;
-    private static final int MSG_ACQUIRED = 101;
-    private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
-    private static final int MSG_AUTHENTICATION_FAILED = 103;
-    private static final int MSG_ERROR = 104;
-    private static final int MSG_REMOVED = 105;
-    private static final int MSG_GET_FEATURE_COMPLETED = 106;
-    private static final int MSG_SET_FEATURE_COMPLETED = 107;
-    private static final int MSG_CHALLENGE_GENERATED = 108;
-    private static final int MSG_FACE_DETECTED = 109;
-    private static final int MSG_AUTHENTICATION_FRAME = 112;
-    private static final int MSG_ENROLLMENT_FRAME = 113;
-
     private final IFaceService mService;
     private final Context mContext;
     private final IBinder mToken = new Binder();
-    @Nullable private AuthenticationCallback mAuthenticationCallback;
-    @Nullable private FaceDetectionCallback mFaceDetectionCallback;
-    @Nullable private EnrollmentCallback mEnrollmentCallback;
-    @Nullable private RemovalCallback mRemovalCallback;
-    @Nullable private SetFeatureCallback mSetFeatureCallback;
-    @Nullable private GetFeatureCallback mGetFeatureCallback;
-    @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
-    private CryptoObject mCryptoObject;
-    private Face mRemovalFace;
     private Handler mHandler;
     private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
+    private HandlerExecutor mExecutor;
 
-    private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+    private class FaceServiceReceiver extends IFaceServiceReceiver.Stub {
+        private final FaceCallback mFaceCallback;
+
+        FaceServiceReceiver(FaceCallback faceCallback) {
+            mFaceCallback = faceCallback;
+        }
 
         @Override // binder call
         public void onEnrollResult(Face face, int remaining) {
-            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendEnrollResult(remaining));
         }
 
         @Override // binder call
         public void onAcquired(int acquireInfo, int vendorCode) {
-            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendAcquiredResult(mContext, acquireInfo,
+                    vendorCode));
         }
 
         @Override // binder call
         public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
-                    isStrongBiometric ? 1 : 0, face).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendAuthenticatedSucceeded(face, userId,
+                    isStrongBiometric));
         }
 
         @Override // binder call
         public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-            mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
-                    .sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendFaceDetected(sensorId, userId,
+                    isStrongBiometric));
         }
 
         @Override // binder call
         public void onAuthenticationFailed() {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+            mExecutor.execute(mFaceCallback::sendAuthenticatedFailed);
         }
 
         @Override // binder call
         public void onError(int error, int vendorCode) {
-            mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendErrorResult(mContext, error, vendorCode));
         }
 
         @Override // binder call
         public void onRemoved(Face face, int remaining) {
-            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendRemovedResult(face, remaining));
             if (remaining == 0) {
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
@@ -137,34 +121,31 @@
 
         @Override
         public void onFeatureSet(boolean success, int feature) {
-            mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendSetFeatureCompleted(success, feature));
         }
 
         @Override
         public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = success;
-            args.arg2 = features;
-            args.arg3 = featureState;
-            mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendGetFeatureCompleted(success, features,
+                    featureState));
         }
 
         @Override
         public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
-                    .sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendChallengeGenerated(sensorId, userId,
+                    challenge));
         }
 
         @Override
         public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendAuthenticationFrame(mContext, frame));
         }
 
         @Override
         public void onEnrollmentFrame(FaceEnrollFrame frame) {
-            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+            mExecutor.execute(() -> mFaceCallback.sendEnrollmentFrame(mContext, frame));
         }
-    };
+    }
 
     /**
      * @hide
@@ -175,7 +156,8 @@
         if (mService == null) {
             Slog.v(TAG, "FaceAuthenticationManagerService was null");
         }
-        mHandler = new MyHandler(context);
+        mHandler = context.getMainThreadHandler();
+        mExecutor = new HandlerExecutor(mHandler);
         if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
                 == PackageManager.PERMISSION_GRANTED) {
             addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -193,9 +175,11 @@
      */
     private void useHandler(Handler handler) {
         if (handler != null) {
-            mHandler = new MyHandler(handler.getLooper());
-        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
-            mHandler = new MyHandler(mContext.getMainLooper());
+            mHandler = handler;
+            mExecutor = new HandlerExecutor(mHandler);
+        } else if (mHandler != mContext.getMainThreadHandler()) {
+            mHandler = mContext.getMainThreadHandler();
+            mExecutor = new HandlerExecutor(mHandler);
         }
     }
 
@@ -249,13 +233,12 @@
 
         if (mService != null) {
             try {
+                final FaceCallback faceCallback = new FaceCallback(callback, crypto);
                 useHandler(handler);
-                mAuthenticationCallback = callback;
-                mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 Trace.beginSection("FaceManager#authenticate");
                 final long authId = mService.authenticate(
-                        mToken, operationId, mServiceReceiver, options);
+                        mToken, operationId, new FaceServiceReceiver(faceCallback), options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                 }
@@ -292,10 +275,11 @@
         options.setOpPackageName(mContext.getOpPackageName());
         options.setAttributionTag(mContext.getAttributionTag());
 
-        mFaceDetectionCallback = callback;
+        final FaceCallback faceCallback = new FaceCallback(callback);
 
         try {
-            final long authId = mService.detectFace(mToken, mServiceReceiver, options);
+            final long authId = mService.detectFace(mToken,
+                    new FaceServiceReceiver(faceCallback), options);
             cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
         } catch (RemoteException e) {
             Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -367,11 +351,11 @@
 
         if (mService != null) {
             try {
-                mEnrollmentCallback = callback;
+                final FaceCallback faceCallback = new FaceCallback(callback);
                 Trace.beginSection("FaceManager#enroll");
                 final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
-                        mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
-                        previewSurface, debugConsent, options);
+                        new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+                        disabledFeatures, previewSurface, debugConsent, options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
                 }
@@ -419,10 +403,11 @@
 
         if (mService != null) {
             try {
-                mEnrollmentCallback = callback;
+                final FaceCallback faceCallback = new FaceCallback(callback);
                 Trace.beginSection("FaceManager#enrollRemotely");
                 final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
-                        mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
+                        new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+                        disabledFeatures);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
                 }
@@ -455,9 +440,9 @@
     public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
         if (mService != null) {
             try {
-                mGenerateChallengeCallback = callback;
-                mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
-                        mContext.getOpPackageName());
+                final FaceCallback faceCallback = new FaceCallback(callback);
+                mService.generateChallenge(mToken, sensorId, userId,
+                        new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -528,9 +513,9 @@
             SetFeatureCallback callback) {
         if (mService != null) {
             try {
-                mSetFeatureCallback = callback;
+                final FaceCallback faceCallback = new FaceCallback(callback);
                 mService.setFeature(mToken, userId, feature, enabled, hardwareAuthToken,
-                        mServiceReceiver, mContext.getOpPackageName());
+                        new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -544,8 +529,8 @@
     public void getFeature(int userId, int feature, GetFeatureCallback callback) {
         if (mService != null) {
             try {
-                mGetFeatureCallback = callback;
-                mService.getFeature(mToken, userId, feature, mServiceReceiver,
+                final FaceCallback faceCallback = new FaceCallback(callback);
+                mService.getFeature(mToken, userId, feature, new FaceServiceReceiver(faceCallback),
                         mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -566,10 +551,9 @@
     public void remove(Face face, int userId, RemovalCallback callback) {
         if (mService != null) {
             try {
-                mRemovalCallback = callback;
-                mRemovalFace = face;
-                mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver,
-                        mContext.getOpPackageName());
+                final FaceCallback faceCallback = new FaceCallback(callback, face);
+                mService.remove(mToken, face.getBiometricId(), userId,
+                        new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -584,8 +568,9 @@
     public void removeAll(int userId, @NonNull RemovalCallback callback) {
         if (mService != null) {
             try {
-                mRemovalCallback = callback;
-                mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+                final FaceCallback faceCallback = new FaceCallback(callback);
+                mService.removeAll(mToken, userId, new FaceServiceReceiver(faceCallback),
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1270,203 +1255,6 @@
         }
     }
 
-    private class MyHandler extends Handler {
-        private MyHandler(Context context) {
-            super(context.getMainLooper());
-        }
-
-        private MyHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(android.os.Message msg) {
-            Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
-            switch (msg.what) {
-                case MSG_ENROLL_RESULT:
-                    sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
-                    break;
-                case MSG_ACQUIRED:
-                    sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
-                    break;
-                case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
-                            msg.arg2 == 1 /* isStrongBiometric */);
-                    break;
-                case MSG_AUTHENTICATION_FAILED:
-                    sendAuthenticatedFailed();
-                    break;
-                case MSG_ERROR:
-                    sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
-                    break;
-                case MSG_REMOVED:
-                    sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
-                    break;
-                case MSG_SET_FEATURE_COMPLETED:
-                    sendSetFeatureCompleted((boolean) msg.obj /* success */,
-                            msg.arg1 /* feature */);
-                    break;
-                case MSG_GET_FEATURE_COMPLETED:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    sendGetFeatureCompleted((boolean) args.arg1 /* success */,
-                            (int[]) args.arg2 /* features */,
-                            (boolean[]) args.arg3 /* featureState */);
-                    args.recycle();
-                    break;
-                case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
-                            (long) msg.obj /* challenge */);
-                    break;
-                case MSG_FACE_DETECTED:
-                    sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
-                            (boolean) msg.obj /* isStrongBiometric */);
-                    break;
-                case MSG_AUTHENTICATION_FRAME:
-                    sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
-                    break;
-                case MSG_ENROLLMENT_FRAME:
-                    sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
-                    break;
-                default:
-                    Slog.w(TAG, "Unknown message: " + msg.what);
-            }
-            Trace.endSection();
-        }
-    }
-
-    private void sendSetFeatureCompleted(boolean success, int feature) {
-        if (mSetFeatureCallback == null) {
-            return;
-        }
-        mSetFeatureCallback.onCompleted(success, feature);
-    }
-
-    private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
-        if (mGetFeatureCallback == null) {
-            return;
-        }
-        mGetFeatureCallback.onCompleted(success, features, featureState);
-    }
-
-    private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
-        if (mGenerateChallengeCallback == null) {
-            return;
-        }
-        mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
-    }
-
-    private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-        if (mFaceDetectionCallback == null) {
-            Slog.e(TAG, "sendFaceDetected, callback null");
-            return;
-        }
-        mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
-    }
-
-    private void sendRemovedResult(Face face, int remaining) {
-        if (mRemovalCallback == null) {
-            return;
-        }
-        mRemovalCallback.onRemovalSucceeded(face, remaining);
-    }
-
-    private void sendErrorResult(int errMsgId, int vendorCode) {
-        // emulate HAL 2.1 behavior and send real errMsgId
-        final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
-                ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
-                    getErrorString(mContext, errMsgId, vendorCode));
-        } else if (mAuthenticationCallback != null) {
-            mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
-                    getErrorString(mContext, errMsgId, vendorCode));
-        } else if (mRemovalCallback != null) {
-            mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
-                    getErrorString(mContext, errMsgId, vendorCode));
-        } else if (mFaceDetectionCallback != null) {
-            mFaceDetectionCallback.onDetectionError(errMsgId);
-            mFaceDetectionCallback = null;
-        }
-    }
-
-    private void sendEnrollResult(Face face, int remaining) {
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentProgress(remaining);
-        }
-    }
-
-    private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
-        if (mAuthenticationCallback != null) {
-            final AuthenticationResult result =
-                    new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
-            mAuthenticationCallback.onAuthenticationSucceeded(result);
-        }
-    }
-
-    private void sendAuthenticatedFailed() {
-        if (mAuthenticationCallback != null) {
-            mAuthenticationCallback.onAuthenticationFailed();
-        }
-    }
-
-    private void sendAcquiredResult(int acquireInfo, int vendorCode) {
-        if (mAuthenticationCallback != null) {
-            final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
-                    new FaceDataFrame(acquireInfo, vendorCode));
-            sendAuthenticationFrame(frame);
-        } else if (mEnrollmentCallback != null) {
-            final FaceEnrollFrame frame = new FaceEnrollFrame(
-                    null /* cell */,
-                    FaceEnrollStages.UNKNOWN,
-                    new FaceDataFrame(acquireInfo, vendorCode));
-            sendEnrollmentFrame(frame);
-        }
-    }
-
-    private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
-        if (frame == null) {
-            Slog.w(TAG, "Received null authentication frame");
-        } else if (mAuthenticationCallback != null) {
-            // TODO(b/178414967): Send additional frame data to callback
-            final int acquireInfo = frame.getData().getAcquiredInfo();
-            final int vendorCode = frame.getData().getVendorCode();
-            final int helpCode = getHelpCode(acquireInfo, vendorCode);
-            final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
-            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
-
-            // Ensure that only non-null help messages are sent.
-            if (helpMessage != null) {
-                mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
-            }
-        }
-    }
-
-    private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
-        if (frame == null) {
-            Slog.w(TAG, "Received null enrollment frame");
-        } else if (mEnrollmentCallback != null) {
-            final FaceDataFrame data = frame.getData();
-            final int acquireInfo = data.getAcquiredInfo();
-            final int vendorCode = data.getVendorCode();
-            final int helpCode = getHelpCode(acquireInfo, vendorCode);
-            final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
-            mEnrollmentCallback.onEnrollmentFrame(
-                    helpCode,
-                    helpMessage,
-                    frame.getCell(),
-                    frame.getStage(),
-                    data.getPan(),
-                    data.getTilt(),
-                    data.getDistance());
-        }
-    }
-
-    private static int getHelpCode(int acquireInfo, int vendorCode) {
-        return acquireInfo == FACE_ACQUIRED_VENDOR
-                ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
-                : acquireInfo;
-    }
-
     /**
      * @hide
      */
diff --git a/core/java/android/hardware/fingerprint/FingerprintCallback.java b/core/java/android/hardware/fingerprint/FingerprintCallback.java
new file mode 100644
index 0000000..e4fbe6e
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintCallback.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2024 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.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR_BASE;
+import static android.hardware.fingerprint.FingerprintManager.getAcquiredString;
+import static android.hardware.fingerprint.FingerprintManager.getErrorString;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.hardware.fingerprint.FingerprintManager.FingerprintDetectionCallback;
+import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback;
+import android.hardware.fingerprint.FingerprintManager.RemovalCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each fingerprint related request.
+ * @hide
+ */
+public class FingerprintCallback {
+    private static final String TAG = "FingerprintCallback";
+    public static final int REMOVE_SINGLE = 1;
+    public static final int REMOVE_ALL = 2;
+    @IntDef({REMOVE_SINGLE, REMOVE_ALL})
+    public @interface RemoveRequest {}
+    @Nullable
+    private AuthenticationCallback mAuthenticationCallback;
+    @Nullable
+    private EnrollmentCallback mEnrollmentCallback;
+    @Nullable
+    private RemovalCallback mRemovalCallback;
+    @Nullable
+    private GenerateChallengeCallback mGenerateChallengeCallback;
+    @Nullable
+    private FingerprintDetectionCallback mFingerprintDetectionCallback;
+    @Nullable
+    private CryptoObject mCryptoObject;
+    @Nullable
+    private @RemoveRequest int mRemoveRequest;
+    @Nullable
+    private Fingerprint mRemoveFingerprint;
+
+    /**
+     * Construction for fingerprint authentication client callback.
+     */
+    FingerprintCallback(@NonNull AuthenticationCallback authenticationCallback,
+            @Nullable CryptoObject cryptoObject) {
+        mAuthenticationCallback = authenticationCallback;
+        mCryptoObject = cryptoObject;
+    }
+
+    /**
+     * Construction for fingerprint detect client callback.
+     */
+    FingerprintCallback(@NonNull FingerprintDetectionCallback fingerprintDetectionCallback) {
+        mFingerprintDetectionCallback = fingerprintDetectionCallback;
+    }
+
+    /**
+     * Construction for fingerprint enroll client callback.
+     */
+    FingerprintCallback(@NonNull EnrollmentCallback enrollmentCallback) {
+        mEnrollmentCallback = enrollmentCallback;
+    }
+
+    /**
+     * Construction for fingerprint generate challenge client callback.
+     */
+    FingerprintCallback(@NonNull GenerateChallengeCallback generateChallengeCallback) {
+        mGenerateChallengeCallback = generateChallengeCallback;
+    }
+
+    /**
+     * Construction for fingerprint removal client callback.
+     */
+    FingerprintCallback(@NonNull RemovalCallback removalCallback, @RemoveRequest int removeRequest,
+            @Nullable Fingerprint removeFingerprint) {
+        mRemovalCallback = removalCallback;
+        mRemoveRequest = removeRequest;
+        mRemoveFingerprint = removeFingerprint;
+    }
+
+    /**
+     * Propagate enroll progress via the callback.
+     * @param remaining number of enrollment steps remaining
+     */
+    public void sendEnrollResult(int remaining) {
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onEnrollmentProgress(remaining);
+        }
+    }
+
+    /**
+     * Propagate remove face completed via the callback.
+     * @param fingerprint removed identifier
+     * @param remaining number of face enrollments remaining
+     */
+    public void sendRemovedResult(@Nullable Fingerprint fingerprint, int remaining) {
+        if (mRemovalCallback == null) {
+            return;
+        }
+
+        if (mRemoveRequest == REMOVE_SINGLE) {
+            if (fingerprint == null) {
+                Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
+                return;
+            }
+
+            if (mRemoveFingerprint == null) {
+                Slog.e(TAG, "Missing fingerprint");
+                return;
+            }
+
+            final int fingerId = fingerprint.getBiometricId();
+            int reqFingerId = mRemoveFingerprint.getBiometricId();
+            if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
+                Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
+                return;
+            }
+        }
+
+        mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
+    }
+
+    /**
+     * Propagate authentication succeeded via the callback.
+     * @param fingerprint matched identifier
+     * @param userId id of the corresponding user
+     * @param isStrongBiometric if the sensor is strong or not
+     */
+    public void sendAuthenticatedSucceeded(@NonNull Fingerprint fingerprint, int userId,
+            boolean isStrongBiometric) {
+        if (mAuthenticationCallback == null) {
+            Slog.e(TAG, "Authentication succeeded but callback is null.");
+            return;
+        }
+
+        final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fingerprint,
+                userId, isStrongBiometric);
+        mAuthenticationCallback.onAuthenticationSucceeded(result);
+    }
+
+    /**
+     * Propagate authentication failed via the callback.
+     */
+    public void sendAuthenticatedFailed() {
+        if (mAuthenticationCallback != null) {
+            mAuthenticationCallback.onAuthenticationFailed();
+        }
+    }
+
+    /**
+     * Propagate acquired result via the callback.
+     * @param context corresponding context
+     * @param acquireInfo represents the framework acquired id
+     * @param vendorCode represents the vendor acquired code
+     */
+    public void sendAcquiredResult(@NonNull Context context, int acquireInfo, int vendorCode) {
+        if (mAuthenticationCallback != null) {
+            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+        }
+        if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
+            mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
+        }
+        final String msg = getAcquiredString(context, acquireInfo, vendorCode);
+        if (msg == null) {
+            return;
+        }
+        // emulate HAL 2.1 behavior and send real acquiredInfo
+        final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
+                ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+        } else if (mAuthenticationCallback != null) {
+            if (acquireInfo != FINGERPRINT_ACQUIRED_START) {
+                mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+            }
+        }
+    }
+
+    /**
+     * Propagate errors via the callback.
+     * @param context corresponding context
+     * @param errMsgId represents the framework error id
+     * @param vendorCode represents the vendor error code
+     */
+    public void sendErrorResult(@NonNull Context context, int errMsgId, int vendorCode) {
+        // emulate HAL 2.1 behavior and send real errMsgId
+        final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
+                ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+                    getErrorString(context, errMsgId, vendorCode));
+        } else if (mAuthenticationCallback != null) {
+            mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+                    getErrorString(context, errMsgId, vendorCode));
+        } else if (mRemovalCallback != null) {
+            mRemovalCallback.onRemovalError(mRemoveFingerprint, clientErrMsgId,
+                    getErrorString(context, errMsgId, vendorCode));
+        } else if (mFingerprintDetectionCallback != null) {
+            mFingerprintDetectionCallback.onDetectionError(errMsgId);
+            mFingerprintDetectionCallback = null;
+        }
+    }
+
+    /**
+     * Propagate challenge generated completed via the callback.
+     * @param sensorId id of the corresponding sensor
+     * @param userId id of the corresponding sensor
+     * @param challenge value of the challenge generated
+     */
+    public void sendChallengeGenerated(long challenge, int sensorId, int userId) {
+        if (mGenerateChallengeCallback == null) {
+            Slog.e(TAG, "sendChallengeGenerated, callback null");
+            return;
+        }
+        mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
+    }
+
+    /**
+     * Propagate fingerprint detected completed via the callback.
+     * @param sensorId id of the corresponding sensor
+     * @param userId id of the corresponding user
+     * @param isStrongBiometric if the sensor is strong or not
+     */
+    public void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+        if (mFingerprintDetectionCallback == null) {
+            Slog.e(TAG, "sendFingerprintDetected, callback null");
+            return;
+        }
+        mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
+    }
+
+    /**
+     * Propagate udfps pointer down via the callback.
+     * @param sensorId id of the corresponding sensor
+     */
+    public void sendUdfpsPointerDown(int sensorId) {
+        if (mAuthenticationCallback == null) {
+            Slog.e(TAG, "sendUdfpsPointerDown, callback null");
+        } else {
+            mAuthenticationCallback.onUdfpsPointerDown(sensorId);
+        }
+
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onUdfpsPointerDown(sensorId);
+        }
+    }
+
+    /**
+     * Propagate udfps pointer up via the callback.
+     * @param sensorId id of the corresponding sensor
+     */
+    public void sendUdfpsPointerUp(int sensorId) {
+        if (mAuthenticationCallback == null) {
+            Slog.e(TAG, "sendUdfpsPointerUp, callback null");
+        } else {
+            mAuthenticationCallback.onUdfpsPointerUp(sensorId);
+        }
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onUdfpsPointerUp(sensorId);
+        }
+    }
+
+    /**
+     * Propagate udfps overlay shown via the callback.
+     */
+    public void sendUdfpsOverlayShown() {
+        if (mEnrollmentCallback != null) {
+            mEnrollmentCallback.onUdfpsOverlayShown();
+        }
+    }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..37f2fb2 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -25,6 +25,8 @@
 import static android.Manifest.permission.USE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_ALL;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_SINGLE;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 
 import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
@@ -57,9 +59,9 @@
 import android.os.CancellationSignal;
 import android.os.CancellationSignal.OnCancelListener;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -94,19 +96,6 @@
 @RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
 public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
     private static final String TAG = "FingerprintManager";
-    private static final boolean DEBUG = true;
-    private static final int MSG_ENROLL_RESULT = 100;
-    private static final int MSG_ACQUIRED = 101;
-    private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
-    private static final int MSG_AUTHENTICATION_FAILED = 103;
-    private static final int MSG_ERROR = 104;
-    private static final int MSG_REMOVED = 105;
-    private static final int MSG_CHALLENGE_GENERATED = 106;
-    private static final int MSG_FINGERPRINT_DETECTED = 107;
-    private static final int MSG_UDFPS_POINTER_DOWN = 108;
-    private static final int MSG_UDFPS_POINTER_UP = 109;
-    private static final int MSG_POWER_BUTTON_PRESSED = 110;
-    private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
 
     /**
      * @hide
@@ -148,34 +137,14 @@
      */
     public static final int SENSOR_ID_ANY = -1;
 
-    private static class RemoveTracker {
-        static final int REMOVE_SINGLE = 1;
-        static final int REMOVE_ALL = 2;
-        @IntDef({REMOVE_SINGLE, REMOVE_ALL})
-        @interface RemoveRequest {}
+    private final IFingerprintService mService;
+    private final Context mContext;
+    private final IBinder mToken = new Binder();
 
-        final @RemoveRequest int mRemoveRequest;
-        @Nullable final Fingerprint mSingleFingerprint;
-
-        RemoveTracker(@RemoveRequest int request, @Nullable Fingerprint fingerprint) {
-            mRemoveRequest = request;
-            mSingleFingerprint = fingerprint;
-        }
-    }
-
-    private IFingerprintService mService;
-    private Context mContext;
-    private IBinder mToken = new Binder();
-    private AuthenticationCallback mAuthenticationCallback;
-    private FingerprintDetectionCallback mFingerprintDetectionCallback;
-    private EnrollmentCallback mEnrollmentCallback;
-    private RemovalCallback mRemovalCallback;
-    private GenerateChallengeCallback mGenerateChallengeCallback;
-    private CryptoObject mCryptoObject;
-    @Nullable private RemoveTracker mRemoveTracker;
     private Handler mHandler;
     @Nullable private float[] mEnrollStageThresholds;
     private List<FingerprintSensorPropertiesInternal> mProps = new ArrayList<>();
+    private HandlerExecutor mExecutor;
 
     /**
      * Retrieves a list of properties for all fingerprint sensors on the device.
@@ -395,7 +364,7 @@
      * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
      */
     @Deprecated
-    public static abstract class AuthenticationCallback
+    public abstract static class AuthenticationCallback
             extends BiometricAuthenticator.AuthenticationCallback {
         /**
          * Called when an unrecoverable error has been encountered and the operation is complete.
@@ -479,7 +448,7 @@
      *
      * @hide
      */
-    public static abstract class EnrollmentCallback {
+    public abstract static class EnrollmentCallback {
         /**
          * Called when an unrecoverable error has been encountered and the operation is complete.
          * No further callbacks will be made on this object.
@@ -536,7 +505,7 @@
      *
      * @hide
      */
-    public static abstract class RemovalCallback {
+    public abstract static class RemovalCallback {
         /**
          * Called when the given fingerprint can't be removed.
          * @param fp The fingerprint that the call attempted to remove
@@ -559,7 +528,7 @@
     /**
      * @hide
      */
-    public static abstract class LockoutResetCallback {
+    public abstract static class LockoutResetCallback {
 
         /**
          * Called when lockout period expired and clients are allowed to listen for fingerprint
@@ -584,9 +553,11 @@
      */
     private void useHandler(Handler handler) {
         if (handler != null) {
-            mHandler = new MyHandler(handler.getLooper());
-        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
-            mHandler = new MyHandler(mContext.getMainLooper());
+            mHandler = handler;
+            mExecutor = new HandlerExecutor(mHandler);
+        } else if (mHandler != mContext.getMainThreadHandler()) {
+            mHandler = mContext.getMainThreadHandler();
+            mExecutor = new HandlerExecutor(mHandler);
         }
     }
 
@@ -676,11 +647,12 @@
 
         if (mService != null) {
             try {
+                final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+                        crypto);
                 useHandler(handler);
-                mAuthenticationCallback = callback;
-                mCryptoObject = crypto;
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
-                final long authId = mService.authenticate(mToken, operationId, mServiceReceiver, options);
+                final long authId = mService.authenticate(mToken, operationId,
+                        new FingerprintServiceReceiver(fingerprintCallback), options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
                 }
@@ -715,10 +687,11 @@
         options.setOpPackageName(mContext.getOpPackageName());
         options.setAttributionTag(mContext.getAttributionTag());
 
-        mFingerprintDetectionCallback = callback;
+        final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
 
         try {
-            final long authId = mService.detectFingerprint(mToken, mServiceReceiver, options);
+            final long authId = mService.detectFingerprint(mToken,
+                    new FingerprintServiceReceiver(fingerprintCallback), options);
             cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
         } catch (RemoteException e) {
             Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -767,9 +740,10 @@
 
         if (mService != null) {
             try {
-                mEnrollmentCallback = callback;
+                final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
                 final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
-                        mServiceReceiver, mContext.getOpPackageName(), enrollReason, options);
+                        new FingerprintServiceReceiver(fingerprintCallback),
+                        mContext.getOpPackageName(), enrollReason, options);
                 if (cancel != null) {
                     cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
                 }
@@ -799,12 +773,13 @@
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
         if (mService != null) try {
-            mGenerateChallengeCallback = callback;
-            mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+                final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
+                mService.generateChallenge(mToken, sensorId, userId,
+                        new FingerprintServiceReceiver(fingerprintCallback),
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
     }
 
     /**
@@ -875,13 +850,14 @@
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
         if (mService != null) try {
-            mRemovalCallback = callback;
-            mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_SINGLE, fp);
-            mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+                final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+                        REMOVE_SINGLE, fp);
+                mService.remove(mToken, fp.getBiometricId(), userId,
+                        new FingerprintServiceReceiver(fingerprintCallback),
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
     }
 
     /**
@@ -892,9 +868,11 @@
     public void removeAll(int userId, @NonNull RemovalCallback callback) {
         if (mService != null) {
             try {
-                mRemovalCallback = callback;
-                mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_ALL, null /* fp */);
-                mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+                final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+                        REMOVE_ALL, null);
+                mService.removeAll(mToken, userId,
+                        new FingerprintServiceReceiver(fingerprintCallback),
+                        mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1162,7 +1140,7 @@
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public void onPowerPressed() {
         Slog.i(TAG, "onPowerPressed");
-        mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
+        mExecutor.execute(() -> sendPowerPressed());
     }
 
     /**
@@ -1346,199 +1324,6 @@
         }
     }
 
-    private class MyHandler extends Handler {
-        private MyHandler(Context context) {
-            super(context.getMainLooper());
-        }
-
-        private MyHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(android.os.Message msg) {
-            switch (msg.what) {
-                case MSG_ENROLL_RESULT:
-                    sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
-                    break;
-                case MSG_ACQUIRED:
-                    sendAcquiredResult(msg.arg1 /* acquire info */,
-                            msg.arg2 /* vendorCode */);
-                    break;
-                case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
-                            msg.arg2 == 1 /* isStrongBiometric */);
-                    break;
-                case MSG_AUTHENTICATION_FAILED:
-                    sendAuthenticatedFailed();
-                    break;
-                case MSG_ERROR:
-                    sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
-                    break;
-                case MSG_REMOVED:
-                    sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
-                    break;
-                case MSG_CHALLENGE_GENERATED:
-                    sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
-                            (long) msg.obj /* challenge */);
-                    break;
-                case MSG_FINGERPRINT_DETECTED:
-                    sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
-                            (boolean) msg.obj /* isStrongBiometric */);
-                    break;
-                case MSG_UDFPS_POINTER_DOWN:
-                    sendUdfpsPointerDown(msg.arg1 /* sensorId */);
-                    break;
-                case MSG_UDFPS_POINTER_UP:
-                    sendUdfpsPointerUp(msg.arg1 /* sensorId */);
-                    break;
-                case MSG_POWER_BUTTON_PRESSED:
-                    sendPowerPressed();
-                    break;
-                case MSG_UDFPS_OVERLAY_SHOWN:
-                    sendUdfpsOverlayShown();
-                default:
-                    Slog.w(TAG, "Unknown message: " + msg.what);
-
-            }
-        }
-    }
-
-    private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
-        if (mRemovalCallback == null) {
-            return;
-        }
-
-        if (mRemoveTracker == null) {
-            Slog.w(TAG, "Removal tracker is null");
-            return;
-        }
-
-        if (mRemoveTracker.mRemoveRequest == RemoveTracker.REMOVE_SINGLE) {
-            if (fingerprint == null) {
-                Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
-                return;
-            }
-
-            if (mRemoveTracker.mSingleFingerprint == null) {
-                Slog.e(TAG, "Missing fingerprint");
-                return;
-            }
-
-            final int fingerId = fingerprint.getBiometricId();
-            int reqFingerId = mRemoveTracker.mSingleFingerprint.getBiometricId();
-            if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
-                Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
-                return;
-            }
-        }
-
-        mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
-    }
-
-    private void sendEnrollResult(Fingerprint fp, int remaining) {
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentProgress(remaining);
-        }
-    }
-
-    private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
-        if (mAuthenticationCallback != null) {
-            final AuthenticationResult result =
-                    new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
-            mAuthenticationCallback.onAuthenticationSucceeded(result);
-        }
-    }
-
-    private void sendAuthenticatedFailed() {
-        if (mAuthenticationCallback != null) {
-            mAuthenticationCallback.onAuthenticationFailed();
-        }
-    }
-
-    private void sendAcquiredResult(int acquireInfo, int vendorCode) {
-        if (mAuthenticationCallback != null) {
-            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
-        }
-        if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
-            mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
-        }
-        final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
-        if (msg == null) {
-            return;
-        }
-        // emulate HAL 2.1 behavior and send real acquiredInfo
-        final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
-                ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
-        } else if (mAuthenticationCallback != null) {
-            if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) {
-                mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
-            }
-        }
-    }
-
-    private void sendErrorResult(int errMsgId, int vendorCode) {
-        // emulate HAL 2.1 behavior and send real errMsgId
-        final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
-                ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
-                    getErrorString(mContext, errMsgId, vendorCode));
-        } else if (mAuthenticationCallback != null) {
-            mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
-                    getErrorString(mContext, errMsgId, vendorCode));
-        } else if (mRemovalCallback != null) {
-            final Fingerprint fp = mRemoveTracker != null
-                    ? mRemoveTracker.mSingleFingerprint : null;
-            mRemovalCallback.onRemovalError(fp, clientErrMsgId,
-                    getErrorString(mContext, errMsgId, vendorCode));
-        } else if (mFingerprintDetectionCallback != null) {
-            mFingerprintDetectionCallback.onDetectionError(errMsgId);
-            mFingerprintDetectionCallback = null;
-        }
-    }
-
-    private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
-        if (mGenerateChallengeCallback == null) {
-            Slog.e(TAG, "sendChallengeGenerated, callback null");
-            return;
-        }
-        mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
-    }
-
-    private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
-        if (mFingerprintDetectionCallback == null) {
-            Slog.e(TAG, "sendFingerprintDetected, callback null");
-            return;
-        }
-        mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
-    }
-
-    private void sendUdfpsPointerDown(int sensorId) {
-        if (mAuthenticationCallback == null) {
-            Slog.e(TAG, "sendUdfpsPointerDown, callback null");
-        } else {
-            mAuthenticationCallback.onUdfpsPointerDown(sensorId);
-        }
-
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onUdfpsPointerDown(sensorId);
-        }
-    }
-
-    private void sendUdfpsPointerUp(int sensorId) {
-        if (mAuthenticationCallback == null) {
-            Slog.e(TAG, "sendUdfpsPointerUp, callback null");
-        } else {
-            mAuthenticationCallback.onUdfpsPointerUp(sensorId);
-        }
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onUdfpsPointerUp(sensorId);
-        }
-    }
-
     private void sendPowerPressed() {
         try {
             mService.onPowerPressed();
@@ -1547,12 +1332,6 @@
         }
     }
 
-    private void sendUdfpsOverlayShown() {
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onUdfpsOverlayShown();
-        }
-    }
-
     /**
      * @hide
      */
@@ -1562,7 +1341,6 @@
         if (mService == null) {
             Slog.v(TAG, "FingerprintService was null");
         }
-        mHandler = new MyHandler(context);
         if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
                 == PackageManager.PERMISSION_GRANTED) {
             addAuthenticatorsRegisteredCallback(
@@ -1574,6 +1352,8 @@
                         }
                     });
         }
+        mHandler = context.getMainThreadHandler();
+        mExecutor = new HandlerExecutor(mHandler);
     }
 
     private int getCurrentUserId() {
@@ -1773,66 +1553,72 @@
         return null;
     }
 
-    private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
+    class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub {
+        private final FingerprintCallback mFingerprintCallback;
+
+        FingerprintServiceReceiver(FingerprintCallback fingerprintCallback) {
+            mFingerprintCallback = fingerprintCallback;
+        }
 
         @Override // binder call
         public void onEnrollResult(Fingerprint fp, int remaining) {
-            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, fp).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendEnrollResult(remaining));
         }
 
         @Override // binder call
         public void onAcquired(int acquireInfo, int vendorCode) {
-            mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendAcquiredResult(mContext, acquireInfo,
+                    vendorCode));
         }
 
         @Override // binder call
         public void onAuthenticationSucceeded(Fingerprint fp, int userId,
                 boolean isStrongBiometric) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
-                    fp).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendAuthenticatedSucceeded(fp, userId,
+                    isStrongBiometric));
         }
 
         @Override
         public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
-            mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, sensorId, userId, isStrongBiometric)
-                    .sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendFingerprintDetected(sensorId, userId,
+                    isStrongBiometric));
         }
 
         @Override // binder call
         public void onAuthenticationFailed() {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+            mExecutor.execute(mFingerprintCallback::sendAuthenticatedFailed);
         }
 
         @Override // binder call
         public void onError(int error, int vendorCode) {
-            mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendErrorResult(mContext, error,
+                    vendorCode));
         }
 
         @Override // binder call
         public void onRemoved(Fingerprint fp, int remaining) {
-            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, fp).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendRemovedResult(fp, remaining));
         }
 
         @Override // binder call
         public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-            mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
-                    .sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendChallengeGenerated(challenge, sensorId,
+                    userId));
         }
 
         @Override // binder call
         public void onUdfpsPointerDown(int sensorId) {
-            mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerDown(sensorId));
         }
 
         @Override // binder call
         public void onUdfpsPointerUp(int sensorId) {
-            mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
+            mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerUp(sensorId));
         }
 
         @Override
         public void onUdfpsOverlayShown() {
-            mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+            mExecutor.execute(mFingerprintCallback::sendUdfpsOverlayShown);
         }
-    };
-
+    }
 }
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 4ae0a57..3b59901 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -98,6 +98,8 @@
 
     IBinder registerAttributionSource(in AttributionSourceState source);
 
+    int getNumRegisteredAttributionSources(int uid);
+
     boolean isRegisteredAttributionSource(in AttributionSourceState source);
 
     int checkPermission(String packageName, String permissionName, String persistentDeviceId,
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fe3fa8c..2daf4ac 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1675,6 +1675,21 @@
     }
 
     /**
+     * Gets the number of currently registered attribution sources for a particular UID. This should
+     * only be used for testing purposes.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.UPDATE_APP_OPS_STATS)
+    public int getNumRegisteredAttributionSourcesForTest(int uid) {
+        try {
+            return mPermissionManager.getNumRegisteredAttributionSources(uid);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return -1;
+    }
+
+    /**
      * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
      * USED in CTS or local tests.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dd93972..6ad7422 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9020,6 +9020,16 @@
                 "accessibility_display_daltonizer";
 
         /**
+         * Integer property that determines the saturation level of color correction. Default value
+         * is defined in Settings config.xml.
+         * [0-10] inclusive where 0 would look as if color space adustment is not applied at all.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL =
+                "accessibility_display_daltonizer_saturation_level";
+
+        /**
          * Setting that specifies whether automatic click when the mouse pointer stops moving is
          * enabled.
          *
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index 1a6cf88..ddb662ad 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -28,6 +28,8 @@
     boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);
 
     IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
+
+    @EnforcePermission("SETUP_FSVERITY")
     int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
             in String packageName);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 40366c2..8d55777 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -89,6 +89,7 @@
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
@@ -110,12 +111,12 @@
 import static android.view.accessibility.Flags.forceInvertColor;
 import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
 import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
 import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 
@@ -1441,6 +1442,7 @@
                 // Keep track of the actual window flags supplied by the client.
                 mClientWindowLayoutFlags = attrs.flags;
 
+                adjustLayoutInDisplayCutoutMode(attrs);
                 setAccessibilityFocus(null, null);
 
                 if (view instanceof RootViewSurfaceTaker) {
@@ -2043,6 +2045,9 @@
             final int appearance = mWindowAttributes.insetsFlags.appearance;
             final int behavior = mWindowAttributes.insetsFlags.behavior;
 
+            // Calling this before copying prevents redundant LAYOUT_CHANGED.
+            final int layoutInDisplayCutoutModeFromCaller = adjustLayoutInDisplayCutoutMode(attrs);
+
             final int changes = mWindowAttributes.copyFrom(attrs);
             if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
                 // Recompute system ui visibility.
@@ -2059,6 +2064,9 @@
                 mWindowAttributes.packageName = mBasePackageName;
             }
 
+            // Restore the layoutInDisplayCutoutMode of the caller;
+            attrs.layoutInDisplayCutoutMode = layoutInDisplayCutoutModeFromCaller;
+
             // Restore preserved flags.
             mWindowAttributes.systemUiVisibility = systemUiVisibility;
             mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
@@ -2101,6 +2109,19 @@
         }
     }
 
+    private int adjustLayoutInDisplayCutoutMode(WindowManager.LayoutParams attrs) {
+        final int originalMode = attrs.layoutInDisplayCutoutMode;
+        if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+                && attrs.isFullscreen()
+                && attrs.getFitInsetsTypes() == 0
+                && attrs.getFitInsetsSides() == 0) {
+            if (originalMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+                attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            }
+        }
+        return originalMode;
+    }
+
     void handleAppVisibility(boolean visible) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 12ce0f4..bdfc236 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -27,6 +27,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
 import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -1190,6 +1191,8 @@
     /**
      * {@inheritDoc}
      */
+    @Override
+    @RequiresNoPermission
     public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
                 int interactionId) {
         synchronized (mInstanceLock) {
@@ -1231,6 +1234,8 @@
     /**
      * {@inheritDoc}
      */
+    @Override
+    @RequiresNoPermission
     public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
                 int interactionId) {
         synchronized (mInstanceLock) {
@@ -1260,6 +1265,7 @@
      * {@inheritDoc}
      */
     @Override
+    @RequiresNoPermission
     public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
                                                        int interactionId) {
         int interactionIdWaitingForPrefetchResultCopy = -1;
@@ -1324,6 +1330,8 @@
     /**
      * {@inheritDoc}
      */
+    @Override
+    @RequiresNoPermission
     public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
         synchronized (mInstanceLock) {
             if (interactionId > mInteractionId) {
@@ -1372,6 +1380,7 @@
      * @param interactionId The interaction id of the request.
      */
     @Override
+    @RequiresNoPermission
     public void sendTakeScreenshotOfWindowError(
             @AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
         synchronized (mInstanceLock) {
@@ -1729,6 +1738,7 @@
      * @param interactionId The interaction id of the request.
      */
     @Override
+    @RequiresNoPermission
     public void sendAttachOverlayResult(
             @AccessibilityService.AttachOverlayResult int result, int interactionId) {
         if (!Flags.a11yOverlayCallbacks()) {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index fe59519..a9e5db5 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -34,6 +34,7 @@
      * @param interactionId The interaction id to match the result with the request.
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @RequiresNoPermission
     void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
 
     /**
@@ -43,6 +44,7 @@
      * @param interactionId The interaction id to match the result with the request.
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @RequiresNoPermission
     void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
         int interactionId);
 
@@ -52,6 +54,7 @@
      * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
      * @param infos The result {@link AccessibilityNodeInfo}s.
      */
+     @RequiresNoPermission
     void setPrefetchAccessibilityNodeInfoResult(
         in List<AccessibilityNodeInfo> infos, int interactionId);
 
@@ -62,15 +65,18 @@
      * @param interactionId The interaction id to match the result with the request.
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @RequiresNoPermission
     void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
 
     /**
     * Sends an error code for a window screenshot request to the requesting client.
     */
+    @RequiresNoPermission
     void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
 
     /**
     * Sends an result code for an attach overlay request to the requesting client.
     */
+    @RequiresNoPermission
     void sendAttachOverlayResult(int result, int interactionId);
 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 1841353..52487fb 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2486,9 +2486,6 @@
             setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
             params.setFitInsetsSides(0);
             params.setFitInsetsTypes(0);
-            if (mEdgeToEdgeEnforced) {
-                params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-            }
         }
 
         if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 773823d..80a7599 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -82,6 +82,14 @@
         "android_util_StringBlock.cpp",
         "android_util_XmlBlock.cpp",
         "android_util_jar_StrictJarFile.cpp",
+        "android_view_InputDevice.cpp",
+        "android_view_KeyCharacterMap.cpp",
+        "android_view_KeyEvent.cpp",
+        "android_view_MotionEvent.cpp",
+        "android_view_Surface.cpp",
+        "android_view_VelocityTracker.cpp",
+        "android_view_VerifiedKeyEvent.cpp",
+        "android_view_VerifiedMotionEvent.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "core_jni_helpers.cpp",
         ":deviceproductinfoconstants_aidl",
@@ -158,16 +166,11 @@
                 "android_view_CompositionSamplingListener.cpp",
                 "android_view_DisplayEventReceiver.cpp",
                 "android_view_InputChannel.cpp",
-                "android_view_InputDevice.cpp",
                 "android_view_InputEventReceiver.cpp",
                 "android_view_InputEventSender.cpp",
                 "android_view_InputQueue.cpp",
-                "android_view_KeyCharacterMap.cpp",
-                "android_view_KeyEvent.cpp",
-                "android_view_MotionEvent.cpp",
                 "android_view_MotionPredictor.cpp",
                 "android_view_PointerIcon.cpp",
-                "android_view_Surface.cpp",
                 "android_view_SurfaceControl.cpp",
                 "android_view_SurfaceControlHdrLayerInfoListener.cpp",
                 "android_view_WindowManagerGlobal.cpp",
@@ -175,9 +178,6 @@
                 "android_view_SurfaceSession.cpp",
                 "android_view_TextureView.cpp",
                 "android_view_TunnelModeEnabledListener.cpp",
-                "android_view_VelocityTracker.cpp",
-                "android_view_VerifiedKeyEvent.cpp",
-                "android_view_VerifiedMotionEvent.cpp",
                 "android_text_Hyphenator.cpp",
                 "android_os_Debug.cpp",
                 "android_os_GraphicsEnvironment.cpp",
@@ -394,7 +394,8 @@
                 "-Wno-unused-function",
             ],
             srcs: [
-                "LayoutlibLoader.cpp",
+                "platform/host/HostRuntime.cpp",
+                "platform/host/native_window_jni.cpp",
             ],
             include_dirs: [
                 "external/vulkan-headers/include",
@@ -414,6 +415,7 @@
                 "libhostgraphics",
                 "libhwui",
                 "libimage_type_recognition",
+                "libinput",
                 "libjpeg",
                 "libpiex",
                 "libpng",
@@ -441,16 +443,9 @@
                 "android_os_MessageQueue.cpp",
                 "android_os_Parcel.cpp",
 
-                "android_view_KeyCharacterMap.cpp",
-                "android_view_KeyEvent.cpp",
                 "android_view_InputChannel.cpp",
-                "android_view_InputDevice.cpp",
                 "android_view_InputEventReceiver.cpp",
                 "android_view_InputEventSender.cpp",
-                "android_view_MotionEvent.cpp",
-                "android_view_VelocityTracker.cpp",
-                "android_view_VerifiedKeyEvent.cpp",
-                "android_view_VerifiedMotionEvent.cpp",
 
                 "android_util_AssetManager.cpp",
                 "android_util_Binder.cpp",
@@ -458,7 +453,6 @@
                 "android_util_FileObserver.cpp",
             ],
             static_libs: [
-                "libinput",
                 "libbinderthreadstateutils",
                 "libsqlite",
                 "libgui_window_info_static",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b801a69..3e3af40 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -28,6 +28,8 @@
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 
+#include <sstream>
+
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
@@ -598,8 +600,8 @@
 
 // ----------------- @CriticalNative ------------------------------
 
-static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
-        jboolean keepHistory) {
+static jlong android_view_MotionEvent_nativeCopy(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+                                                 jlong sourceNativePtr, jboolean keepHistory) {
     MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
     if (!destEvent) {
         destEvent = new MotionEvent();
@@ -609,8 +611,8 @@
     return reinterpret_cast<jlong>(destEvent);
 }
 
-static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
-                                                  jint idBits) {
+static jlong android_view_MotionEvent_nativeSplit(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+                                                  jlong sourceNativePtr, jint idBits) {
     MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
     if (!destEvent) {
         destEvent = new MotionEvent();
@@ -621,168 +623,192 @@
     return reinterpret_cast<jlong>(destEvent);
 }
 
-static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getId();
 }
 
-static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDeviceId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDeviceId();
 }
 
-static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getSource();
 }
 
-static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                     jint source) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setSource(source);
 }
 
-static jint android_view_MotionEvent_nativeGetDisplayId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDisplayId();
 }
 
-static void android_view_MotionEvent_nativeSetDisplayId(jlong nativePtr, jint displayId) {
+static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                        jint displayId) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->setDisplayId(displayId);
 }
 
-static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getAction();
 }
 
-static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+static void android_view_MotionEvent_nativeSetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                     jint action) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setAction(action);
 }
 
-static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+static int android_view_MotionEvent_nativeGetActionButton(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getActionButton();
 }
 
-static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+static void android_view_MotionEvent_nativeSetActionButton(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint button) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setActionButton(button);
 }
 
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->isTouchEvent();
 }
 
-static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getFlags();
 }
 
-static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                    jint flags) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setFlags(flags);
 }
 
-static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getEdgeFlags();
 }
 
-static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+static void android_view_MotionEvent_nativeSetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                        jint edgeFlags) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setEdgeFlags(edgeFlags);
 }
 
-static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetMetaState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getMetaState();
 }
 
-static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetButtonState(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getButtonState();
 }
 
-static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+static void android_view_MotionEvent_nativeSetButtonState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                          jint buttonState) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setButtonState(buttonState);
 }
 
-static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetClassification(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return static_cast<jint>(event->getClassification());
 }
 
-static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
-        jfloat deltaY) {
+static void android_view_MotionEvent_nativeOffsetLocation(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                          jfloat deltaX, jfloat deltaY) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->offsetLocation(deltaX, deltaY);
 }
 
-static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getRawXOffset();
 }
 
-static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getRawYOffset();
 }
 
-static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXPrecision(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getXPrecision();
 }
 
-static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYPrecision(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getYPrecision();
 }
 
-static jfloat android_view_MotionEvent_nativeGetXCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXCursorPosition(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getXCursorPosition();
 }
 
-static jfloat android_view_MotionEvent_nativeGetYCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYCursorPosition(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getYCursorPosition();
 }
 
-static void android_view_MotionEvent_nativeSetCursorPosition(jlong nativePtr, jfloat x, jfloat y) {
+static void android_view_MotionEvent_nativeSetCursorPosition(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat x, jfloat y) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setCursorPosition(x, y);
 }
 
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return event->getDownTime();
 }
 
-static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+static void android_view_MotionEvent_nativeSetDownTimeNanos(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong downTimeNanos) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->setDownTime(downTimeNanos);
 }
 
-static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetPointerCount(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return jint(event->getPointerCount());
 }
 
-static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+static jint android_view_MotionEvent_nativeFindPointerIndex(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pointerId) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return jint(event->findPointerIndex(pointerId));
 }
 
-static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetHistorySize(
+        CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     return jint(event->getHistorySize());
 }
 
-static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+static void android_view_MotionEvent_nativeScale(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+                                                 jfloat scale) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
     event->scale(scale);
 }
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 869b53d..ac6298d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,15 +27,19 @@
 #include <android_runtime/android_graphics_SurfaceTexture.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/Log.h>
+#ifdef __ANDROID__
 #include <private/android/AHardwareBufferHelpers.h>
 
 #include "android_os_Parcel.h"
 #include <binder/Parcel.h>
 
 #include <gui/BLASTBufferQueue.h>
+#endif
 #include <gui/Surface.h>
+#ifdef __ANDROID__
 #include <gui/SurfaceControl.h>
 #include <gui/view/Surface.h>
+#endif
 
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
@@ -67,6 +71,7 @@
     jfieldID bottom;
 } gRectClassInfo;
 
+#ifdef __ANDROID__
 class JNamedColorSpace {
 public:
     // ColorSpace.Named.SRGB.ordinal() = 0;
@@ -84,6 +89,7 @@
             return ui::Dataspace::V0_SRGB;
     }
 }
+#endif
 
 // ----------------------------------------------------------------------------
 
@@ -144,6 +150,7 @@
 
 // ----------------------------------------------------------------------------
 
+#ifdef __ANDROID__
 static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
         jobject surfaceTextureObj) {
     sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
@@ -162,6 +169,7 @@
     surface->incStrong(&sRefBaseOwner);
     return jlong(surface.get());
 }
+#endif
 
 static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
     sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
@@ -269,7 +277,7 @@
 }
 
 // ----------------------------------------------------------------------------
-
+#ifdef __ANDROID__
 static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
         jlong surfaceControlNativeObj) {
     sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
@@ -380,6 +388,7 @@
     // to the Parcel
     surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
 }
+#endif
 
 static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
     Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -412,6 +421,7 @@
     return surface->disconnect(-1, IGraphicBufferProducer::DisconnectMode::AllLocal);
 }
 
+#ifdef __ANDROID__
 static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz, jlong nativeObject,
                                                      jobject hardwareBuffer, jint colorSpaceId) {
     Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -422,6 +432,7 @@
             fromNamedColorSpaceValueToDataspace(colorSpaceId));
     return err;
 }
+#endif
 
 static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject,
         jboolean enabled) {
@@ -457,8 +468,10 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gSurfaceMethods[] = {
+#ifdef __ANDROID__
         {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
          (void*)nativeCreateFromSurfaceTexture},
+#endif
         {"nativeRelease", "(J)V", (void*)nativeRelease},
         {"nativeIsValid", "(J)Z", (void*)nativeIsValid},
         {"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
@@ -467,21 +480,27 @@
         {"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
          (void*)nativeUnlockCanvasAndPost},
         {"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+#ifdef __ANDROID__
         {"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
         {"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
         {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
         {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
         {"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
         {"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
         {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
         {"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
         {"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+#ifdef __ANDROID__
         {"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
          (void*)nativeAttachAndQueueBufferWithColorSpace},
+#endif
         {"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
         {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
         {"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
+#ifdef __ANDROID__
         {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+#endif
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
 };
 
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 637b823..23cedb8 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -19,6 +19,7 @@
 
 #include <android/native_window.h>
 #include <ui/PublicFormat.h>
+#include <utils/StrongPointer.h>
 
 #include "jni.h"
 
diff --git a/core/jni/platform/OWNERS b/core/jni/platform/OWNERS
new file mode 100644
index 0000000..10ce5cf
--- /dev/null
+++ b/core/jni/platform/OWNERS
@@ -0,0 +1,4 @@
+include /graphics/java/android/graphics/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/platform/host/HostRuntime.cpp
similarity index 94%
rename from core/jni/LayoutlibLoader.cpp
rename to core/jni/platform/host/HostRuntime.cpp
index 83b6afa..0433855 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -80,7 +80,7 @@
 
 namespace android {
 
-extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
+extern int register_android_animation_PropertyValuesHolder(JNIEnv* env);
 extern int register_android_content_AssetManager(JNIEnv* env);
 extern int register_android_content_StringBlock(JNIEnv* env);
 extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -106,15 +106,17 @@
 extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
-extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv* env);
 
-#define REG_JNI(name)      { name }
+#define REG_JNI(name) \
+    { name }
 struct RegJNIRec {
     int (*mProc)(JNIEnv*);
 };
 
-// Map of all possible class names to register to their corresponding JNI registration function pointer
-// The actual list of registered classes will be determined at runtime via the 'native_classes' System property
+// Map of all possible class names to register to their corresponding JNI registration function
+// pointer The actual list of registered classes will be determined at runtime via the
+// 'native_classes' System property
 static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
         {"android.animation.PropertyValuesHolder",
          REG_JNI(register_android_animation_PropertyValuesHolder)},
@@ -154,8 +156,7 @@
 };
 
 static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
-        const vector<string>& classesToRegister, JNIEnv* env) {
-
+                              const vector<string>& classesToRegister, JNIEnv* env) {
     for (const string& className : classesToRegister) {
         if (jniRegMap.at(className).mProc(env) < 0) {
             return -1;
@@ -169,15 +170,14 @@
     return 0;
 }
 
-int AndroidRuntime::registerNativeMethods(JNIEnv* env,
-        const char* className, const JNINativeMethod* gMethods, int numMethods) {
+int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+                                          const JNINativeMethod* gMethods, int numMethods) {
     return jniRegisterNativeMethods(env, className, gMethods, numMethods);
 }
 
 JNIEnv* AndroidRuntime::getJNIEnv() {
     JNIEnv* env;
-    if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK)
-        return nullptr;
+    if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
     return env;
 }
 
@@ -186,11 +186,10 @@
 }
 
 static vector<string> parseCsv(const string& csvString) {
-    vector<string>   result;
+    vector<string> result;
     istringstream stream(csvString);
     string segment;
-    while(getline(stream, segment, ','))
-    {
+    while (getline(stream, segment, ',')) {
         result.push_back(segment);
     }
     return result;
@@ -274,7 +273,9 @@
     }
 
     struct CloseHandleWrapper {
-        void operator()(HANDLE h) { CloseHandle(h); }
+        void operator()(HANDLE h) {
+            CloseHandle(h);
+        }
     };
     std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
             CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
@@ -384,8 +385,9 @@
     // Configuration is stored as java System properties.
     // Get a reference to System.getProperty
     jclass system = FindClassOrDie(env, "java/lang/System");
-    jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
-                                                         "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+    jmethodID getPropertyMethod =
+            GetStaticMethodIDOrDie(env, system, "getProperty",
+                                   "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
 
     // Java system properties that contain LayoutLib config. The initial values in the map
     // are the default values if the property is not specified.
diff --git a/core/jni/platform/host/native_window_jni.cpp b/core/jni/platform/host/native_window_jni.cpp
new file mode 100644
index 0000000..c17c480
--- /dev/null
+++ b/core/jni/platform/host/native_window_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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 <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android_runtime/android_view_Surface.h>
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+using namespace android;
+
+ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
+    sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
+    if (win != NULL) {
+        ANativeWindow_acquire(win.get());
+    }
+    return win.get();
+}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5ae365c..f5bbbb4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -103,6 +103,7 @@
         optional SettingProto accessibility_pinch_to_zoom_anywhere_enabled = 55 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_single_finger_panning_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_floating_menu_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     }
     optional Accessibility accessibility = 2;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b262ebd..0df49dc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2300,7 +2300,7 @@
              ensure that the status bar has enough contrast with the contents of this app, and set
              an appropriate effective bar background accordingly.
              See: {@link android.R.attr#enforceStatusBarContrast}
-             <p>If the app targets
+             <p>If the window belongs to an app targeting
              {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
              this attribute is ignored.
              @deprecated Draw proper background behind
@@ -2320,7 +2320,7 @@
              ensure that the navigation bar has enough contrast with the contents of this app, and
              set an appropriate effective bar background accordingly.
              See: {@link android.R.attr#enforceNavigationBarContrast}
-             <p>If the app targets
+             <p>If the window belongs to an app targeting
              {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
              this attribute is ignored.
              @deprecated Draw proper background behind
@@ -2335,7 +2335,7 @@
              have been requested to be translucent with
              {@link android.R.attr#windowTranslucentNavigation}.
              Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}.
-             <p>If the app targets
+             <p>If the window belongs to an app targeting
              {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
              this attribute is ignored.
              @deprecated Draw proper background behind
@@ -2431,7 +2431,9 @@
 
         <!-- Controls how the window is laid out if there is a {@code DisplayCutout}.
         <p>
-        Defaults to {@code default}.
+        Defaults to {@code default}. But if the window fills the screen, and it belongs to an app
+        targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or
+        above, the behavior will be the same as specifying {@code always} regardless.
         <p>
         See also
         {@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode
@@ -2528,8 +2530,8 @@
 
         <!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
 
-             <p>If this is false, the edge-to-edge enforcement will be applied to the window if its
-             app targets
+             <p>If this is false, the edge-to-edge enforcement will be applied to the window if it
+             belongs to an app targeting
              {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above.
              The affected behaviors are:
              <ul>
@@ -2537,9 +2539,8 @@
                  through the {@link android.view.WindowInsets} to the content view, as if calling
                  {@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false.
                  <li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of
-                 the non-floating windows will be set to {@link
+                 the fill-screen windows will behave as specifying {@link
                  android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}.
-                 Changing it to other values will cause {@link lang.IllegalArgumentException}.
                  <li>The framework will set {@link android.R.attr#statusBarColor},
                  {@link android.R.attr#navigationBarColor}, and
                  {@link android.R.attr#navigationBarDividerColor} to transparent.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a622d36..1a61870 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3009,6 +3009,10 @@
     <!-- Maximum number of users we allow to be running at a time -->
     <integer name="config_multiuserMaxRunningUsers">3</integer>
 
+    <!-- Number of seconds of uptime after a full user enters the background before we attempt to
+         stop it due to inactivity. Set to -1 to disable scheduling stopping background users. -->
+    <integer name="config_backgroundUserScheduledStopTimeSecs">1800</integer> <!-- 30 minutes -->
+
     <!-- Whether to delay user data locking for background user.
          If false, user switched-out from user switching will still be in running state until
          config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 7c9f2ef..a248ede 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -235,6 +235,12 @@
     <integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
     <java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
 
+    <!-- Reevaluate existing data networks for bootstrap sim data usage at mentioned intervals
+         during esim bootstrap activation. If the value set is 0 or -1, default interval of
+         60000 millis will be set. -->
+    <integer name="config_reevaluate_bootstrap_sim_data_usage_millis">60000</integer>
+    <java-symbol type="integer" name="config_reevaluate_bootstrap_sim_data_usage_millis" />
+
     <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
          to identify providers that should be ignored if the carrier config
          carrier_supported_satellite_services_per_provider_bundle does not support them.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 461137c..4c941fd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6457,16 +6457,4 @@
     <string name="satellite_notification_how_it_works">How it works</string>
     <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
     <string name="unarchival_session_app_label">Pending...</string>
-
-    <!-- Background user sound notification related messages -->
-    <!-- Notification title when sound comes from a call on background user [CHAR LIMIT=NOTIF_TITLE]-->
-    <string name="bg_user_sound_notification_title_call">Call for <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
-    <!-- Notification title when sound comes from an alarm or timer on background user [CHAR LIMIT=NOTIF_TITLE]-->
-    <string name="bg_user_sound_notification_title_alarm">Alarm for <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
-    <!-- Notification action button to prompt user switch to the background user [CHAR LIMIT=NONE] -->
-    <string name="bg_user_sound_notification_button_switch_user">Switch user</string>
-    <!-- Notification action button to mute the sound from the background user [CHAR LIMIT=NONE] -->
-    <string name="bg_user_sound_notification_button_mute">Mute</string>
-    <!-- Notification content action to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]-->
-    <string name="bg_user_sound_notification_message">Tap to mute sound</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 91d7ce0..ead5827 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -488,6 +488,7 @@
   <java-symbol type="integer" name="config_lockSoundVolumeDb" />
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
+  <java-symbol type="integer" name="config_backgroundUserScheduledStopTimeSecs" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
   <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
@@ -5380,13 +5381,6 @@
   <java-symbol type="string" name="satellite_notification_how_it_works" />
   <java-symbol type="drawable" name="ic_satellite_alt_24px" />
 
-  <!-- System notification for background user sound -->
-  <java-symbol type="string" name="bg_user_sound_notification_title_call" />
-  <java-symbol type="string" name="bg_user_sound_notification_title_alarm" />
-  <java-symbol type="string" name="bg_user_sound_notification_button_switch_user" />
-  <java-symbol type="string" name="bg_user_sound_notification_button_mute" />
-  <java-symbol type="string" name="bg_user_sound_notification_message" />
-
   <!-- DisplayManager configs. -->
   <java-symbol type="bool" name="config_evenDimmerEnabled" />
 
diff --git a/core/tests/FileSystemUtilsTest/TEST_MAPPING b/core/tests/FileSystemUtilsTest/TEST_MAPPING
index 89b3a7a..d41e981 100644
--- a/core/tests/FileSystemUtilsTest/TEST_MAPPING
+++ b/core/tests/FileSystemUtilsTest/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FileSystemUtilsTests"
     }
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d930e4d..3c042ba 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -78,6 +78,6 @@
     public void testRestrictionLevel() throws Exception {
         // For the moment mostly want to confirm we don't crash
         assertNotNull(ActivityManager.restrictionLevelToName(
-                ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+                ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED));
     }
 }
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 3a872b5..5bf88da 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -28,7 +28,9 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -97,7 +99,7 @@
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
 
-        when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+        when(mContext.getMainThreadHandler()).thenReturn(mHandler);
         when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
         when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
         when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -210,6 +212,39 @@
         verify(mFaceDetectionCallback).onDetectionError(anyInt());
     }
 
+    @Test
+    public void authenticate_onErrorCanceled() throws RemoteException {
+        final FaceManager.AuthenticationCallback authenticationCallback1 = mock(
+                FaceManager.AuthenticationCallback.class);
+        final FaceManager.AuthenticationCallback authenticationCallback2 = mock(
+                FaceManager.AuthenticationCallback.class);
+
+        final ArgumentCaptor<IFaceServiceReceiver> faceServiceReceiverArgumentCaptor =
+                ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+        mFaceManager.authenticate(null, new CancellationSignal(),
+                authenticationCallback1, mHandler,
+                new FaceAuthenticateOptions.Builder().build());
+        mFaceManager.authenticate(null, new CancellationSignal(),
+                authenticationCallback2, mHandler,
+                new FaceAuthenticateOptions.Builder().build());
+
+        verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+                faceServiceReceiverArgumentCaptor.capture(), any());
+
+        final List<IFaceServiceReceiver> faceServiceReceivers =
+                faceServiceReceiverArgumentCaptor.getAllValues();
+        faceServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+        verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+        faceServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+        verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+    }
+
     private void initializeProperties() throws RemoteException {
         verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
 
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index ce7d6a9..c3ea7d3 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -26,7 +26,9 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -93,6 +95,7 @@
         mHandler = new Handler(mLooper.getLooper());
 
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+        when(mContext.getMainThreadHandler()).thenReturn(mHandler);
         when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
         when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
         when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -187,4 +190,38 @@
 
         verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
     }
+
+    @Test
+    public void authenticate_onErrorCanceled() throws RemoteException {
+        final FingerprintManager.AuthenticationCallback authenticationCallback1 = mock(
+                FingerprintManager.AuthenticationCallback.class);
+        final FingerprintManager.AuthenticationCallback authenticationCallback2 = mock(
+                FingerprintManager.AuthenticationCallback.class);
+
+        final ArgumentCaptor<IFingerprintServiceReceiver> fingerprintServiceReceiverArgumentCaptor =
+                ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+        mFingerprintManager.authenticate(null, new CancellationSignal(),
+                authenticationCallback1, mHandler,
+                new FingerprintAuthenticateOptions.Builder().build());
+        mFingerprintManager.authenticate(null, new CancellationSignal(),
+                authenticationCallback2, mHandler,
+                new FingerprintAuthenticateOptions.Builder().build());
+
+        verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+                fingerprintServiceReceiverArgumentCaptor.capture(), any());
+
+        final List<IFingerprintServiceReceiver> fingerprintServiceReceivers =
+                fingerprintServiceReceiverArgumentCaptor.getAllValues();
+        fingerprintServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+        verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+        fingerprintServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 80fef6c..ccebd03 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1258,18 +1258,6 @@
         sInstrumentation.waitForIdleSync();
 
         mViewRootImpl = mView.getViewRootImpl();
-        waitForFrameRateCategoryToSettle(mView);
-
-        sInstrumentation.runOnMainSync(() -> {
-            assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
-                    mViewRootImpl.getPreferredFrameRateCategory());
-            mView.invalidate();
-            int expected = toolkitFrameRateDefaultNormalReadOnly()
-                    ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
-            runAfterDraw(() -> assertEquals(expected,
-                    mViewRootImpl.getLastPreferredFrameRateCategory()));
-        });
-        waitForAfterDraw();
 
         waitForFrameRateCategoryToSettle(mView);
 
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 82251b8..4921e4a 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,7 +20,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 
@@ -82,14 +81,8 @@
         createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
         installDecor();
 
-        if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
-                && !mPhoneWindow.isFloating()) {
-            assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
-                    is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
-        } else {
-            assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
-                    is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
-        }
+        assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+                is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index eebd133..77b8663 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.recents;
 
+import android.annotation.Nullable;
+import android.graphics.Color;
+
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
@@ -40,4 +43,12 @@
      */
     default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
     }
+
+    /**
+     * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+     * may be used to clear any previously set colors to avoid showing a background at all. The
+     * color is always shown at full opacity.
+     */
+    default void setTransitionBackgroundColor(@Nullable Color color) {
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0c99aed..e7d9812 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -30,6 +30,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Color;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -476,6 +477,16 @@
                 });
             });
         }
+
+        @Override
+        public void setTransitionBackgroundColor(@Nullable Color color) {
+            mMainExecutor.execute(() -> {
+                if (mTransitionHandler == null) {
+                    return;
+                }
+                mTransitionHandler.setTransitionBackgroundColor(color);
+            });
+        }
     }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 24cf370..c625b69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -36,6 +36,7 @@
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
 import android.content.Intent;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -56,6 +57,8 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.protolog.common.ProtoLog;
@@ -92,6 +95,7 @@
     private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
 
     private final HomeTransitionObserver mHomeTransitionObserver;
+    private @Nullable Color mBackgroundColor;
 
     public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
             @Nullable RecentTasksController recentTasksController,
@@ -123,6 +127,15 @@
         mStateListeners.add(listener);
     }
 
+    /**
+     * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+     * may be used to clear any previously set colors to avoid showing a background at all. The
+     * color is always shown at full opacity.
+     */
+    public void setTransitionBackgroundColor(@Nullable Color color) {
+        mBackgroundColor = color;
+    }
+
     @VisibleForTesting
     public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
             IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -469,6 +482,16 @@
             final int belowLayers = info.getChanges().size();
             final int middleLayers = info.getChanges().size() * 2;
             final int aboveLayers = info.getChanges().size() * 3;
+
+            // Add a background color to each transition root in this transition.
+            if (mBackgroundColor != null) {
+                info.getChanges().stream()
+                        .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info))
+                        .distinct()
+                        .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash())
+                        .forEach((root) -> createBackgroundSurface(t, root, middleLayers));
+            }
+
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -1107,6 +1130,29 @@
             return true;
         }
 
+        private void createBackgroundSurface(SurfaceControl.Transaction transaction,
+                SurfaceControl parent, int layer) {
+            if (mBackgroundColor == null) {
+                return;
+            }
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                    "  adding background color to layer=%d", layer);
+            final SurfaceControl background = new SurfaceControl.Builder()
+                    .setName("recents_background")
+                    .setColorLayer()
+                    .setOpaque(true)
+                    .setParent(parent)
+                    .build();
+            transaction.setColor(background, colorToFloatArray(mBackgroundColor));
+            transaction.setLayer(background, layer);
+            transaction.setAlpha(background, 1F);
+            transaction.show(background);
+        }
+
+        private static float[] colorToFloatArray(@NonNull Color color) {
+            return new float[]{color.red(), color.green(), color.blue()};
+        }
+
         private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
                 SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
             if (!sendUserLeaveHint && task.isLeaf()) {
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index 2573931..36d8fba 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -52,6 +52,8 @@
 
     virtual void destroy() {}
 
+    int getBuffersDataSpace() { return 0; }
+
 protected:
     virtual ~Surface() {}
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7439fbc..eceba92 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -93,6 +93,7 @@
         host: {
             include_dirs: [
                 "external/vulkan-headers/include",
+                "frameworks/av/media/ndk/include",
             ],
             cflags: [
                 "-Wno-unused-variable",
@@ -142,7 +143,6 @@
                 "libsync",
                 "libui",
                 "aconfig_text_flags_c_lib",
-                "server_configurable_flags",
             ],
             static_libs: [
                 "libEGL_blobCache",
@@ -267,6 +267,7 @@
     cppflags: ["-Wno-conversion-null"],
 
     srcs: [
+        "apex/android_canvas.cpp",
         "apex/android_matrix.cpp",
         "apex/android_paint.cpp",
         "apex/android_region.cpp",
@@ -279,7 +280,6 @@
         android: {
             srcs: [ // sources that depend on android only libraries
                 "apex/android_bitmap.cpp",
-                "apex/android_canvas.cpp",
                 "apex/jni_runtime.cpp",
             ],
         },
@@ -338,6 +338,8 @@
         "jni/android_graphics_ColorSpace.cpp",
         "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
         "jni/android_graphics_drawable_VectorDrawable.cpp",
+        "jni/android_graphics_HardwareRenderer.cpp",
+        "jni/android_graphics_HardwareBufferRenderer.cpp",
         "jni/android_graphics_HardwareRendererObserver.cpp",
         "jni/android_graphics_Matrix.cpp",
         "jni/android_graphics_Picture.cpp",
@@ -422,8 +424,6 @@
         android: {
             srcs: [ // sources that depend on android only libraries
                 "jni/android_graphics_TextureLayer.cpp",
-                "jni/android_graphics_HardwareRenderer.cpp",
-                "jni/android_graphics_HardwareBufferRenderer.cpp",
                 "jni/GIFMovie.cpp",
                 "jni/GraphicsStatsService.cpp",
                 "jni/Movie.cpp",
@@ -448,6 +448,12 @@
                 "libstatssocket_lazy",
             ],
         },
+        linux: {
+            srcs: ["platform/linux/utils/SharedLib.cpp"],
+        },
+        darwin: {
+            srcs: ["platform/darwin/utils/SharedLib.cpp"],
+        },
         host: {
             cflags: [
                 "-Wno-unused-const-variable",
@@ -543,6 +549,7 @@
         "renderthread/CanvasContext.cpp",
         "renderthread/DrawFrameTask.cpp",
         "renderthread/Frame.cpp",
+        "renderthread/RenderEffectCapabilityQuery.cpp",
         "renderthread/RenderProxy.cpp",
         "renderthread/RenderTask.cpp",
         "renderthread/TimeLord.cpp",
@@ -576,6 +583,7 @@
         "HWUIProperties.sysprop",
         "Interpolator.cpp",
         "JankTracker.cpp",
+        "Layer.cpp",
         "LayerUpdateQueue.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
@@ -624,7 +632,6 @@
                 "renderthread/CacheManager.cpp",
                 "renderthread/EglManager.cpp",
                 "renderthread/ReliableSurface.cpp",
-                "renderthread/RenderEffectCapabilityQuery.cpp",
                 "renderthread/VulkanManager.cpp",
                 "renderthread/VulkanSurface.cpp",
                 "renderthread/RenderThread.cpp",
@@ -635,7 +642,6 @@
                 "AutoBackendTextureRelease.cpp",
                 "DeferredLayerUpdater.cpp",
                 "HardwareBitmapUploader.cpp",
-                "Layer.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
                 "WebViewFunctorManager.cpp",
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
index 7e3f771..d3b48d3 100644
--- a/libs/hwui/jni/HardwareBufferHelpers.cpp
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -16,7 +16,9 @@
 
 #include "HardwareBufferHelpers.h"
 
+#ifdef __ANDROID__
 #include <dlfcn.h>
+#endif
 #include <log/log.h>
 
 #ifdef __ANDROID__
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d9e2c8c..df9f830 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -25,13 +25,16 @@
 #include <SkColorSpace.h>
 #include <SkData.h>
 #include <SkImage.h>
+#ifdef __ANDROID__
 #include <SkImageAndroid.h>
+#else
+#include <SkImagePriv.h>
+#endif
 #include <SkPicture.h>
 #include <SkPixmap.h>
 #include <SkSerialProcs.h>
 #include <SkStream.h>
 #include <SkTypeface.h>
-#include <dlfcn.h>
 #include <gui/TraceUtils.h>
 #include <include/encode/SkPngEncoder.h>
 #include <inttypes.h>
@@ -39,8 +42,10 @@
 #include <media/NdkImage.h>
 #include <media/NdkImageReader.h>
 #include <nativehelper/JNIPlatformHelp.h>
+#ifdef __ANDROID__
 #include <pipeline/skia/ShaderCache.h>
 #include <private/EGL/cache.h>
+#endif
 #include <renderthread/CanvasContext.h>
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
@@ -59,6 +64,7 @@
 #include "JvmErrorReporter.h"
 #include "android_graphics_HardwareRendererObserver.h"
 #include "utils/ForceDark.h"
+#include "utils/SharedLib.h"
 
 namespace android {
 
@@ -498,7 +504,11 @@
                 return sk_ref_sp(img);
             }
             bm.setImmutable();
+#ifdef __ANDROID__
             return SkImages::PinnableRasterFromBitmap(bm);
+#else
+            return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+#endif
         }
         return sk_ref_sp(img);
     }
@@ -713,6 +723,7 @@
 
 static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+#ifdef __ANDROID__
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     if (jwidth <= 0 || jheight <= 0) {
         ALOGW("Invalid width %d or height %d", jwidth, jheight);
@@ -796,6 +807,9 @@
     sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
     return bitmap::createBitmap(env, bitmap.release(),
             android::bitmap::kBitmapCreateFlag_Premultiplied);
+#else
+    return nullptr;
+#endif
 }
 
 static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
@@ -909,6 +923,7 @@
 
 static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
         jstring diskCachePath, jstring skiaDiskCachePath) {
+#ifdef __ANDROID__
     const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
     android::egl_set_cache_filename(cacheArray);
     env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -916,6 +931,7 @@
     const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
     uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
     env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
+#endif
 }
 
 static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) {
@@ -1092,8 +1108,12 @@
     gCopyRequest.getDestinationBitmap =
             GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
 
-    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
-    fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+#ifdef __ANDROID__
+    void* handle_ = SharedLib::openSharedLib("libandroid");
+#else
+    void* handle_ = SharedLib::openSharedLib("libandroid_runtime");
+#endif
+    fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface");
     LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
                         "Failed to find required symbol ANativeWindow_fromSurface!");
 
diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp
new file mode 100644
index 0000000..6e9f0b4
--- /dev/null
+++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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 "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+    return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+    return dlsym(library, symbol);
+}
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp
new file mode 100644
index 0000000..a9acf37
--- /dev/null
+++ b/libs/hwui/platform/linux/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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 "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+    return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+    return dlsym(library, symbol);
+}
+
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h
new file mode 100644
index 0000000..f4dcf0f
--- /dev/null
+++ b/libs/hwui/utils/SharedLib.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHAREDLIB_H
+#define SHAREDLIB_H
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+class SharedLib {
+public:
+    static void* openSharedLib(std::string filename);
+    static void* getSymbol(void* library, const char* symbol);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif  // SHAREDLIB_H
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 19e59a7..d6d4989 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -79,6 +79,13 @@
 }
 
 flag {
+    name: "subscriptions_listener_thread"
+    namespace: "location"
+    description: "Flag for running onSubscriptionsChangeListener on FgThread"
+    bug: "332451908"
+}
+
+flag {
     name: "gnss_configuration_from_resource"
     namespace: "location"
     description: "Flag for GNSS configuration from resource"
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index ce7474c..3ba0d59 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5232,6 +5232,13 @@
         setParameters(keys, values);
     }
 
+    private void logAndRun(String message, Runnable r) {
+        final String TAG = "MediaCodec";
+        android.util.Log.d(TAG, "enter: " + message);
+        r.run();
+        android.util.Log.d(TAG, "exit : " + message);
+    }
+
     /**
      * Sets an asynchronous callback for actionable MediaCodec events.
      *
@@ -5261,14 +5268,40 @@
                 // even if we were to extend this to be callable dynamically, it must
                 // be called when codec is flushed, so no messages are pending.
                 if (newHandler != mCallbackHandler) {
-                    mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
-                    mCallbackHandler.removeMessages(EVENT_CALLBACK);
+                    if (android.media.codec.Flags.setCallbackStall()) {
+                        logAndRun(
+                                "[new handler] removeMessages(SET_CALLBACK)",
+                                () -> {
+                                    mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+                                });
+                        logAndRun(
+                                "[new handler] removeMessages(CALLBACK)",
+                                () -> {
+                                    mCallbackHandler.removeMessages(EVENT_CALLBACK);
+                                });
+                    } else {
+                        mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+                        mCallbackHandler.removeMessages(EVENT_CALLBACK);
+                    }
                     mCallbackHandler = newHandler;
                 }
             }
         } else if (mCallbackHandler != null) {
-            mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
-            mCallbackHandler.removeMessages(EVENT_CALLBACK);
+            if (android.media.codec.Flags.setCallbackStall()) {
+                logAndRun(
+                        "[null handler] removeMessages(SET_CALLBACK)",
+                        () -> {
+                            mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+                        });
+                logAndRun(
+                        "[null handler] removeMessages(CALLBACK)",
+                        () -> {
+                            mCallbackHandler.removeMessages(EVENT_CALLBACK);
+                        });
+            } else {
+                mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+                mCallbackHandler.removeMessages(EVENT_CALLBACK);
+            }
         }
 
         if (mCallbackHandler != null) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 5331046..6593533 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -205,6 +205,10 @@
      * media container format specified by mimeType at the requested
      * security level.
      *
+     * Calling this method while the application is running on the physical Android device or a
+     * {@link android.companion.virtual.VirtualDevice} may lead to different results, based on
+     * the different DRM capabilities of the devices.
+     *
      * @param uuid The UUID of the crypto scheme.
      * @param mimeType The MIME type of the media container, e.g. "video/mp4"
      *   or "video/webm"
@@ -1400,6 +1404,10 @@
      * Open a new session with the MediaDrm object. A session ID is returned.
      * By default, sessions are opened at the native security level of the device.
      *
+     * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+     * the security level will be adjusted accordingly to the maximum supported level for the
+     * display.
+     *
      * @throws NotProvisionedException if provisioning is needed
      * @throws ResourceBusyException if required resources are in use
      */
@@ -1422,6 +1430,10 @@
      * can be queried using {@link #getSecurityLevel}. A session
      * ID is returned.
      *
+     * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+     * the security level will be adjusted accordingly to the maximum supported level for the
+     * display.
+     *
      * @param level the new security level
      * @throws NotProvisionedException if provisioning is needed
      * @throws ResourceBusyException if required resources are in use
@@ -2180,6 +2192,11 @@
      * Returns a value that may be passed as a parameter to {@link #openSession(int)}
      * requesting that the session be opened at the maximum security level of
      * the device.
+     *
+     * This security level is only valid for the application running on the physical Android
+     * device (e.g. {@link android.content.Context#DEVICE_ID_DEFAULT}). While running on a
+     * {@link android.companion.virtual.VirtualDevice} the maximum supported security level
+     * might be different.
      */
     public static final int getMaxSecurityLevel() {
         return SECURITY_LEVEL_MAX;
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45b4370..212e390 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -528,8 +528,8 @@
 
     &#64;Override
     // Declare the receivers associated with your input ports.
-    public List<MidiReceiver> onGetInputPortReceivers() {
-        return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
+    public List&lt;MidiReceiver> onGetInputPortReceivers() {
+        return new ArrayList&lt;MidiReceiver>(Collections.singletonList(mInputReceiver));
     }
 
     /**
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 94fce79..8609c4d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -82,6 +82,7 @@
         "libhidlbase",
         "libsonivox",
         "server_configurable_flags",
+        "android.companion.virtual.virtualdevice_aidl-cpp",
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.drm@1.3",
@@ -100,6 +101,7 @@
     static_libs: [
         "libgrallocusage",
         "libmedia_midiiowrapper",
+        "android.companion.virtualdevice.flags-aconfig-cc",
         "android.media.playback.flags-aconfig-cc",
     ],
 
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 1c25080..48cd53d 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -27,6 +27,8 @@
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
+#include <android_companion_virtualdevice_flags.h>
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
 #include <android/hardware/drm/1.3/IDrmFactory.h>
 #include <binder/Parcel.h>
 #include <binder/PersistableBundle.h>
@@ -41,8 +43,10 @@
 #include <map>
 #include <string>
 
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
+namespace virtualdevice_flags = android::companion::virtualdevice::flags;
 
 namespace android {
 
@@ -1045,6 +1049,26 @@
     return level;
 }
 
+std::vector<int> getVirtualDeviceIds() {
+    if (!virtualdevice_flags::device_aware_drm()) {
+        ALOGW("Device-aware DRM flag disabled.");
+        return std::vector<int>();
+    }
+
+    sp<IBinder> binder =
+            defaultServiceManager()->checkService(String16("virtualdevice_native"));
+    if (binder != nullptr) {
+        auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+        std::vector<int> deviceIds;
+        const uid_t uid = IPCThreadState::self()->getCallingUid();
+        vdm->getDeviceIdsForUid(uid, &deviceIds);
+        return deviceIds;
+    } else {
+        ALOGW("Cannot get virtualdevice_native service");
+        return std::vector<int>();
+    }
+}
+
 static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) {
     sp<IDrm> drm = android::DrmUtils::MakeDrm();
     if (drm == NULL) return env->NewByteArray(0);
@@ -1081,6 +1105,15 @@
     }
     DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
 
+    if (getVirtualDeviceIds().size() > 0) {
+        // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+        // higher security levels decode output cannot be captured and
+        // streamed to virtual devices rendered on virtual displays.
+        if (securityLevel > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+            return false;
+        }
+    }
+
     bool isSupported;
     status_t err = JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType,
             securityLevel, &isSupported);
@@ -1106,6 +1139,16 @@
         return NULL;
     }
 
+    if (getVirtualDeviceIds().size() > 0) {
+        // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+        // higher security levels decode output cannot be captured and
+        // streamed to virtual devices rendered on virtual displays.
+        if (level == DrmPlugin::kSecurityLevelMax ||
+            level > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+            level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+        }
+    }
+
     DrmStatus err = drm->openSession(level, sessionId);
 
     if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) {
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index a64e3f2..36cba2d 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -19,6 +19,8 @@
 
 #include "jni.h"
 
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <mediadrm/IDrm.h>
 #include <mediadrm/IDrmClient.h>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 65bbb6fc..a0ebbfe 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -197,7 +197,12 @@
                 filter(allFilters, BluetoothLeDeviceFilter.class);
         final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
 
-        checkBoundDevicesIfNeeded(request, btFilters);
+        // No need to startDiscovery if the device is already bound or connected for
+        // singleDevice dialog.
+        if (checkBoundDevicesIfNeeded(request, btFilters)) {
+            stopSelf();
+            return;
+        }
 
         // If no filters are specified: look for everything.
         final boolean forceStartScanningAll = isEmpty(allFilters);
@@ -257,33 +262,37 @@
         stopSelf();
     }
 
-    private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+    private boolean checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
             @NonNull List<BluetoothDeviceFilter> btFilters) {
         // If filtering to get single device by mac address, also search in the set of already
         // bonded devices to allow linking those directly
-        if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+        if (btFilters.isEmpty() || !request.isSingleDevice()) return false;
 
         final BluetoothDeviceFilter singleMacAddressFilter =
                 find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
 
-        if (singleMacAddressFilter == null) return;
+        if (singleMacAddressFilter == null) return false;
 
-        findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
-        findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
-        findAndReportMatches(
-                mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+        return findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters)
+                || findAndReportMatches(mBtManager.getConnectedDevices(
+                        BluetoothProfile.GATT), btFilters)
+                || findAndReportMatches(mBtManager.getConnectedDevices(
+                        BluetoothProfile.GATT_SERVER), btFilters);
     }
 
-    private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+    private boolean findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
             @NonNull List<BluetoothDeviceFilter> filters) {
-        if (devices == null) return;
+        if (devices == null) return false;
 
         for (BluetoothDevice device : devices) {
             final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
             if (match != null) {
                 onDeviceFound(match);
+                return true;
             }
         }
+
+        return false;
     }
 
     private BluetoothBroadcastReceiver startBtScanningIfNeeded(
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
deleted file mode 100644
index fa4d6af..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 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.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
-    private ArrayUtils() { /* cannot be instantiated */ }
-    public static final File[] EMPTY_FILE = new File[0];
-
-
-    /**
-     * Return first index of {@code value} in {@code array}, or {@code -1} if
-     * not found.
-     */
-    public static <T> int indexOf(@Nullable T[] array, T value) {
-        if (array == null) return -1;
-        for (int i = 0; i < array.length; i++) {
-            if (Objects.equals(array[i], value)) return i;
-        }
-        return -1;
-    }
-
-    /** @hide */
-    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
-        return (val != null) ? val : EMPTY_FILE;
-    }
-
-    /**
-     * Checks if given array is null or has zero elements.
-     */
-    public static boolean isEmpty(@Nullable int[] array) {
-        return array == null || array.length == 0;
-    }
-
-    /**
-     * True if the byte array is null or has length 0.
-     */
-    public static boolean isEmpty(@Nullable byte[] array) {
-        return array == null || array.length == 0;
-    }
-
-    /**
-     * Converts from List of bytes to byte array
-     * @param list
-     * @return byte[]
-     */
-    public static byte[] toPrimitive(List<byte[]> list) {
-        if (list.size() == 0) {
-            return new byte[0];
-        }
-        int byteLen = list.get(0).length;
-        byte[] array = new byte[list.size() * byteLen];
-        for (int i = 0; i < list.size(); i++) {
-            for (int j = 0; j < list.get(i).length; j++) {
-                array[i * byteLen + j] = list.get(i)[j];
-            }
-        }
-        return array;
-    }
-
-    /**
-     * Adds value to given array if not already present, providing set-like
-     * behavior.
-     */
-    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
-        return appendInt(cur, val, false);
-    }
-
-    /**
-     * Adds value to given array.
-     */
-    public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
-            boolean allowDuplicates) {
-        if (cur == null) {
-            return new int[] { val };
-        }
-        final int n = cur.length;
-        if (!allowDuplicates) {
-            for (int i = 0; i < n; i++) {
-                if (cur[i] == val) {
-                    return cur;
-                }
-            }
-        }
-        int[] ret = new int[n + 1];
-        System.arraycopy(cur, 0, ret, 0, n);
-        ret[n] = val;
-        return ret;
-    }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
deleted file mode 100644
index afcf689..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- *  * Copyright (C) 2024 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.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.HandlerThread;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executor;
-
-/**
- * Thread for asynchronous event processing. This thread is configured as
- * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
- * resources will be dedicated to it, and it will "have less chance of impacting
- * the responsiveness of the user interface."
- * <p>
- * This thread is best suited for tasks that the user is not actively waiting
- * for, or for tasks that the user expects to be executed eventually.
- *
- * @see com.android.internal.os.BackgroundThread
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public final class BackgroundThread extends HandlerThread {
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static BackgroundThread sInstance;
-    @GuardedBy("sLock")
-    private static Handler sHandler;
-    @GuardedBy("sLock")
-    private static HandlerExecutor sHandlerExecutor;
-
-    private BackgroundThread() {
-        super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
-    }
-
-    @GuardedBy("sLock")
-    private static void ensureThreadLocked() {
-        if (sInstance == null) {
-            sInstance = new BackgroundThread();
-            sInstance.start();
-            sHandler = new Handler(sInstance.getLooper());
-            sHandlerExecutor = new HandlerExecutor(sHandler);
-        }
-    }
-
-    /**
-     * Get the singleton instance of this class.
-     *
-     * @return the singleton instance of this class
-     */
-    @NonNull
-    public static BackgroundThread get() {
-        synchronized (sLock) {
-            ensureThreadLocked();
-            return sInstance;
-        }
-    }
-
-    /**
-     * Get the singleton {@link Handler} for this class.
-     *
-     * @return the singleton {@link Handler} for this class.
-     */
-    @NonNull
-    public static Handler getHandler() {
-        synchronized (sLock) {
-            ensureThreadLocked();
-            return sHandler;
-        }
-    }
-
-    /**
-     * Get the singleton {@link Executor} for this class.
-     *
-     * @return the singleton {@link Executor} for this class.
-     */
-    @NonNull
-    public static Executor getExecutor() {
-        synchronized (sLock) {
-            ensureThreadLocked();
-            return sHandlerExecutor;
-        }
-    }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
deleted file mode 100644
index e4923bf..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2024 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.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
-    /**
-     * Read a text file into a String, optionally limiting the length.
-     *
-     * @param file     to read (will not seek, so things like /proc files are OK)
-     * @param max      length (positive for head, negative of tail, 0 for no limit)
-     * @param ellipsis to add of the file was truncated (can be null)
-     * @return the contents of the file, possibly truncated
-     * @throws IOException if something goes wrong reading the file
-     * @hide
-     */
-    public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
-            @Nullable String ellipsis) throws IOException {
-        InputStream input = new FileInputStream(file);
-        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
-        // input stream, bytes read not equal to buffer size is not necessarily the correct
-        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
-        BufferedInputStream bis = new BufferedInputStream(input);
-        try {
-            long size = file.length();
-            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
-                if (size > 0 && (max == 0 || size < max)) max = (int) size;
-                byte[] data = new byte[max + 1];
-                int length = bis.read(data);
-                if (length <= 0) return "";
-                if (length <= max) return new String(data, 0, length);
-                if (ellipsis == null) return new String(data, 0, max);
-                return new String(data, 0, max) + ellipsis;
-            } else if (max < 0) {  // "tail" mode: keep the last N
-                int len;
-                boolean rolled = false;
-                byte[] last = null;
-                byte[] data = null;
-                do {
-                    if (last != null) rolled = true;
-                    byte[] tmp = last;
-                    last = data;
-                    data = tmp;
-                    if (data == null) data = new byte[-max];
-                    len = bis.read(data);
-                } while (len == data.length);
-
-                if (last == null && len <= 0) return "";
-                if (last == null) return new String(data, 0, len);
-                if (len > 0) {
-                    rolled = true;
-                    System.arraycopy(last, len, last, 0, last.length - len);
-                    System.arraycopy(data, 0, last, last.length - len, len);
-                }
-                if (ellipsis == null || !rolled) return new String(last);
-                return ellipsis + new String(last);
-            } else {  // "cat" mode: size unknown, read it all in streaming fashion
-                ByteArrayOutputStream contents = new ByteArrayOutputStream();
-                int len;
-                byte[] data = new byte[1024];
-                do {
-                    len = bis.read(data);
-                    if (len > 0) contents.write(data, 0, len);
-                } while (len == data.length);
-                return contents.toString();
-            }
-        } finally {
-            bis.close();
-            input.close();
-        }
-    }
-
-    /**
-     * Perform an fsync on the given FileOutputStream. The stream at this
-     * point must be flushed but not yet closed.
-     *
-     * @hide
-     */
-    public static boolean sync(FileOutputStream stream) {
-        try {
-            if (stream != null) {
-                stream.getFD().sync();
-            }
-            return true;
-        } catch (IOException e) {
-        }
-        return false;
-    }
-
-    /**
-     * List the files in the directory or return empty file.
-     *
-     * @hide
-     */
-    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
-        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
-            : ArrayUtils.EMPTY_FILE;
-    }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
deleted file mode 100644
index fdb15e2..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2024 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.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-/**
- * An adapter {@link Executor} that posts all executed tasks onto the given
- * {@link Handler}.
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public class HandlerExecutor implements Executor {
-    private final Handler mHandler;
-
-    public HandlerExecutor(@NonNull Handler handler) {
-        mHandler = Objects.requireNonNull(handler);
-    }
-
-    @Override
-    public void execute(Runnable command) {
-        if (!mHandler.post(command)) {
-            throw new RejectedExecutionException(mHandler + " is shutting down");
-        }
-    }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
deleted file mode 100644
index 5cdc253..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2024 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.utils;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
-    private long[] mValues;
-    private int mSize;
-    private int mHead;
-    private int mTail;
-
-    private long[] newUnpaddedLongArray(int num) {
-        return new long[num];
-    }
-    /**
-     * Initializes a queue with the given starting capacity.
-     *
-     * @param initialCapacity the capacity.
-     */
-    public LongArrayQueue(int initialCapacity) {
-        if (initialCapacity == 0) {
-            mValues = EmptyArray.LONG;
-        } else {
-            mValues = newUnpaddedLongArray(initialCapacity);
-        }
-        mSize = 0;
-        mHead = mTail = 0;
-    }
-
-    /**
-     * Initializes a queue with default starting capacity.
-     */
-    public LongArrayQueue() {
-        this(16);
-    }
-
-    /** @hide */
-    public static int growSize(int currentSize) {
-        return currentSize <= 4 ? 8 : currentSize * 2;
-    }
-
-    private void grow() {
-        if (mSize < mValues.length) {
-            throw new IllegalStateException("Queue not full yet!");
-        }
-        final int newSize = growSize(mSize);
-        final long[] newArray = newUnpaddedLongArray(newSize);
-        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
-        System.arraycopy(mValues, mHead, newArray, 0, r);
-        System.arraycopy(mValues, 0, newArray, r, mHead);
-        mValues = newArray;
-        mHead = 0;
-        mTail = mSize;
-    }
-
-    /**
-     * Returns the number of elements in the queue.
-     */
-    public int size() {
-        return mSize;
-    }
-
-    /**
-     * Removes all elements from this queue.
-     */
-    public void clear() {
-        mSize = 0;
-        mHead = mTail = 0;
-    }
-
-    /**
-     * Adds a value to the tail of the queue.
-     *
-     * @param value the value to be added.
-     */
-    public void addLast(long value) {
-        if (mSize == mValues.length) {
-            grow();
-        }
-        mValues[mTail] = value;
-        mTail = (mTail + 1) % mValues.length;
-        mSize++;
-    }
-
-    /**
-     * Removes an element from the head of the queue.
-     *
-     * @return the element at the head of the queue.
-     * @throws NoSuchElementException if the queue is empty.
-     */
-    public long removeFirst() {
-        if (mSize == 0) {
-            throw new NoSuchElementException("Queue is empty!");
-        }
-        final long ret = mValues[mHead];
-        mHead = (mHead + 1) % mValues.length;
-        mSize--;
-        return ret;
-    }
-
-    /**
-     * Returns the element at the given position from the head of the queue, where 0 represents the
-     * head of the queue.
-     *
-     * @param position the position from the head of the queue.
-     * @return the element found at the given position.
-     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
-     *                                   {@code position} >= {@link #size()}
-     */
-    public long get(int position) {
-        if (position < 0 || position >= mSize) {
-            throw new IndexOutOfBoundsException("Index " + position
-                + " not valid for a queue of size " + mSize);
-        }
-        final int index = (mHead + position) % mValues.length;
-        return mValues[index];
-    }
-
-    /**
-     * Returns the element at the head of the queue, without removing it.
-     *
-     * @return the element at the head of the queue.
-     * @throws NoSuchElementException if the queue is empty
-     */
-    public long peekFirst() {
-        if (mSize == 0) {
-            throw new NoSuchElementException("Queue is empty!");
-        }
-        return mValues[mHead];
-    }
-
-    /**
-     * Returns the element at the tail of the queue.
-     *
-     * @return the element at the tail of the queue.
-     * @throws NoSuchElementException if the queue is empty.
-     */
-    public long peekLast() {
-        if (mSize == 0) {
-            throw new NoSuchElementException("Queue is empty!");
-        }
-        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
-        return mValues[index];
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public String toString() {
-        if (mSize <= 0) {
-            return "{}";
-        }
-
-        final StringBuilder buffer = new StringBuilder(mSize * 64);
-        buffer.append('{');
-        buffer.append(get(0));
-        for (int i = 1; i < mSize; i++) {
-            buffer.append(", ");
-            buffer.append(get(i));
-        }
-        buffer.append('}');
-        return buffer.toString();
-    }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
deleted file mode 100644
index dbbef61..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2024 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.utils;
-
-import android.annotation.NonNull;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import libcore.util.XmlObjectFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
-    private static final String STRING_ARRAY_SEPARATOR = ":";
-
-    /** @hide */
-    public static final void beginDocument(XmlPullParser parser, String firstElementName)
-            throws XmlPullParserException, IOException {
-        int type;
-        while ((type = parser.next()) != parser.START_TAG
-            && type != parser.END_DOCUMENT) {
-            // Do nothing
-        }
-
-        if (type != parser.START_TAG) {
-            throw new XmlPullParserException("No start tag found");
-        }
-
-        if (!parser.getName().equals(firstElementName)) {
-            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
-                + ", expected " + firstElementName);
-        }
-    }
-
-    /** @hide */
-    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
-            throws IOException, XmlPullParserException {
-        for (;;) {
-            int type = parser.next();
-            if (type == XmlPullParser.END_DOCUMENT
-                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
-                return false;
-            }
-            if (type == XmlPullParser.START_TAG
-                    && parser.getDepth() == outerDepth + 1) {
-                return true;
-            }
-        }
-    }
-
-    private static XmlPullParser newPullParser() {
-        try {
-            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
-            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
-            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-            return parser;
-        } catch (XmlPullParserException e) {
-            throw new AssertionError();
-        }
-    }
-
-    /** @hide */
-    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
-            throws IOException {
-        final byte[] magic = new byte[4];
-        if (in instanceof FileInputStream) {
-            try {
-                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
-            } catch (ErrnoException e) {
-                throw e.rethrowAsIOException();
-            }
-        } else {
-            if (!in.markSupported()) {
-                in = new BufferedInputStream(in);
-            }
-            in.mark(8);
-            in.read(magic);
-            in.reset();
-        }
-
-        final TypedXmlPullParser xml;
-        xml = (TypedXmlPullParser) newPullParser();
-        try {
-            xml.setInput(in, "UTF_8");
-        } catch (XmlPullParserException e) {
-            throw new IOException(e);
-        }
-        return xml;
-    }
-}
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index e998fe8..2aff2c3 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -34,7 +34,7 @@
                     android:layout_height="wrap_content"
                     android:layout_gravity="center"
                     android:layout_alignParentStart="true"
-                    android:paddingLeft="@dimen/autofill_view_left_padding"
+                    android:paddingStart="@dimen/autofill_view_left_padding"
                     app:tint="?androidprv:attr/materialColorOnSurface"
                     android:background="@null"/>
 
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9db681a..46a5138 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -121,16 +121,10 @@
   <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
   <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
-  <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the get flow. [CHAR LIMIT=200] -->
-  <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string>
   <!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_sign_in_for">Use your account for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
   <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
-  <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
-  <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] -->
-  <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
-  <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] -->
-  <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+  <string name="get_dialog_description_single_tap">Use your screen lock to sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g></string>
   <!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
   <!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 429bdbf..7bc3241 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -317,6 +317,14 @@
         )
     }
 
+    fun createFlowOnMoreOptionsOnlySelectedOnCreationSelection() {
+        uiState = uiState.copy(
+                createCredentialUiState = uiState.createCredentialUiState?.copy(
+                        currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION_ONLY,
+                )
+        )
+    }
+
     fun createFlowOnBackCreationSelectionButtonSelected() {
         uiState = uiState.copy(
             createCredentialUiState = uiState.createCredentialUiState?.copy(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index 6bf803a..0d19a45 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -34,7 +34,6 @@
 import com.android.credentialmanager.getflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
 import com.android.credentialmanager.model.BiometricRequestInfo
-import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.model.EntryInfo
 import com.android.credentialmanager.model.creation.CreateOptionInfo
 import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -210,7 +209,7 @@
     onCancelFlowAndFinish: () -> Unit
 ) {
     try {
-        if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+        if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
             onBiometricFailureFallback(biometricFlowType)
             return
         }
@@ -250,40 +249,40 @@
  * consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks
  * occur if *only* device credentials are available, to avoid going right into the PIN screen.
  * Note that if device credential is the only available modality but not requested, or if none
- * of the requested modalities are available, we propagate the error to the provider instead of
- * falling back and expect them to handle it as they would prior.
- * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases
+ * of the requested modalities are available, we fallback to the normal flow to ensure a selector
+ * shows up.
+ * // TODO(b/334197980) : While we already fallback in cases the selector doesn't show, confirm
+ * // final plan.
  */
-private fun onlyUsingDeviceCredentials(
+private fun canCallBiometricPrompt(
     biometricDisplayInfo: BiometricDisplayInfo,
     context: Context
 ): Boolean {
     val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators
     if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
-        return true
-    }
-
-    val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials(
-        allowedAuthenticators)
-
-    if (!allowedAuthContainsDeviceCredential) {
-        // At this point, allowed authenticators is requesting biometrics without device creds.
-        // Thus, a fallback mechanism will be displayed via our own negative button - "cancel".
-        // Beyond this point, fallbacks will occur if none of the stronger authenticators can
-        // be used.
         return false
     }
 
     val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
 
-    if (allowedAuthContainsDeviceCredential &&
-        biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
-        BiometricManager.BIOMETRIC_SUCCESS &&
-        biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+    if (biometricManager.canAuthenticate(allowedAuthenticators) !=
         BiometricManager.BIOMETRIC_SUCCESS) {
-        return true
+        return false
     }
 
+    if (ifOnlySupportsAtMostDeviceCredentials(biometricManager)) return false
+
+    return true
+}
+
+private fun ifOnlySupportsAtMostDeviceCredentials(biometricManager: BiometricManager): Boolean {
+    if (biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
+        BiometricManager.BIOMETRIC_SUCCESS &&
+        biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+        BiometricManager.BIOMETRIC_SUCCESS
+    ) {
+        return true
+    }
     return false
 }
 
@@ -480,16 +479,7 @@
     )
 
     descriptionText = context.getString(
-        when (singleEntryType) {
-            CredentialType.PASSKEY ->
-                R.string.get_dialog_description_single_tap_passkey
-
-            CredentialType.PASSWORD ->
-                R.string.get_dialog_description_single_tap_password
-
-            CredentialType.UNKNOWN ->
-                R.string.get_dialog_description_single_tap_saved_sign_in
-        },
+        R.string.get_dialog_description_single_tap,
         getRequestDisplayInfo.appName,
         username
     )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 149c14a..2c3c63b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -28,7 +28,6 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.outlined.Lock
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
@@ -47,7 +46,6 @@
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -55,7 +53,6 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.credentialmanager.R
 import com.android.credentialmanager.ui.theme.EntryShape
 import com.android.credentialmanager.ui.theme.Shapes
 
@@ -321,6 +318,8 @@
 fun MoreOptionTopAppBar(
     text: String,
     onNavigationIconClicked: () -> Unit,
+    navigationIcon: ImageVector,
+    navigationIconContentDescription: String,
     bottomPadding: Dp,
 ) {
     Row(
@@ -336,40 +335,6 @@
                     contentAlignment = Alignment.Center,
             ) {
                 Icon(
-                        imageVector = Icons.Filled.ArrowBack,
-                        contentDescription = stringResource(
-                                R.string.accessibility_back_arrow_button
-                        ),
-                        modifier = Modifier.size(24.dp).autoMirrored(),
-                        tint = LocalAndroidColorScheme.current.onSurfaceVariant,
-                )
-            }
-        }
-        LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
-    }
-}
-
-@Composable
-fun MoreOptionTopAppBarWithCustomNavigation(
-        text: String,
-        onNavigationIconClicked: () -> Unit,
-        navigationIcon: ImageVector,
-        navigationIconContentDescription: String,
-        bottomPadding: Dp,
-) {
-    Row(
-            modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
-            verticalAlignment = Alignment.CenterVertically,
-    ) {
-        IconButton(
-                modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
-                onClick = onNavigationIconClicked
-        ) {
-            Box(
-                    modifier = Modifier.size(48.dp),
-                    contentAlignment = Alignment.Center,
-            ) {
-                Icon(
                         imageVector = navigationIcon,
                         contentDescription = navigationIconContentDescription,
                         modifier = Modifier.size(24.dp).autoMirrored(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index a0915d2..282a1b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -32,6 +32,8 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.NewReleases
 import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.outlined.QrCodeScanner
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -107,7 +109,7 @@
                                 onCancelFlowAndFinish = viewModel::onUserCancel,
                                 onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
                                 onMoreOptionSelected =
-                                viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+                                viewModel::createFlowOnMoreOptionsOnlySelectedOnCreationSelection,
                                 requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
                                 enabledProviderInfo = createCredentialUiState
                                         .activeEntry?.activeProvider!!,
@@ -120,6 +122,41 @@
                                 onBiometricPromptStateChange =
                                 viewModel::onBiometricPromptStateChange
                             )
+                        CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard(
+                                requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+                                enabledProviderList = createCredentialUiState.enabledProviders,
+                                disabledProviderList = createCredentialUiState.disabledProviders,
+                                sortedCreateOptionsPairs =
+                                createCredentialUiState.sortedCreateOptionsPairs,
+                                onBackCreationSelectionButtonSelected =
+                                viewModel::createFlowOnBackCreationSelectionButtonSelected,
+                                onOptionSelected =
+                                viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+                                onDisabledProvidersSelected =
+                                viewModel::createFlowOnLaunchSettings,
+                                onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+                                onLog = { viewModel.logUiEvent(it) },
+                                customTopAppBar = { MoreOptionTopAppBar(
+                                        text = stringResource(
+                                                R.string.save_credential_to_title,
+                                                when (createCredentialUiState.requestDisplayInfo
+                                                        .type) {
+                                                    CredentialType.PASSKEY ->
+                                                        stringResource(R.string.passkey)
+                                                    CredentialType.PASSWORD ->
+                                                        stringResource(R.string.password)
+                                                    CredentialType.UNKNOWN -> stringResource(
+                                                            R.string.sign_in_info)
+                                                }
+                                        ),
+                                        onNavigationIconClicked = viewModel::onUserCancel,
+                                        bottomPadding = 16.dp,
+                                        navigationIcon = Icons.Filled.Close,
+                                        navigationIconContentDescription = stringResource(
+                                                R.string.accessibility_close_button
+                                        )
+                                )}
+                        )
                         CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
                                 requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
                                 enabledProviderList = createCredentialUiState.enabledProviders,
@@ -207,22 +244,31 @@
     onDisabledProvidersSelected: () -> Unit,
     onRemoteEntrySelected: (EntryInfo) -> Unit,
     onLog: @Composable (UiEventEnum) -> Unit,
+    customTopAppBar: (@Composable() () -> Unit)? = null
 ) {
     SheetContainerCard(topAppBar = {
-        MoreOptionTopAppBar(
-            text = stringResource(
-                R.string.save_credential_to_title,
-                when (requestDisplayInfo.type) {
-                    CredentialType.PASSKEY ->
-                        stringResource(R.string.passkey)
-                    CredentialType.PASSWORD ->
-                        stringResource(R.string.password)
-                    CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
-                }
-            ),
-            onNavigationIconClicked = onBackCreationSelectionButtonSelected,
-            bottomPadding = 16.dp,
-        )
+        if (customTopAppBar != null) {
+            customTopAppBar()
+        } else {
+            MoreOptionTopAppBar(
+                    text = stringResource(
+                            R.string.save_credential_to_title,
+                            when (requestDisplayInfo.type) {
+                                CredentialType.PASSKEY ->
+                                    stringResource(R.string.passkey)
+                                CredentialType.PASSWORD ->
+                                    stringResource(R.string.password)
+                                CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
+                            }
+                    ),
+                    onNavigationIconClicked = onBackCreationSelectionButtonSelected,
+                    bottomPadding = 16.dp,
+                    navigationIcon = Icons.Filled.ArrowBack,
+                    navigationIconContentDescription = stringResource(
+                            R.string.accessibility_back_arrow_button
+                    )
+            )
+        }
     }) {
         // bottom padding already
         item {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index ddd4139..130937c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -181,4 +181,5 @@
   MORE_OPTIONS_SELECTION,
   DEFAULT_PROVIDER_CONFIRMATION,
   EXTERNAL_ONLY_SELECTION,
+  MORE_OPTIONS_SELECTION_ONLY,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ce4f402..c98bb5e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.outlined.QrCodeScanner
 import androidx.compose.material3.Divider
@@ -71,7 +72,6 @@
 import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
 import com.android.credentialmanager.common.ui.ModalBottomSheet
 import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
-import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation
 import com.android.credentialmanager.common.ui.SheetContainerCard
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.common.ui.SnackbarActionText
@@ -175,7 +175,7 @@
                                     onBackButtonClicked = viewModel::onUserCancel,
                                     onCancel = viewModel::onUserCancel,
                                     onLog = { viewModel.logUiEvent(it) },
-                                    customTopBar = { MoreOptionTopAppBarWithCustomNavigation(
+                                    customTopBar = { MoreOptionTopAppBar(
                                             text = stringResource(
                                                     R.string.get_dialog_title_sign_in_options),
                                             onNavigationIconClicked = viewModel::onUserCancel,
@@ -683,7 +683,10 @@
                     text = stringResource(R.string.get_dialog_title_sign_in_options),
                     onNavigationIconClicked = onBackButtonClicked,
                     bottomPadding = 0.dp,
-            )
+                    navigationIcon = Icons.Filled.ArrowBack,
+                    navigationIconContentDescription = stringResource(
+                            R.string.accessibility_back_arrow_button
+            ))
         }
     }) {
         var isFirstSection = true
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,7 @@
     sdk_version: "system_current",
     rename_resources_package: false,
     static_libs: [
+        "xz-java",
         "androidx.leanback_leanback",
         "androidx.annotation_annotation",
         "androidx.fragment_fragment",
@@ -78,6 +79,7 @@
     overrides: ["PackageInstaller"],
 
     static_libs: [
+        "xz-java",
         "androidx.leanback_leanback",
         "androidx.fragment_fragment",
         "androidx.lifecycle_lifecycle-livedata",
@@ -110,6 +112,7 @@
     overrides: ["PackageInstaller"],
 
     static_libs: [
+        "xz-java",
         "androidx.leanback_leanback",
         "androidx.annotation_annotation",
         "androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 05f4d69..bf69d3b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,6 +146,17 @@
                 android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
                 android:exported="false" />
 
+        <!-- Wearable Components -->
+        <service android:name=".wear.WearPackageInstallerService"
+                 android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+                 android:foregroundServiceType="systemExempted"
+                 android:exported="true"/>
+
+        <provider android:name=".wear.WearPackageIconProvider"
+                  android:authorities="com.google.android.packageinstaller.wear.provider"
+                  android:grantUriPermissions="true"
+                  android:exported="false" />
+
         <receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
             tools:node="remove" />
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
new file mode 100644
index 0000000..53a460d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 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.wear;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Task that installs an APK. This must not be called on the main thread.
+ * This code is based off the Finsky/Wearsky implementation
+ */
+public class InstallTask {
+    private static final String TAG = "InstallTask";
+
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    private final Context mContext;
+    private String mPackageName;
+    private ParcelFileDescriptor mParcelFileDescriptor;
+    private PackageInstallerImpl.InstallListener mCallback;
+    private PackageInstaller.Session mSession;
+    private IntentSender mCommitCallback;
+
+    private Exception mException = null;
+    private int mErrorCode = 0;
+    private String mErrorDesc = null;
+
+    public InstallTask(Context context, String packageName,
+            ParcelFileDescriptor parcelFileDescriptor,
+            PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
+            IntentSender commitCallback) {
+        mContext = context;
+        mPackageName = packageName;
+        mParcelFileDescriptor = parcelFileDescriptor;
+        mCallback = callback;
+        mSession = session;
+        mCommitCallback = commitCallback;
+    }
+
+    public boolean isError() {
+        return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
+    }
+
+    public void execute() {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new IllegalStateException("This method cannot be called from the UI thread.");
+        }
+
+        OutputStream sessionStream = null;
+        try {
+            sessionStream = mSession.openWrite(mPackageName, 0, -1);
+
+            // 2b: Stream the asset to the installer. Note:
+            // Note: writeToOutputStreamFromAsset() always safely closes the input stream
+            writeToOutputStreamFromAsset(sessionStream);
+            mSession.fsync(sessionStream);
+        } catch (Exception e) {
+            mException = e;
+            mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
+            mErrorDesc = "Could not write to stream";
+        } finally {
+            if (sessionStream != null) {
+                // 2c: close output stream
+                try {
+                    sessionStream.close();
+                } catch (Exception e) {
+                    // Ignore otherwise
+                    if (mException == null) {
+                        mException = e;
+                        mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
+                        mErrorDesc = "Could not close session stream";
+                    }
+                }
+            }
+        }
+
+        if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
+            // An error occurred, we're done
+            Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
+                    + mErrorDesc + ", " + mException);
+            mSession.close();
+            mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
+        } else {
+            // 3. Commit the session (this actually installs it.)  Session map
+            // will be cleaned up in the callback.
+            mCallback.installBeginning();
+            mSession.commit(mCommitCallback);
+            mSession.close();
+        }
+    }
+
+    /**
+     * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
+     * corresponding to the {@code Asset} and then write the contents into an
+     * {@code OutputStream} that is passed in.
+     * <br>
+     * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
+     */
+    private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
+        if (outputStream == null) {
+            mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
+            mErrorDesc = "Got a null OutputStream.";
+            return false;
+        }
+
+        if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null)  {
+            mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
+            mErrorDesc = "Could not get FD";
+            return false;
+        }
+
+        InputStream inputStream = null;
+        try {
+            byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
+            int bytesRead;
+            inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
+
+            while ((bytesRead = inputStream.read(inputBuf)) > -1) {
+                if (bytesRead > 0) {
+                    outputStream.write(inputBuf, 0, bytesRead);
+                }
+            }
+
+            outputStream.flush();
+        } catch (IOException e) {
+            mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
+            mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
+            return false;
+        } finally {
+            safeClose(inputStream);
+        }
+
+        return true;
+    }
+
+    /**
+     * Quietly close a closeable resource (e.g. a stream or file). The input may already
+     * be closed and it may even be null.
+     */
+    public static void safeClose(Closeable resource) {
+        if (resource != null) {
+            try {
+                resource.close();
+            } catch (IOException ioe) {
+                // Catch and discard the error
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
new file mode 100644
index 0000000..3daf3d8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.wear;
+
+/**
+ * Constants for Installation / Uninstallation requests.
+ * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
+ */
+public class InstallerConstants {
+    /** Request succeeded */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * The new PackageInstaller also returns a small set of less granular error codes, which
+     * we'll remap to the range -500 and below to keep away from existing installer codes
+     * (which run from -1 to -110).
+     */
+    public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
+
+    public static final int ERROR_COULD_NOT_GET_FD = -603;
+    /** This node is not targeted by this request. */
+
+    /** The install did not complete because could not create PackageInstaller session */
+    public final static int ERROR_INSTALL_CREATE_SESSION = -612;
+    /** The install did not complete because could not open PackageInstaller session  */
+    public final static int ERROR_INSTALL_OPEN_SESSION = -613;
+    /** The install did not complete because could not open PackageInstaller output stream */
+    public final static int ERROR_INSTALL_OPEN_STREAM = -614;
+    /** The install did not complete because of an exception while streaming bytes */
+    public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
+    /** The install did not complete because of an unexpected exception from PackageInstaller */
+    public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
+    /** The install did not complete because of an unexpected userActionRequired callback */
+    public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
+    /** The install did not complete because of an unexpected broadcast (missing fields) */
+    public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
+    /** The install did not complete because of an error while copying from downloaded file */
+    public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
+    /** The install did not complete because of an error while copying to the PackageInstaller
+     * output stream */
+    public final static int ERROR_INSTALL_COPY_STREAM = -620;
+    /** The install did not complete because of an error while closing the PackageInstaller
+     * output stream */
+    public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
new file mode 100644
index 0000000..bdc22cf
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.wear;
+
+import android.content.Context;
+
+/**
+ * Factory that creates a Package Installer.
+ */
+public class PackageInstallerFactory {
+    private static PackageInstallerImpl sPackageInstaller;
+
+    /**
+     * Return the PackageInstaller shared object. {@code init} should have already been called.
+     */
+    public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
+        if (sPackageInstaller == null) {
+            sPackageInstaller = new PackageInstallerImpl(context);
+        }
+        return sPackageInstaller;
+    }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
new file mode 100644
index 0000000..1e37f15
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 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.wear;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of package manager installation using modern PackageInstaller api.
+ *
+ * Heavily copied from Wearsky/Finsky implementation
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class PackageInstallerImpl {
+    private static final String TAG = "PackageInstallerImpl";
+
+    /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
+    private static final String ACTION_INSTALL_COMMIT =
+            "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
+
+    private final Context mContext;
+    private final PackageInstaller mPackageInstaller;
+    private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
+    private final Map<String, PackageInstaller.Session> mOpenSessionMap;
+
+    public PackageInstallerImpl(Context context) {
+        mContext = context.getApplicationContext();
+        mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+
+        // Capture a map of known sessions
+        // This list will be pruned a bit later (stale sessions will be canceled)
+        mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
+        List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
+        for (int i = 0; i < mySessions.size(); i++) {
+            PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
+            String packageName = sessionInfo.getAppPackageName();
+            PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
+
+            // Checking for old info is strictly for logging purposes
+            if (oldInfo != null) {
+                Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
+                        .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
+            }
+        }
+        mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
+    }
+
+    /**
+     * This callback will be made after an installation attempt succeeds or fails.
+     */
+    public interface InstallListener {
+        /**
+         * This callback signals that preflight checks have succeeded and installation
+         * is beginning.
+         */
+        void installBeginning();
+
+        /**
+         * This callback signals that installation has completed.
+         */
+        void installSucceeded();
+
+        /**
+         * This callback signals that installation has failed.
+         */
+        void installFailed(int errorCode, String errorDesc);
+    }
+
+    /**
+     * This is a placeholder implementation that bundles an entire "session" into a single
+     * call. This will be replaced by more granular versions that allow longer session lifetimes,
+     * download progress tracking, etc.
+     *
+     * This must not be called on main thread.
+     */
+    public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
+            final InstallListener callback) {
+        // 0. Generic try/catch block because I am not really sure what exceptions (other than
+        // IOException) might be thrown by PackageInstaller and I want to handle them
+        // at least slightly gracefully.
+        try {
+            // 1. Create or recover a session, and open it
+            // Try recovery first
+            PackageInstaller.Session session = null;
+            PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+            if (sessionInfo != null) {
+                // See if it's openable, or already held open
+                session = getSession(packageName);
+            }
+            // If open failed, or there was no session, create a new one and open it.
+            // If we cannot create or open here, the failure is terminal.
+            if (session == null) {
+                try {
+                    innerCreateSession(packageName);
+                } catch (IOException ioe) {
+                    Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
+                    callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
+                            "Could not create session");
+                    mSessionInfoMap.remove(packageName);
+                    return;
+                }
+                sessionInfo = mSessionInfoMap.get(packageName);
+                try {
+                    session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+                    mOpenSessionMap.put(packageName, session);
+                } catch (SecurityException se) {
+                    Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
+                    callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
+                            "Can't open session");
+                    mSessionInfoMap.remove(packageName);
+                    return;
+                }
+            }
+
+            // 2. Launch task to handle file operations.
+            InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
+                    callback, session,
+                    getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
+            task.execute();
+            if (task.isError()) {
+                cancelSession(sessionInfo.getSessionId(), packageName);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
+                    + e.getMessage());
+            callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
+                    "Unexpected exception while installing " + packageName);
+        }
+    }
+
+    /**
+     * Retrieve an existing session. Will open if needed, but does not attempt to create.
+     */
+    private PackageInstaller.Session getSession(String packageName) {
+        // Check for already-open session
+        PackageInstaller.Session session = mOpenSessionMap.get(packageName);
+        if (session != null) {
+            try {
+                // Probe the session to ensure that it's still open. This may or may not
+                // throw (if non-open), but it may serve as a canary for stale sessions.
+                session.getNames();
+                return session;
+            } catch (IOException ioe) {
+                Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
+                mOpenSessionMap.remove(packageName);
+            } catch (SecurityException se) {
+                Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
+                mOpenSessionMap.remove(packageName);
+            }
+        }
+        // Check to see if this is a known session
+        PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+        if (sessionInfo == null) {
+            return null;
+        }
+        // Try to open it. If we fail here, assume that the SessionInfo was stale.
+        try {
+            session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+        } catch (SecurityException se) {
+            Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
+            mSessionInfoMap.remove(packageName);
+            return null;
+        } catch (IOException ioe) {
+            Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
+                    + " - deleting info");
+            mSessionInfoMap.remove(packageName);
+            return null;
+        }
+        mOpenSessionMap.put(packageName, session);
+        return session;
+    }
+
+    /** This version throws an IOException when the session cannot be created */
+    private void innerCreateSession(String packageName) throws IOException {
+        if (mSessionInfoMap.containsKey(packageName)) {
+            Log.w(TAG, "Creating session for " + packageName + " when one already exists");
+            return;
+        }
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.setAppPackageName(packageName);
+
+        // IOException may be thrown at this point
+        int sessionId = mPackageInstaller.createSession(params);
+        PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
+        mSessionInfoMap.put(packageName, sessionInfo);
+    }
+
+    /**
+     * Cancel a session based on its sessionId. Package name is for logging only.
+     */
+    private void cancelSession(int sessionId, String packageName) {
+        // Close if currently held open
+        closeSession(packageName);
+        // Remove local record
+        mSessionInfoMap.remove(packageName);
+        try {
+            mPackageInstaller.abandonSession(sessionId);
+        } catch (SecurityException se) {
+            // The session no longer exists, so we can exit quietly.
+            return;
+        }
+    }
+
+    /**
+     * Close a session if it happens to be held open.
+     */
+    private void closeSession(String packageName) {
+        PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
+        if (session != null) {
+            // Unfortunately close() is not idempotent. Try our best to make this safe.
+            try {
+                session.close();
+            } catch (Exception e) {
+                Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
+                        + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Creates a commit callback for the package install that's underway. This will be called
+     * some time after calling session.commit() (above).
+     */
+    private IntentSender getCommitCallback(final String packageName, final int sessionId,
+            final InstallListener callback) {
+        // Create a single-use broadcast receiver
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mContext.unregisterReceiver(this);
+                handleCommitCallback(intent, packageName, sessionId, callback);
+            }
+        };
+        // Create a matching intent-filter and register the receiver
+        String action = ACTION_INSTALL_COMMIT + "." + packageName;
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(action);
+        mContext.registerReceiver(broadcastReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED);
+
+        // Create a matching PendingIntent and use it to generate the IntentSender
+        Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
+                broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
+                        | PendingIntent.FLAG_MUTABLE);
+        return pendingIntent.getIntentSender();
+    }
+
+    /**
+     * Examine the extras to determine information about the package update/install, decode
+     * the result, and call the appropriate callback.
+     *
+     * @param intent The intent, which the PackageInstaller will have added Extras to
+     * @param packageName The package name we created the receiver for
+     * @param sessionId The session Id we created the receiver for
+     * @param callback The callback to report success/failure to
+     */
+    private void handleCommitCallback(Intent intent, String packageName, int sessionId,
+            InstallListener callback) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Installation of " + packageName + " finished with extras "
+                    + intent.getExtras());
+        }
+        String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+        int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            cancelSession(sessionId, packageName);
+            callback.installSucceeded();
+        } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
+            // TODO - use the constant when the correct/final name is in the SDK
+            // TODO This is unexpected, so we are treating as failure for now
+            cancelSession(sessionId, packageName);
+            callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
+                    "Unexpected: user action required");
+        } else {
+            cancelSession(sessionId, packageName);
+            int errorCode = getPackageManagerErrorCode(status);
+            Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
+                    + statusMessage);
+            callback.installFailed(errorCode, null);
+        }
+    }
+
+    private int getPackageManagerErrorCode(int status) {
+        // This is a hack: because PackageInstaller now reports error codes
+        // with small positive values, we need to remap them into a space
+        // that is more compatible with the existing package manager error codes.
+        // See https://sites.google.com/a/google.com/universal-store/documentation
+        //       /android-client/download-error-codes
+        int errorCode;
+        if (status == Integer.MIN_VALUE) {
+            errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
+        } else {
+            errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
+        }
+        return errorCode;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 0000000..2c289b2
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+    private static final String KEY_PACKAGE_NAME =
+            "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+    private static final String KEY_ASSET_URI =
+            "com.google.android.clockwork.EXTRA_ASSET_URI";
+    private static final String KEY_START_ID =
+            "com.google.android.clockwork.EXTRA_START_ID";
+    private static final String KEY_PERM_URI =
+            "com.google.android.clockwork.EXTRA_PERM_URI";
+    private static final String KEY_CHECK_PERMS =
+            "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+    private static final String KEY_SKIP_IF_SAME_VERSION =
+            "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+    private static final String KEY_COMPRESSION_ALG =
+            "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+    private static final String KEY_COMPANION_SDK_VERSION =
+            "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+    private static final String KEY_COMPANION_DEVICE_VERSION =
+            "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+    private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+            "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+    private static final String KEY_SKIP_IF_LOWER_VERSION =
+            "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
+
+    public static String getPackageName(Bundle b) {
+        return b.getString(KEY_PACKAGE_NAME);
+    }
+
+    public static Bundle setPackageName(Bundle b, String packageName) {
+        b.putString(KEY_PACKAGE_NAME, packageName);
+        return b;
+    }
+
+    public static Uri getAssetUri(Bundle b) {
+        return b.getParcelable(KEY_ASSET_URI);
+    }
+
+    public static Uri getPermUri(Bundle b) {
+        return b.getParcelable(KEY_PERM_URI);
+    }
+
+    public static boolean checkPerms(Bundle b) {
+        return b.getBoolean(KEY_CHECK_PERMS);
+    }
+
+    public static boolean skipIfSameVersion(Bundle b) {
+        return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+    }
+
+    public static int getCompanionSdkVersion(Bundle b) {
+        return b.getInt(KEY_COMPANION_SDK_VERSION);
+    }
+
+    public static int getCompanionDeviceVersion(Bundle b) {
+        return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+    }
+
+    public static String getCompressionAlg(Bundle b) {
+        return b.getString(KEY_COMPRESSION_ALG);
+    }
+
+    public static int getStartId(Bundle b) {
+        return b.getInt(KEY_START_ID);
+    }
+
+    public static boolean skipIfLowerVersion(Bundle b) {
+        return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
+    }
+
+    public static Bundle setStartId(Bundle b, int startId) {
+        b.putInt(KEY_START_ID, startId);
+        return b;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 0000000..02b9d29
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 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.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+    private static final String TAG = "WearPackageIconProvider";
+    public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+    private static final String REQUIRED_PERMISSION =
+            "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+    /** MIME types. */
+    public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException("Query is not supported.");
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("URI passed in is null.");
+        }
+
+        if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+            return ICON_TYPE;
+        }
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException("Insert is not supported.");
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (uri == null) {
+            throw new IllegalArgumentException("URI passed in is null.");
+        }
+
+        enforcePermissions(uri);
+
+        if (ICON_TYPE.equals(getType(uri))) {
+            final File file = WearPackageUtil.getIconFile(
+                    this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+            if (file != null) {
+                file.delete();
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("Update is not supported.");
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(
+            Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+        if (uri == null) {
+            throw new IllegalArgumentException("URI passed in is null.");
+        }
+
+        enforcePermissions(uri);
+
+        if (ICON_TYPE.equals(getType(uri))) {
+            final File file = WearPackageUtil.getIconFile(
+                    this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+            if (file != null) {
+                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+            }
+        }
+        return null;
+    }
+
+    public static Uri getUriForPackage(final String packageName) {
+        return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+    }
+
+    private String getPackageNameFromUri(Uri uri) {
+        if (uri == null) {
+            return null;
+        }
+        List<String> pathSegments = uri.getPathSegments();
+        String packageName = pathSegments.get(pathSegments.size() - 1);
+
+        if (packageName.endsWith(".icon")) {
+            packageName = packageName.substring(0, packageName.lastIndexOf("."));
+        }
+        return packageName;
+    }
+
+    /**
+     * Make sure the calling app is either a system app or the same app or has the right permission.
+     * @throws SecurityException if the caller has insufficient permissions.
+     */
+    @TargetApi(Build.VERSION_CODES.BASE_1_1)
+    private void enforcePermissions(Uri uri) {
+        // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+        // allow System process to access this provider.
+        Context context = getContext();
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final int myUid = android.os.Process.myUid();
+
+        if (uid == myUid || isSystemApp(context, pid)) {
+            return;
+        }
+
+        if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+            return;
+        }
+
+        // last chance, check against any uri grants
+        if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                == PERMISSION_GRANTED) {
+            return;
+        }
+
+        throw new SecurityException("Permission Denial: reading "
+                + getClass().getName() + " uri " + uri + " from pid=" + pid
+                + ", uid=" + uid);
+    }
+
+    /**
+     * From the pid of the calling process, figure out whether this is a system app or not. We do
+     * this by checking the application information corresponding to the pid and then checking if
+     * FLAG_SYSTEM is set.
+     */
+    @TargetApi(Build.VERSION_CODES.CUPCAKE)
+    private boolean isSystemApp(Context context, int pid) {
+        // Get the Activity Manager Object
+        ActivityManager aManager =
+                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        // Get the list of running Applications
+        List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+                aManager.getRunningAppProcesses();
+        for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+            if (rapInfo.pid == pid) {
+                try {
+                    PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+                            rapInfo.pkgList[0], 0);
+                    if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+                            (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        Log.d(TAG, pid + " is a system app.");
+                        return true;
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.e(TAG, "Could not find package information.", e);
+                    return false;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 0000000..ae0f4ec
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2015 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.wear;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.Nullable;
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ *  Install Action example:
+ *  adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
+ *     -d package://com.google.android.gms \
+ *     --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ *     --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
+ *     --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
+ *     --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
+ *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ *  Uninstall Action example:
+ *  adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ *     -d package://com.google.android.gms \
+ *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ *  Retry GMS:
+ *  adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service
+        implements EventResultPersister.EventResultObserver {
+    private static final String TAG = "WearPkgInstallerService";
+
+    private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+    private static final String BROADCAST_ACTION =
+            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
+
+    private final int START_INSTALL = 1;
+    private final int START_UNINSTALL = 2;
+
+    private int mInstallNotificationId = 1;
+    private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+    private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+    private class UninstallParams {
+        public String mPackageName;
+        public PowerManager.WakeLock mLock;
+
+        UninstallParams(String packageName, PowerManager.WakeLock lock) {
+            mPackageName = packageName;
+            mLock = lock;
+        }
+    }
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case START_INSTALL:
+                    installPackage(msg.getData());
+                    break;
+                case START_UNINSTALL:
+                    uninstallPackage(msg.getData());
+                    break;
+            }
+        }
+    }
+    private ServiceHandler mServiceHandler;
+    private NotificationChannel mNotificationChannel;
+    private static volatile PowerManager.WakeLock lockStatic = null;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        HandlerThread thread = new HandlerThread("PackageInstallerThread",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        mServiceHandler = new ServiceHandler(thread.getLooper());
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (!DeviceUtils.isWear(this)) {
+            Log.w(TAG, "Not running on wearable.");
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        if (intent == null) {
+            Log.w(TAG, "Got null intent.");
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Got install/uninstall request " + intent);
+        }
+
+        Uri packageUri = intent.getData();
+        if (packageUri == null) {
+            Log.e(TAG, "No package URI in intent");
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+        if (packageName == null) {
+            Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+
+        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+        if (!lock.isHeld()) {
+            lock.acquire();
+        }
+
+        Bundle intentBundle = intent.getExtras();
+        if (intentBundle == null) {
+            intentBundle = new Bundle();
+        }
+        WearPackageArgs.setStartId(intentBundle, startId);
+        WearPackageArgs.setPackageName(intentBundle, packageName);
+        Message msg;
+        String notifTitle;
+        if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+            msg = mServiceHandler.obtainMessage(START_INSTALL);
+            notifTitle = getString(R.string.installing);
+        } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+            msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+            notifTitle = getString(R.string.uninstalling);
+        } else {
+            Log.e(TAG, "Unknown action : " + intent.getAction());
+            finishServiceEarly(startId);
+            return START_NOT_STICKY;
+        }
+        Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
+        startForeground(notifPair.first, notifPair.second);
+        msg.setData(intentBundle);
+        mServiceHandler.sendMessage(msg);
+        return START_NOT_STICKY;
+    }
+
+    private void installPackage(Bundle argsBundle) {
+        int startId = WearPackageArgs.getStartId(argsBundle);
+        final String packageName = WearPackageArgs.getPackageName(argsBundle);
+        final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+        final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+        boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+        boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+        int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+        int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+        String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+        boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+                    ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+                    checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+                    ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+                    companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
+                    ", skipIfLowerVersion: " + skipIfLowerVersion);
+        }
+        final PackageManager pm = getPackageManager();
+        File tempFile = null;
+        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+        boolean messageSent = false;
+        try {
+            PackageInfo existingPkgInfo = null;
+            try {
+                existingPkgInfo = pm.getPackageInfo(packageName,
+                        PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
+                if (existingPkgInfo != null) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Replacing package:" + packageName);
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Ignore this exception. We could not find the package, will treat as a new
+                // installation.
+            }
+            // TODO(28021618): This was left as a temp file due to the fact that this code is being
+            //       deprecated and that we need the bare minimum to continue working moving forward
+            //       If this code is used as reference, this permission logic might want to be
+            //       reworked to use a stream instead of a file so that we don't need to write a
+            //       file at all.  Note that there might be some trickiness with opening a stream
+            //       for multiple users.
+            ParcelFileDescriptor parcelFd = getContentResolver()
+                    .openFileDescriptor(assetUri, "r");
+            tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+                    parcelFd, packageName, compressionAlg);
+            if (tempFile == null) {
+                Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+                return;
+            }
+            PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
+                    PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
+            if (pkgInfo == null) {
+                Log.e(TAG, "Could not parse apk information for " + packageName);
+                return;
+            }
+
+            if (!pkgInfo.packageName.equals(packageName)) {
+                Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+                        packageName);
+                return;
+            }
+
+            ApplicationInfo appInfo = pkgInfo.applicationInfo;
+            appInfo.sourceDir = tempFile.getPath();
+            appInfo.publicSourceDir = tempFile.getPath();
+            getLabelAndUpdateNotification(packageName,
+                    getString(R.string.installing_app, appInfo.loadLabel(pm)));
+
+            List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
+
+            // Log if the installed pkg has a higher version number.
+            if (existingPkgInfo != null) {
+                long longVersionCode = pkgInfo.getLongVersionCode();
+                if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
+                    if (skipIfSameVersion) {
+                        Log.w(TAG, "Version number (" + longVersionCode +
+                                ") of new app is equal to existing app for " + packageName +
+                                "; not installing due to versionCheck");
+                        return;
+                    } else {
+                        Log.w(TAG, "Version number of new app (" + longVersionCode +
+                                ") is equal to existing app for " + packageName);
+                    }
+                } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
+                    if (skipIfLowerVersion) {
+                        // Starting in Feldspar, we are not going to allow downgrades of any app.
+                        Log.w(TAG, "Version number of new app (" + longVersionCode +
+                                ") is lower than existing app ( "
+                                + existingPkgInfo.getLongVersionCode() +
+                                ") for " + packageName + "; not installing due to versionCheck");
+                        return;
+                    } else {
+                        Log.w(TAG, "Version number of new app (" + longVersionCode +
+                                ") is lower than existing app ( "
+                                + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
+                    }
+                }
+
+                // Following the Android Phone model, we should only check for permissions for any
+                // newly defined perms.
+                if (existingPkgInfo.requestedPermissions != null) {
+                    for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+                        // If the permission is granted, then we will not ask to request it again.
+                        if ((existingPkgInfo.requestedPermissionsFlags[i] &
+                                PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+                                        " is already granted for " + packageName);
+                            }
+                            wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+                        }
+                    }
+                }
+            }
+
+            // Check that the wearable has all the features.
+            boolean hasAllFeatures = true;
+            for (FeatureInfo feature : pkgInfo.reqFeatures) {
+                if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+                        (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+                    Log.e(TAG, "Wearable does not have required feature: " + feature +
+                            " for " + packageName);
+                    hasAllFeatures = false;
+                }
+            }
+
+            if (!hasAllFeatures) {
+                return;
+            }
+
+            // Check permissions on both the new wearable package and also on the already installed
+            // wearable package.
+            // If the app is targeting API level 23, we will also start a service in ClockworkHome
+            // which will ultimately prompt the user to accept/reject permissions.
+            if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
+                    companionDeviceVersion, permUri, wearablePerms, tempFile)) {
+                Log.w(TAG, "Wearable does not have enough permissions.");
+                return;
+            }
+
+            // Finally install the package.
+            ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
+            PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
+                    new PackageInstallListener(this, lock, startId, packageName));
+
+            messageSent = true;
+            Log.i(TAG, "Sent installation request for " + packageName);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+        } finally {
+            if (!messageSent) {
+                // Some error happened. If the message has been sent, we can wait for the observer
+                // which will finish the service.
+                if (tempFile != null) {
+                    tempFile.delete();
+                }
+                finishService(lock, startId);
+            }
+        }
+    }
+
+    // TODO: This was left using the old PackageManager API due to the fact that this code is being
+    //       deprecated and that we need the bare minimum to continue working moving forward
+    //       If this code is used as reference, this logic should be reworked to use the new
+    //       PackageInstaller APIs similar to how installPackage was reworked
+    private void uninstallPackage(Bundle argsBundle) {
+        int startId = WearPackageArgs.getStartId(argsBundle);
+        final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+        UninstallParams params = new UninstallParams(packageName, lock);
+        mServiceIdToParams.put(startId, params);
+
+        final PackageManager pm = getPackageManager();
+        try {
+            PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+            getLabelAndUpdateNotification(packageName,
+                    getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+
+            int uninstallId = UninstallEventReceiver.addObserver(this,
+                    EventResultPersister.GENERATE_NEW_ID, this);
+
+            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+            broadcastIntent.setPackage(getPackageName());
+
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+                    broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                            | PendingIntent.FLAG_MUTABLE);
+
+            // Found package, send uninstall request.
+            pm.getPackageInstaller().uninstall(
+                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    PackageManager.DELETE_ALL_USERS,
+                    pendingIntent.getIntentSender());
+
+            Log.i(TAG, "Sent delete request for " + packageName);
+        } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
+            // Couldn't find the package, no need to call uninstall.
+            Log.w(TAG, "Could not find package, not deleting " + packageName, e);
+            finishService(lock, startId);
+        } catch (EventResultPersister.OutOfIdsException e) {
+            Log.e(TAG, "Fails to start uninstall", e);
+            finishService(lock, startId);
+        }
+    }
+
+    @Override
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+        if (mServiceIdToParams.containsKey(serviceId)) {
+            UninstallParams params = mServiceIdToParams.get(serviceId);
+            try {
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+                } else {
+                    Log.e(TAG, "Package uninstall failed " + params.mPackageName
+                            + ", returnCode " + legacyStatus);
+                }
+            } finally {
+                finishService(params.mLock, serviceId);
+            }
+        }
+    }
+
+    private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
+            int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
+            File apkFile) {
+        // Assumption: We are running on Android O.
+        // If the Phone App is targeting M, all permissions may not have been granted to the phone
+        // app. If the Wear App is then not targeting M, there may be permissions that are not
+        // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
+        // app.
+        if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+            // Install the app if Wear App is ready for the new perms model.
+            return true;
+        }
+
+        if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
+            // All permissions requested by the watch are already granted on the phone, no need
+            // to do anything.
+            return true;
+        }
+
+        // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
+        if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
+            Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
+                    + "phone app is targeting at least 23, will continue.");
+        }
+
+        return false;
+    }
+
+    /**
+     * Given a {@string packageName} corresponding to a phone app, query the provider for all the
+     * perms that are granted.
+     *
+     * @return true if the Wear App has any perms that have not been granted yet on the phone side.
+     * @return true if there is any error cases.
+     */
+    private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
+            List<String> wearablePermissions) {
+        if (permUri == null) {
+            Log.e(TAG, "Permission URI is null");
+            // Pretend there is an ungranted permission to avoid installing for error cases.
+            return true;
+        }
+        Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
+        if (permCursor == null) {
+            Log.e(TAG, "Could not get the cursor for the permissions");
+            // Pretend there is an ungranted permission to avoid installing for error cases.
+            return true;
+        }
+
+        Set<String> grantedPerms = new HashSet<>();
+        Set<String> ungrantedPerms = new HashSet<>();
+        while(permCursor.moveToNext()) {
+            // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+            // verify their types.
+            if (permCursor.getColumnCount() == 2
+                    && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+                    && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+                String perm = permCursor.getString(0);
+                Integer granted = permCursor.getInt(1);
+                if (granted == 1) {
+                    grantedPerms.add(perm);
+                } else {
+                    ungrantedPerms.add(perm);
+                }
+            }
+        }
+        permCursor.close();
+
+        boolean hasUngrantedPerm = false;
+        for (String wearablePerm : wearablePermissions) {
+            if (!grantedPerms.contains(wearablePerm)) {
+                hasUngrantedPerm = true;
+                if (!ungrantedPerms.contains(wearablePerm)) {
+                    // This is an error condition. This means that the wearable has permissions that
+                    // are not even declared in its host app. This is a developer error.
+                    Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+                            + "\" that is not defined in the host application's manifest.");
+                } else {
+                    Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+                            "\" that is not granted in the host application.");
+                }
+            }
+        }
+        return hasUngrantedPerm;
+    }
+
+    /** Finishes the service after fulfilling obligation to call startForeground. */
+    private void finishServiceEarly(int startId) {
+        Pair<Integer, Notification> notifPair = buildNotification(
+                getApplicationContext().getPackageName(), "");
+        startForeground(notifPair.first, notifPair.second);
+        finishService(null, startId);
+    }
+
+    private void finishService(PowerManager.WakeLock lock, int startId) {
+        if (lock != null && lock.isHeld()) {
+            lock.release();
+        }
+        stopSelf(startId);
+    }
+
+    private synchronized PowerManager.WakeLock getLock(Context context) {
+        if (lockStatic == null) {
+            PowerManager mgr =
+                    (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            lockStatic = mgr.newWakeLock(
+                    PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+            lockStatic.setReferenceCounted(true);
+        }
+        return lockStatic;
+    }
+
+    private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
+        private Context mContext;
+        private PowerManager.WakeLock mWakeLock;
+        private int mStartId;
+        private String mApplicationPackageName;
+        private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
+                int startId, String applicationPackageName) {
+            mContext = context;
+            mWakeLock = wakeLock;
+            mStartId = startId;
+            mApplicationPackageName = applicationPackageName;
+        }
+
+        @Override
+        public void installBeginning() {
+            Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
+        }
+
+        @Override
+        public void installSucceeded() {
+            try {
+                Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
+
+                // Delete tempFile from the file system.
+                File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
+                if (tempFile != null) {
+                    tempFile.delete();
+                }
+            } finally {
+                finishService(mWakeLock, mStartId);
+            }
+        }
+
+        @Override
+        public void installFailed(int errorCode, String errorDesc) {
+            Log.e(TAG, "Package install failed " + mApplicationPackageName
+                    + ", errorCode " + errorCode);
+            finishService(mWakeLock, mStartId);
+        }
+    }
+
+    private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
+            final String title) {
+        int notifId;
+        if (mNotifIdMap.containsKey(packageName)) {
+            notifId = mNotifIdMap.get(packageName);
+        } else {
+            notifId = mInstallNotificationId++;
+            mNotifIdMap.put(packageName, notifId);
+        }
+
+        if (mNotificationChannel == null) {
+            mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
+                    getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
+            NotificationManager notificationManager = getSystemService(NotificationManager.class);
+            notificationManager.createNotificationChannel(mNotificationChannel);
+        }
+        return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
+            .setSmallIcon(R.drawable.ic_file_download)
+            .setContentTitle(title)
+            .build());
+    }
+
+    private void getLabelAndUpdateNotification(String packageName, String title) {
+        // Update notification since we have a label now.
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
+        notificationManager.notify(notifPair.first, notifPair.second);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 0000000..6a9145d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.wear;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WearPackageUtil {
+    private static final String TAG = "WearablePkgInstaller";
+
+    private static final String COMPRESSION_LZMA = "lzma";
+    private static final String COMPRESSION_XZ = "xz";
+
+    public static File getTemporaryFile(Context context, String packageName) {
+        try {
+            File newFileDir = new File(context.getFilesDir(), "tmp");
+            newFileDir.mkdirs();
+            Os.chmod(newFileDir.getAbsolutePath(), 0771);
+            File newFile = new File(newFileDir, packageName + ".apk");
+            return newFile;
+        }   catch (ErrnoException e) {
+            Log.e(TAG, "Failed to open.", e);
+            return null;
+        }
+    }
+
+    public static File getIconFile(final Context context, final String packageName) {
+        try {
+            File newFileDir = new File(context.getFilesDir(), "images/icons");
+            newFileDir.mkdirs();
+            Os.chmod(newFileDir.getAbsolutePath(), 0771);
+            return new File(newFileDir, packageName + ".icon");
+        }   catch (ErrnoException e) {
+            Log.e(TAG, "Failed to open.", e);
+            return null;
+        }
+    }
+
+    /**
+     * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+     * by the PackageManager, we will parse it before sending it to the PackageManager.
+     * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
+     * the fd to a File.
+     *
+     * @param context
+     * @param fd FileDescriptor to convert to File
+     * @param packageName Name of package, will define the name of the file
+     * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+     *                       decompress it here
+     */
+    public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+            String packageName, String compressionAlg) {
+        File newFile = getTemporaryFile(context, packageName);
+        if (fd == null || fd.getFileDescriptor() == null)  {
+            return null;
+        }
+        InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+        try {
+            if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+                fr = new XZInputStream(fr);
+            } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+                fr = new LZMAInputStream(fr);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+            return null;
+        }
+
+        int nRead;
+        byte[] data = new byte[1024];
+        try {
+            final FileOutputStream fo = new FileOutputStream(newFile);
+            while ((nRead = fr.read(data, 0, data.length)) != -1) {
+                fo.write(data, 0, nRead);
+            }
+            fo.flush();
+            fo.close();
+            Os.chmod(newFile.getAbsolutePath(), 0644);
+            return newFile;
+        } catch (IOException e) {
+            Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+            return null;
+        }   catch (ErrnoException e) {
+            Log.e(TAG, "Could not set permissions on file ", e);
+            return null;
+        } finally {
+            try {
+                fr.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to close the file from FD ", e);
+            }
+        }
+    }
+
+    /**
+     * @return com.google.com from expected formats like
+     * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+     */
+    public static String getSanitizedPackageName(Uri packageUri) {
+        String packageName = packageUri.getEncodedSchemeSpecificPart();
+        if (packageName != null) {
+            return packageName.replaceAll("^/+", "");
+        }
+        return packageName;
+    }
+}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
new file mode 100644
index 0000000..fadcf7b
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+<resources>
+    <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+        <item name="elevationOverlayEnabled">true</item>
+        <item name="elevationOverlayColor">?attr/colorPrimary</item>
+        <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+        <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
new file mode 100644
index 0000000..0c20287
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+<resources>
+    <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">20dp</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+    </style>
+
+    <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
+        <item name="android:textSize">36dp</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
new file mode 100644
index 0000000..7c9d1a4
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+<resources>
+    <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+        <item name="elevationOverlayEnabled">true</item>
+        <item name="elevationOverlayColor">?attr/colorPrimary</item>
+        <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+        <item name="colorAccent">@color/settingslib_materialColorPrimary</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
deleted file mode 100644
index c7fbb5f..0000000
--- a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2024 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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="@color/settingslib_materialColorOnSurfaceVariant"
-        android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
-</vector>
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
deleted file mode 100644
index a2b9648..0000000
--- a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2024 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.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeight"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:background="?android:attr/selectableItemBackground"
-    android:orientation="vertical"
-    android:clipToPadding="false">
-
-    <LinearLayout
-        android:id="@+id/icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="56dp"
-        android:gravity="start|top"
-        android:orientation="horizontal"
-        android:paddingEnd="12dp"
-        android:paddingTop="16dp"
-        android:paddingBottom="4dp">
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="start"
-            android:textAlignment="viewStart"
-            android:paddingTop="16dp"
-            android:paddingBottom="8dp"
-            android:textColor="@color/settingslib_materialColorOnSurfaceVariant"
-            android:hyphenationFrequency="normalFast"
-            android:lineBreakWordStyle="phrase"
-            android:ellipsize="marquee" />
-
-        <com.android.settingslib.widget.LinkTextView
-            android:id="@+id/settingslib_learn_more"
-            android:text="@string/settingslib_learn_more_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="start"
-            android:textAlignment="viewStart"
-            android:paddingBottom="8dp"
-            android:clickable="true"
-            android:visibility="gone" />
-    </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
new file mode 100644
index 0000000..e2ed1e4
--- /dev/null
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -0,0 +1,21 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "SettingsLibGraph-srcs",
+    srcs: ["src/**/*"],
+}
+
+android_library {
+    name: "SettingsLibGraph",
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+    srcs: [":SettingsLibGraph-srcs"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.preference_preference",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Graph/AndroidManifest.xml b/packages/SettingsLib/Graph/AndroidManifest.xml
new file mode 100644
index 0000000..93acb35
--- /dev/null
+++ b/packages/SettingsLib/Graph/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.graph">
+
+  <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
new file mode 100644
index 0000000..9231f40
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
@@ -0,0 +1,70 @@
+package com.android.settingslib.graph
+
+import androidx.annotation.StringRes
+import androidx.annotation.XmlRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+
+/** Manager to create and initialize preference screen. */
+class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
+    private val context = preferenceManager.context
+    // the map will preserve order
+    private val updaters = mutableMapOf<String, PreferenceUpdater>()
+    private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
+
+    /** Creates an empty [PreferenceScreen]. */
+    fun createPreferenceScreen(): PreferenceScreen =
+        preferenceManager.createPreferenceScreen(context)
+
+    /** Creates [PreferenceScreen] from resource. */
+    fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
+        preferenceManager.inflateFromResource(context, xmlRes, null)
+
+    /** Adds updater for given preference. */
+    fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
+        addPreferenceUpdater(context.getString(key), updater)
+
+    /** Adds updater for given preference. */
+    fun addPreferenceUpdater(
+        key: String,
+        updater: PreferenceUpdater,
+    ): PreferenceScreenManager {
+        updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
+        return this
+    }
+
+    /** Adds updater for preference screen. */
+    fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
+        screenUpdaters.add(updater)
+        return this
+    }
+
+    /** Adds a list of updaters for preference screen. */
+    fun addPreferenceScreenUpdater(
+        vararg updaters: PreferenceScreenUpdater,
+    ): PreferenceScreenManager {
+        screenUpdaters.addAll(updaters)
+        return this
+    }
+
+    /** Updates preference screen with registered updaters. */
+    fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
+        for ((key, updater) in updaters) {
+            preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
+        }
+        for (updater in screenUpdaters) {
+            updater.updatePreferenceScreen(preferenceScreen)
+        }
+    }
+}
+
+/** Updater of [Preference]. */
+interface PreferenceUpdater {
+    fun updatePreference(preference: Preference)
+}
+
+/** Updater of [PreferenceScreen]. */
+interface PreferenceScreenUpdater {
+    fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
new file mode 100644
index 0000000..9e4c1f6
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
@@ -0,0 +1,26 @@
+package com.android.settingslib.graph
+
+import android.content.Context
+import androidx.preference.PreferenceScreen
+
+/**
+ * Interface to provide [PreferenceScreen].
+ *
+ * It is expected to be implemented by Activity/Fragment and the implementation needs to use
+ * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
+ * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
+ * `onDestroy`, etc.) are not invoked.
+ */
+interface PreferenceScreenProvider {
+
+    /**
+     * Creates [PreferenceScreen].
+     *
+     * Preference screen creation could happen in background service. The implementation MUST use
+     * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
+     */
+    fun createPreferenceScreen(
+        context: Context,
+        preferenceScreenManager: PreferenceScreenManager,
+    ): PreferenceScreen?
+}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@color/settingslib_materialColorOnPrimaryContainer"
+          android:alpha="?android:attr/disabledAlpha" />
+    <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="@color/settingslib_materialColorOnPrimaryContainer"
+          android:alpha="?android:attr/disabledAlpha" />
+    <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..5192a9a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2024 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/settingslib_materialColorSecondaryFixed" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..4b16832
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2024 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/settingslib_materialColorPrimaryFixed" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
index 81ddf29..1429e3b 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
@@ -14,7 +14,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
   -->
-
+<!--Deprecated. After sdk 35 don't use it.-->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="@android:color/system_accent2_500" android:lStar="51" />
 </selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..eedc364
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Disabled status of thumb -->
+    <item android:state_enabled="false"
+          android:color="@color/settingslib_materialColorOutline"
+          android:alpha="?android:attr/disabledAlpha" />
+    <!-- Toggle off status of thumb -->
+    <item android:state_checked="false"
+          android:color="@color/settingslib_materialColorOutline" />
+    <!-- Enabled or toggle on status of thumb -->
+    <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
index 037b80a..b46181e 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -13,6 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in light theme	-->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
 </selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
index 762bb31..f0bcf0a 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
+<!--Deprecated. After sdk 35 don't use it.-->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="@android:color/system_neutral2_500" android:lStar="45" />
 </selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
new file mode 100644
index 0000000..4ced9f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+    <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+    <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+    <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..eedc364
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Disabled status of thumb -->
+    <item android:state_enabled="false"
+          android:color="@color/settingslib_materialColorOutline"
+          android:alpha="?android:attr/disabledAlpha" />
+    <!-- Toggle off status of thumb -->
+    <item android:state_checked="false"
+          android:color="@color/settingslib_materialColorOutline" />
+    <!-- Enabled or toggle on status of thumb -->
+    <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
new file mode 100644
index 0000000..230eb7d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="?android:attr/disabledAlpha"
+        android:color="@color/settingslib_materialColorOnSurface"/>
+    <item android:color="@color/settingslib_materialColorOnSurface"/>
+</selector>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
new file mode 100644
index 0000000..5bd2a29
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="?android:attr/disabledAlpha"
+        android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+    <item android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
new file mode 100644
index 0000000..3cb3435
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@android:id/background">
+        <shape>
+            <corners android:radius="8dp" />
+            <solid android:color="@color/settingslib_materialColorSurfaceVariant" />
+        </shape>
+    </item>
+
+    <item
+        android:id="@android:id/progress">
+        <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true">
+            <shape>
+                <corners android:radius="8dp" />
+                <solid android:color="?android:attr/textColorPrimary" />
+                <size android:width="8dp"/>
+            </shape>
+        </scale>
+    </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
new file mode 100644
index 0000000..285ab73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:radius="?android:attr/dialogCornerRadius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
new file mode 100644
index 0000000..e417307
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:topLeftRadius="0dp"
+                android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+                android:topRightRadius="0dp"
+                android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
new file mode 100644
index 0000000..e964657
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:radius="1dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
new file mode 100644
index 0000000..a9d69c2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:left="?android:attr/listPreferredItemPaddingStart"
+        android:right="?android:attr/listPreferredItemPaddingEnd"
+        android:top="1dp">
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_preference_bg_color" />
+            <corners
+                android:topLeftRadius="?android:attr/dialogCornerRadius"
+                android:bottomLeftRadius="0dp"
+                android:topRightRadius="?android:attr/dialogCornerRadius"
+                android:bottomRightRadius="0dp" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 5411591..0a36a4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -29,17 +29,20 @@
     <color name="settingslib_track_off_color">@android:color/system_neutral1_700</color>
 
     <!-- Dialog accent color -->
+    <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
     <color name="settingslib_dialog_accent">@android:color/system_accent1_100</color>
     <!-- Dialog background color. -->
     <color name="settingslib_dialog_background">@color/settingslib_surface_dark</color>
     <!-- Dialog error color. -->
     <color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
 
     <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
 
     <!-- copy from accent_primary_variant_dark_device_default-->
+    <!-- TODO: deprecate it after moving into partner code-->
     <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
 
     <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
@@ -48,7 +51,9 @@
 
     <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
 
+    <!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in dark theme	-->
     <color name="settingslib_surface_dark">@android:color/system_neutral1_800</color>
 
+    <!--Deprecated. After sdk 35, don't use it-->
     <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
index beed90e..8cfe54f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
@@ -38,7 +38,8 @@
     <color name="settingslib_track_off_color">@android:color/system_surface_container_highest_dark
     </color>
 
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
     <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color>
-
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
     <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 229d9e3..7c76ea1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -16,6 +16,34 @@
 -->
 
 <resources>
+    <!-- Material next state on color-->
+    <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+    <!-- Material next state off color-->
+    <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+    <!-- Material next thumb disable color-->
+    <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+    <!-- Material next thumb off color-->
+    <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+    <!-- Material next thumb off color-->
+    <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+    <!-- Material next track on color-->
+    <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+    <!-- Material next track off color-->
+    <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+    <!-- Dialog background color. -->
+    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+    <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+    <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
     <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color>
     <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color>
     <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index fe47e85..7706e0e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -35,49 +35,57 @@
     <color name="settingslib_track_off_color">@color/settingslib_switch_track_off</color>
 
     <!-- Dialog accent color -->
+    <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
     <color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
     <!-- Dialog background color -->
     <color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
     <!-- Dialog error color. -->
     <color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
 
     <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
 
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
 
     <!-- copy from accent_primary_variant_light_device_default-->
+    <!-- TODO: deprecate it after moving into partner code-->
     <color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
-
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
-
+    <!--Deprecated. After sdk 35 don't use it.using materialColorOnSurfaceInverse in dark theme-->
     <color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
-
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceInverse in light theme-->
     <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
-
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
     <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
-
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
     <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
 
     <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
 
     <color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
 
-    <color name="settingslib_material_grey_900">#ff212121</color>
-
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color>
-
+    <!--Deprecated. After sdk 35 don't use it.-->
     <color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
 
+    <!--Deprecated. After sdk 35, don't use it-->
     <color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
 
     <color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index e4befc2..a9534c3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
 <resources>
     <style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
         <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+        <item name="preferenceCategoryTitleTextColor">@color/settingslib_text_color_preference_category_title</item>
         <item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
         <item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
         <item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
index 24e3c46..fb637fb 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
@@ -16,9 +16,11 @@
   -->
 
 <resources>
-    <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
+    <style name="Theme.SettingsBase_v33" parent="Theme.SettingsBase_v31" >
         <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
         <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
         <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
     </style>
+
+    <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v33" />
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
index 3709b5d..185ac3e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
@@ -39,8 +39,8 @@
 
     <!-- Material next track outline color-->
     <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
-
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
     <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color>
-
+    <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
     <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2691344..2a6499a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -16,12 +16,42 @@
 -->
 
 <resources>
+    <!-- Material next state on color-->
+    <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+    <!-- Material next state off color-->
+    <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+    <!-- Material next thumb disable color-->
+    <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+    <!-- Material next thumb off color-->
+    <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+    <!-- Material next thumb off color-->
+    <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+    <!-- Material next track on color-->
+    <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+    <!-- Material next track off color-->
+    <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+    <!-- Dialog background color. -->
+    <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+    <!-- Material next track outline color-->
+    <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
+
+    <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+    <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
     <!-- The text color of spinner title -->
     <color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color>
     <!-- The text color of dropdown item title -->
     <color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color>
 
-
     <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color>
     <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color>
     <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
deleted file mode 100644
index fff41c3..0000000
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2024 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.
-  -->
-<resources>
-    <style name="TextAppearance.TopIntroText"
-        parent="@android:style/TextAppearance.DeviceDefault">
-        <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
-    </style>
-
-</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index 01dfd7d..cdd5c25 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -1,31 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2024 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.
+    Copyright (C) 2024 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.
   -->
 
 <resources>
-    <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
-        <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
-        <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
-        <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
-
+    <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
         <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
-        <!-- component module background -->
         <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
         <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
         <item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
         <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
     </style>
+
+    <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" />
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index f73081a..169c330 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -51,6 +51,7 @@
     };
     private static final int[] MICROPHONE_OPS = new int[]{
             AppOpsManager.OP_RECORD_AUDIO,
+            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
     };
     private static final int[] CAMERA_OPS = new int[]{
             AppOpsManager.OP_CAMERA,
@@ -144,6 +145,11 @@
             if (!showSystemApps) {
                 for (int op : mOps) {
                     final String permission = AppOpsManager.opToPermission(op);
+                    if (permission == null) {
+                        // Some ops like OP_PHONE_CALL_MICROPHONE don't have corresponding
+                        // permissions. No need to check in this case.
+                        continue;
+                    }
                     final int permissionFlags = mPackageManager.getPermissionFlags(permission,
                             packageName,
                             user);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 8fd4e91..822a608 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -18,14 +18,18 @@
 
 import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;
 
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.IDeviceIdleController;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.telecom.DefaultDialerManager;
 import android.text.TextUtils;
@@ -121,6 +125,14 @@
             return true;
         }
 
+        if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+            // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
+            final int userId = UserHandle.getUserId(uid);
+            if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
+                return true;
+            }
+        }
+
         return false;
     }
 
@@ -163,27 +175,77 @@
 
     /**
      * Add app into power save allow list.
-     * @param pkg packageName
+     * @param pkg packageName of the app
      */
+    // TODO: Fix all callers to pass in UID
     public void addApp(String pkg) {
+        addApp(pkg, Process.INVALID_UID);
+    }
+
+    /**
+     * Add app into power save allow list.
+     * @param pkg packageName of the app
+     * @param uid uid of the app
+     */
+    public void addApp(String pkg, int uid) {
         try {
+            if (android.app.Flags.appRestrictionsApi()) {
+                if (uid == Process.INVALID_UID) {
+                    uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+                }
+                final boolean wasInList = isAllowlisted(pkg, uid);
+
+                if (!wasInList) {
+                    mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+                            pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+                            true, ActivityManager.RESTRICTION_REASON_USER,
+                            "settings", 0);
+                }
+            }
+
             mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
             mAllowlistedApps.add(pkg);
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Unable to find package", e);
         }
     }
 
     /**
      * Remove package from power save allow list.
-     * @param pkg
+     * @param pkg packageName of the app
      */
     public void removeApp(String pkg) {
+        removeApp(pkg, Process.INVALID_UID);
+    }
+
+    /**
+     * Remove package from power save allow list.
+     * @param pkg packageName of the app
+     * @param uid uid of the app
+     */
+    public void removeApp(String pkg, int uid) {
         try {
+            if (android.app.Flags.appRestrictionsApi()) {
+                if (uid == Process.INVALID_UID) {
+                    uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+                }
+                final boolean wasInList = isAllowlisted(pkg, uid);
+                if (wasInList) {
+                    mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+                            pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+                            false, ActivityManager.RESTRICTION_REASON_USER,
+                            "settings", 0);
+                }
+            }
+
             mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
             mAllowlistedApps.remove(pkg);
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Unable to find package", e);
         }
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index b656253..0d318c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -96,7 +96,7 @@
         assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
         assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
 
-        mPowerAllowlistBackend.addApp(PACKAGE_TWO);
+        mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID);
 
         verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
         assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -104,7 +104,7 @@
         assertThat(mPowerAllowlistBackend.isAllowlisted(
                 new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue();
 
-        mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
+        mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID);
 
         verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
         assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -112,7 +112,7 @@
         assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
         assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
 
-        mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
+        mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID);
 
         verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
         assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse();
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index a33c160..75c0cec 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -272,6 +272,7 @@
         Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
         Settings.Secure.AUDIO_DEVICE_INVENTORY,
         Settings.Secure.SCREEN_RESOLUTION_MODE,
-        Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS
+        Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1bff592..8faf917 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -430,5 +430,7 @@
         VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator(
                 Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
+        VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+                new InclusiveIntegerRangeValidator(0, 10));
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 46cee6b..fa9b279 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1760,6 +1760,9 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
                 SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+                SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER_SATURATION_LEVEL);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
                 SecureSettingsProto.Accessibility.DISPLAY_INVERSION_ENABLED);
         dumpSetting(s, p,
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 4cbc18c..f65d797 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -24,10 +24,7 @@
 
 java_library_host {
     name: "SystemUILintChecker",
-    srcs: [
-        "src/**/*.kt",
-        "src/**/*.java",
-    ],
+    srcs: ["src/**/*.kt"],
     plugins: ["auto_service_plugin"],
     libs: [
         "auto_service_annotations",
@@ -38,35 +35,13 @@
 
 java_test_host {
     name: "SystemUILintCheckerTest",
-    srcs: [
-        "tests/**/*.kt",
-        "tests/**/*.java",
-    ],
+    defaults: ["AndroidLintCheckerTestDefaults"],
+    srcs: ["tests/**/*.kt"],
     data: [
         ":framework",
-        ":androidx.annotation_annotation",
+        ":androidx.annotation_annotation-nodeps",
     ],
     static_libs: [
         "SystemUILintChecker",
-        "junit",
-        "lint",
-        "lint_tests",
     ],
-    test_options: {
-        unit_test: true,
-        tradefed_options: [
-            {
-                // lint bundles in some classes that were built with older versions
-                // of libraries, and no longer load. Since tradefed tries to load
-                // all classes in the jar to look for tests, it crashes loading them.
-                // Exclude these classes from tradefed's search.
-                name: "exclude-paths",
-                value: "org/apache",
-            },
-            {
-                name: "exclude-paths",
-                value: "META-INF",
-            },
-        ],
-    },
 }
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
index 30e2a25..f9bf306 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
@@ -34,7 +34,6 @@
  * Checks if any class has implemented the `Dumpable` interface but has not registered itself with
  * the `DumpManager`.
  */
-@Suppress("UnstableApiUsage")
 class DumpableNotRegisteredDetector : Detector(), SourceCodeScanner {
 
     private var isDumpable: Boolean = false
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 5840e8f..024c394 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -59,7 +59,7 @@
                             `BroadcastDispatcher` instead, which registers the receiver on a \
                              background thread. `BroadcastDispatcher` also improves our visibility \
                              into ANRs.""",
-                            moreInfo = "go/identifying-broadcast-threads",
+                            moreInfo = "http://go/identifying-broadcast-threads",
                     category = Category.PERFORMANCE,
                     priority = 8,
                     severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 141dd05..f3b24a3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -16,15 +16,8 @@
 
 package com.android.internal.systemui.lint
 
-import com.android.annotations.NonNull
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
 import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
 import java.io.File
-import org.intellij.lang.annotations.Language
-
-@Suppress("UnstableApiUsage")
-@NonNull
-private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
 
 /*
  * This file contains stubs of framework APIs and System UI classes for testing purposes only. The
@@ -33,16 +26,5 @@
 internal val androidStubs =
     arrayOf(
         LibraryReferenceTestFile(File("framework.jar").canonicalFile),
-        LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
-        indentedJava(
-            """
-package com.android.systemui.settings;
-import android.content.pm.UserInfo;
-
-public interface UserTracker {
-    int getUserId();
-    UserInfo getUserInfo();
-}
-"""
-        ),
+        LibraryReferenceTestFile(File("androidx.annotation_annotation-nodeps.jar").canonicalFile),
     )
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 4c4185d..c9bc8b3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -19,17 +19,14 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
 
     override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
 
-    @Ignore
     @Test
     fun testBindService() {
         lint()
@@ -37,7 +34,9 @@
                 TestFiles.java(
                         """
                     package test.pkg;
+
                     import android.content.Context;
+                    import android.content.Intent;
 
                     public class TestClass {
                         public void bind(Context context) {
@@ -48,13 +47,13 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BindServiceOnMainThreadDetector.ISSUE)
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
+                src/test/pkg/TestClass.java:9: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
                       context.bindService(intent, null, 0);
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -62,7 +61,6 @@
             )
     }
 
-    @Ignore
     @Test
     fun testBindServiceAsUser() {
         lint()
@@ -70,7 +68,9 @@
                 TestFiles.java(
                         """
                     package test.pkg;
+
                     import android.content.Context;
+                    import android.content.Intent;
                     import android.os.UserHandle;
 
                     public class TestClass {
@@ -82,13 +82,13 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BindServiceOnMainThreadDetector.ISSUE)
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
+                src/test/pkg/TestClass.java:10: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
                       context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -114,7 +114,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BindServiceOnMainThreadDetector.ISSUE)
             .run()
@@ -147,7 +147,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BindServiceOnMainThreadDetector.ISSUE)
             .run()
@@ -181,7 +181,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BindServiceOnMainThreadDetector.ISSUE)
             .run()
@@ -219,12 +219,10 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BindServiceOnMainThreadDetector.ISSUE)
             .run()
             .expectClean()
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 30b68f7..3788dda 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = BroadcastSentViaContextDetector()
@@ -30,7 +29,6 @@
 
     @Test
     fun testSendBroadcast() {
-        println(stubs.size)
         lint()
             .files(
                 TestFiles.java(
@@ -47,7 +45,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
@@ -80,7 +78,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
@@ -114,7 +112,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
@@ -149,7 +147,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
@@ -176,7 +174,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
@@ -201,12 +199,10 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(BroadcastSentViaContextDetector.ISSUE)
             .run()
             .expectClean()
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
index ff150c8c..2c20321 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -21,11 +21,8 @@
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
-@Ignore("b/254533331")
 class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
     override fun getDetector(): Detector {
         return CleanArchitectureDependencyViolationDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index ee6e0ce..0652e69 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -24,7 +24,6 @@
 import java.util.EnumSet
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
index 3d6cbc7..6c6c263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
     override fun getDetector(): Detector = DumpableNotRegisteredDetector()
 
@@ -37,7 +36,8 @@
 
                     class SomeClass() {
                     }
-                """.trimIndent()
+                """
+                        .trimIndent()
                 ),
                 *stubs,
             )
@@ -67,7 +67,8 @@
                             pw.println("testDump");
                         }
                     }
-                """.trimIndent()
+                """
+                        .trimIndent()
                 ),
                 *stubs,
             )
@@ -97,7 +98,8 @@
                             pw.println("testDump");
                         }
                     }
-                """.trimIndent()
+                """
+                        .trimIndent()
                 ),
                 *stubs,
             )
@@ -127,7 +129,8 @@
                             pw.println("testDump");
                         }
                     }
-                """.trimIndent()
+                """
+                        .trimIndent()
                 ),
                 *stubs,
             )
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index ed3d14a..bb34d91 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = NonInjectedMainThreadDetector()
@@ -46,7 +45,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedMainThreadDetector.ISSUE)
             .run()
@@ -79,7 +78,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedMainThreadDetector.ISSUE)
             .run()
@@ -104,7 +103,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedMainThreadDetector.ISSUE)
             .run()
@@ -136,7 +135,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedMainThreadDetector.ISSUE)
             .run()
@@ -149,6 +148,4 @@
                 """
             )
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 846129a..fe5b576 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = NonInjectedServiceDetector()
@@ -44,7 +43,7 @@
                         """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedServiceDetector.ISSUE)
             .run()
@@ -76,7 +75,7 @@
                         """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedServiceDetector.ISSUE)
             .run()
@@ -109,7 +108,7 @@
                         """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedServiceDetector.ISSUE)
             .run()
@@ -134,7 +133,7 @@
                         """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(NonInjectedServiceDetector.ISSUE)
             .run()
@@ -147,6 +146,4 @@
                 """
             )
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 0ac8f8e..3f12569dfc 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
@@ -35,9 +34,8 @@
                 TestFiles.java(
                         """
                     package test.pkg;
-                    import android.content.BroadcastReceiver;
+
                     import android.content.Context;
-                    import android.content.IntentFilter;
 
                     public class TestClass {
                         public void bind(Context context, BroadcastReceiver receiver,
@@ -48,13 +46,13 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(RegisterReceiverViaContextDetector.ISSUE)
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+                src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
                       context.registerReceiver(receiver, filter, 0);
                               ~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -69,9 +67,8 @@
                 TestFiles.java(
                         """
                     package test.pkg;
-                    import android.content.BroadcastReceiver;
+
                     import android.content.Context;
-                    import android.content.IntentFilter;
 
                     @SuppressWarnings("RegisterReceiverViaContext")
                     public class TestClass {
@@ -83,7 +80,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(RegisterReceiverViaContextDetector.ISSUE)
             .run()
@@ -97,11 +94,8 @@
                 TestFiles.java(
                         """
                     package test.pkg;
-                    import android.content.BroadcastReceiver;
+
                     import android.content.Context;
-                    import android.content.IntentFilter;
-                    import android.os.Handler;
-                    import android.os.UserHandle;
 
                     public class TestClass {
                         public void bind(Context context, BroadcastReceiver receiver,
@@ -113,13 +107,13 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(RegisterReceiverViaContextDetector.ISSUE)
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+                src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
                       context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
                               ~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
@@ -134,11 +128,8 @@
                 TestFiles.java(
                         """
                     package test.pkg;
-                    import android.content.BroadcastReceiver;
+
                     import android.content.Context;
-                    import android.content.IntentFilter;
-                    import android.os.Handler;
-                    import android.os.UserHandle;
 
                     public class TestClass {
                         public void bind(Context context, BroadcastReceiver receiver,
@@ -150,19 +141,17 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(RegisterReceiverViaContextDetector.ISSUE)
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+                src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
                       context.registerReceiverForAllUsers(receiver, filter, "permission",
                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 0 errors, 1 warnings
                 """
             )
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 34a4249..7944ce3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -182,5 +181,19 @@
             .expectClean()
     }
 
-    private val stubs = androidStubs
+    private val stubs =
+        arrayOf(
+            *androidStubs,
+            java(
+                    """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+public interface UserTracker {
+    int getUserId();
+    UserInfo getUserInfo();
+}
+"""
+                )
+                .indented()
+        )
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 34becc6..756751c 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = SoftwareBitmapDetector()
@@ -45,7 +44,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(SoftwareBitmapDetector.ISSUE)
             .run()
@@ -80,7 +79,7 @@
                 """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(SoftwareBitmapDetector.ISSUE)
             .run()
@@ -102,12 +101,10 @@
                     }
                 """
                 ),
-                *stubs
+                *androidStubs
             )
             .issues(SoftwareBitmapDetector.ISSUE)
             .run()
             .expectClean()
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index efe4c90..fd00018 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -21,7 +21,6 @@
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
 
     override fun getDetector(): Detector = StaticSettingsProviderDetector()
@@ -85,7 +84,7 @@
                         """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(StaticSettingsProviderDetector.ISSUE)
             .run()
@@ -226,12 +225,10 @@
                         """
                     )
                     .indented(),
-                *stubs
+                *androidStubs
             )
             .issues(StaticSettingsProviderDetector.ISSUE)
             .run()
             .expectClean()
     }
-
-    private val stubs = androidStubs
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 3f93f07..29b3828 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -10,7 +10,6 @@
 import org.junit.runners.JUnit4
 import org.junit.runners.model.Statement
 
-@Suppress("UnstableApiUsage")
 @RunWith(JUnit4::class)
 abstract class SystemUILintDetectorTest : LintDetectorTest() {
 
@@ -18,7 +17,7 @@
         @ClassRule
         @JvmField
         val libraryChecker: LibraryExists =
-            LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+            LibraryExists("framework.jar", "androidx.annotation_annotation-nodeps.jar")
     }
 
     class LibraryExists(vararg val libraryNames: String) : TestRule {
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
index db73154..a4e82a7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
@@ -18,28 +18,22 @@
 package com.android.internal.systemui.lint
 
 import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
 
-@Suppress("UnstableApiUsage")
 class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
-    override fun getDetector(): Detector {
-        return TestFunctionNameViolationDetector()
-    }
+    override fun getDetector(): Detector = TestFunctionNameViolationDetector()
 
-    override fun getIssues(): List<Issue> {
-        return listOf(
-            TestFunctionNameViolationDetector.ISSUE,
-        )
-    }
+    override fun getIssues(): List<Issue> = listOf(TestFunctionNameViolationDetector.ISSUE)
 
     @Test
     fun violations() {
         lint()
             .files(
-                kotlin(
-                    """
+                TestFiles.kotlin(
+                        """
                     package test.pkg.name
 
                     import org.junit.Test
@@ -64,13 +58,11 @@
                         }
                     }
                 """
-                        .trimIndent()
-                ),
-                testAnnotationStub,
+                    )
+                    .indented(),
+                testAnnotationStub
             )
-            .issues(
-                TestFunctionNameViolationDetector.ISSUE,
-            )
+            .issues(TestFunctionNameViolationDetector.ISSUE)
             .run()
             .expectWarningCount(0)
             .expect(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 4533f58..356bfe2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -11,6 +11,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.dimensionResource
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
@@ -24,11 +25,10 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
-import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
@@ -75,6 +75,7 @@
     viewModel: CommunalViewModel,
     dataSourceDelegator: SceneDataSourceDelegator,
     dialogFactory: SystemUIDialogFactory,
+    colors: CommunalColors,
 ) {
     val coroutineScope = rememberCoroutineScope()
     val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
@@ -135,7 +136,7 @@
                     emptyMap()
                 },
         ) {
-            CommunalScene(viewModel, dialogFactory, modifier = modifier)
+            CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
         }
     }
 }
@@ -143,15 +144,18 @@
 /** Scene containing the glanceable hub UI. */
 @Composable
 private fun SceneScope.CommunalScene(
-    viewModel: BaseCommunalViewModel,
+    viewModel: CommunalViewModel,
+    colors: CommunalColors,
     dialogFactory: SystemUIDialogFactory,
     modifier: Modifier = Modifier,
 ) {
+    val backgroundColor by colors.backgroundColor.collectAsState()
+
     Box(
         modifier =
             Modifier.element(Communal.Elements.Scrim)
                 .fillMaxSize()
-                .background(LocalAndroidColorScheme.current.outlineVariant),
+                .background(Color(backgroundColor.toArgb())),
     )
     Box(modifier.element(Communal.Elements.Content)) {
         CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5d87a03..94515d3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -250,7 +250,6 @@
                             )
                         isDraggingToRemove
                     },
-                    onOpenWidgetPicker = onOpenWidgetPicker,
                     gridState = gridState,
                     contentListState = contentListState,
                     selectedKey = selectedKey,
@@ -393,7 +392,6 @@
     contentListState: ContentListState,
     setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     updateDragPositionForRemove: (offset: Offset) -> Boolean,
-    onOpenWidgetPicker: (() -> Unit)? = null,
     widgetConfigurator: WidgetConfigurator?,
 ) {
     var gridModifier =
@@ -461,7 +459,6 @@
                         model = list[index],
                         viewModel = viewModel,
                         size = size,
-                        onOpenWidgetPicker = onOpenWidgetPicker,
                         selected = selected && !isDragging,
                         widgetConfigurator = widgetConfigurator,
                     )
@@ -739,7 +736,6 @@
     size: SizeF,
     selected: Boolean,
     modifier: Modifier = Modifier,
-    onOpenWidgetPicker: (() -> Unit)? = null,
     widgetConfigurator: WidgetConfigurator? = null,
 ) {
     when (model) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index dee2559..37fe798 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -161,9 +161,9 @@
     private var isOnRemoveButton = false
 
     fun onStarted() {
-        // assume item will be added to the second to last position before CTA tile.
+        // assume item will be added to the end.
+        contentListState.list.add(placeHolder)
         placeHolderIndex = contentListState.list.size - 1
-        placeHolderIndex?.let { contentListState.list.add(it, placeHolder) }
     }
 
     fun onMoved(event: DragAndDropEvent) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index dc3b612..418c6bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -44,10 +44,27 @@
     internal val scope = SceneScopeImpl(layoutImpl, this)
 
     var content by mutableStateOf(content)
-    var userActions by mutableStateOf(actions)
+    private var _userActions by mutableStateOf(checkValid(actions))
     var zIndex by mutableFloatStateOf(zIndex)
     var targetSize by mutableStateOf(IntSize.Zero)
 
+    var userActions
+        get() = _userActions
+        set(value) {
+            _userActions = checkValid(value)
+        }
+
+    private fun checkValid(
+        userActions: Map<UserAction, UserActionResult>
+    ): Map<UserAction, UserActionResult> {
+        userActions.forEach { (action, result) ->
+            if (key == result.toScene) {
+                error("Transition to the same scene is not supported. Scene $key, action $action")
+            }
+        }
+        return userActions
+    }
+
     @Composable
     @OptIn(ExperimentalComposeUiApi::class)
     fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 723a182..2eaccb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -48,10 +48,14 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,7 +66,7 @@
         private val LayoutSize = 300.dp
     }
 
-    private var currentScene by mutableStateOf(TestScenes.SceneA)
+    private var currentScene by mutableStateOf(SceneA)
     private lateinit var layoutState: SceneTransitionLayoutState
 
     // We use createAndroidComposeRule() here and not createComposeRule() because we need an
@@ -84,15 +88,15 @@
             modifier = Modifier.size(LayoutSize),
         ) {
             scene(
-                TestScenes.SceneA,
-                userActions = mapOf(Back to TestScenes.SceneB),
+                SceneA,
+                userActions = mapOf(Back to SceneB),
             ) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
                     Text("SceneA")
                 }
             }
-            scene(TestScenes.SceneB) {
+            scene(SceneB) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(
                         size = 100.dp,
@@ -102,7 +106,7 @@
                     Text("SceneB")
                 }
             }
-            scene(TestScenes.SceneC) {
+            scene(SceneC) {
                 Box(Modifier.fillMaxSize()) {
                     SharedFoo(
                         size = 150.dp,
@@ -144,42 +148,42 @@
         rule.onNodeWithText("SceneB").assertDoesNotExist()
         rule.onNodeWithText("SceneC").assertDoesNotExist()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         // Change to scene B. Only that scene is displayed.
-        currentScene = TestScenes.SceneB
+        currentScene = SceneB
         rule.onNodeWithText("SceneA").assertDoesNotExist()
         rule.onNodeWithText("SceneB").assertIsDisplayed()
         rule.onNodeWithText("SceneC").assertDoesNotExist()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
     }
 
     @Test
     fun testBack() {
         rule.setContent { TestContent() }
 
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         rule.activity.onBackPressed()
         rule.waitForIdle()
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
     }
 
     @Test
     fun testTransitionState() {
         rule.setContent { TestContent() }
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         // We will advance the clock manually.
         rule.mainClock.autoAdvance = false
 
         // Change the current scene. Until composition is triggered, this won't change the layout
         // state.
-        currentScene = TestScenes.SceneB
+        currentScene = SceneB
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
 
         // On the next frame, we will recompose because currentScene changed, which will start the
         // transition (i.e. it will change the transitionState to be a Transition) in a
@@ -187,8 +191,8 @@
         rule.mainClock.advanceTimeByFrame()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
         val transition = layoutState.transitionState as TransitionState.Transition
-        assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
-        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.fromScene).isEqualTo(SceneA)
+        assertThat(transition.toScene).isEqualTo(SceneB)
         assertThat(transition.progress).isEqualTo(0f)
 
         // Then, on the next frame, the animator we started gets its initial value and clock
@@ -216,7 +220,7 @@
         // B.
         rule.mainClock.advanceTimeByFrame()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
     }
 
     @Test
@@ -242,7 +246,7 @@
         // Go to scene B and let the animation start. See [testLayoutState()] and
         // [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
         // by 2 frames to be at the start of the animation.
-        currentScene = TestScenes.SceneB
+        currentScene = SceneB
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
 
@@ -251,7 +255,7 @@
 
         // Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
         // that B has a higher zIndex than A.
-        sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
+        sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneB))
 
         // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -273,7 +277,7 @@
             .of(DpOffset(25.dp, 25.dp))
 
         // Animate to scene C, let the animation start then go to the middle of the transition.
-        currentScene = TestScenes.SceneC
+        currentScene = SceneC
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -285,7 +289,7 @@
         val expectedLeft = 0.dp
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
-        sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
+        sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
         assertThat((layoutState.transitionState as TransitionState.Transition).progress)
             .isEqualTo(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
@@ -302,15 +306,15 @@
         // Wait for the transition to C to finish.
         rule.mainClock.advanceTimeBy(TestTransitionDuration)
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
 
         // Go back to scene A. This should happen instantly (once the animation started, i.e. after
         // 2 frames) given that we use a snap() animation spec.
-        currentScene = TestScenes.SceneA
+        currentScene = SceneA
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
         assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
-        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
     }
 
     @Test
@@ -346,4 +350,28 @@
             )
         }
     }
+
+    @Test
+    fun userActionFromSceneAToSceneA_throwsNotSupported() {
+        val exception: IllegalStateException =
+            assertThrows(IllegalStateException::class.java) {
+                rule.setContent {
+                    SceneTransitionLayout(
+                        state =
+                            updateSceneTransitionLayoutState(
+                                currentScene = currentScene,
+                                onChangeScene = { currentScene = it },
+                                transitions = EmptyTestTransitions
+                            ),
+                        modifier = Modifier.size(LayoutSize),
+                    ) {
+                        // from SceneA to SceneA
+                        scene(SceneA, userActions = mapOf(Back to SceneA), content = {})
+                    }
+                }
+            }
+
+        assertThat(exception).hasMessageThat().contains(Back.toString())
+        assertThat(exception).hasMessageThat().contains(SceneA.debugName)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index f0498de..1501d9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -51,7 +51,6 @@
                 statusBarStateController = statusBarStateController,
                 mainExecutor = mainExecutor,
                 legacyActivityStarter = { legacyActivityStarterInternal },
-                activityStarterInternal = { activityStarterInternal },
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
new file mode 100644
index 0000000..55e46dc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.wmshell
+
+import android.content.pm.UserInfo
+import android.graphics.Color
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
+import com.android.systemui.communal.util.fakeCommunalColors
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.SysUiState
+import com.android.systemui.model.sysUiState
+import com.android.systemui.notetask.NoteTaskInitializer
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.wm.shell.desktopmode.DesktopMode
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.onehanded.OneHanded
+import com.android.wm.shell.onehanded.OneHandedEventCallback
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback
+import com.android.wm.shell.pip.Pip
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.sysui.ShellInterface
+import java.util.Optional
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [WMShell].
+ *
+ * Build/Install/Run: atest SystemUITests:WMShellTest
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WMShellTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+
+    @Mock private lateinit var mShellInterface: ShellInterface
+    @Mock private lateinit var mScreenLifecycle: ScreenLifecycle
+    @Mock private lateinit var mPip: Pip
+    @Mock private lateinit var mSplitScreen: SplitScreen
+    @Mock private lateinit var mOneHanded: OneHanded
+    @Mock private lateinit var mNoteTaskInitializer: NoteTaskInitializer
+    @Mock private lateinit var mDesktopMode: DesktopMode
+    @Mock private lateinit var mRecentTasks: RecentTasks
+
+    private val mCommandQueue: CommandQueue = kosmos.commandQueue
+    private val mConfigurationController: ConfigurationController = kosmos.configurationController
+    private val mKeyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+    private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+    private val mSysUiState: SysUiState = kosmos.sysUiState
+    private val mWakefulnessLifecycle: WakefulnessLifecycle = kosmos.wakefulnessLifecycle
+    private val mUserTracker: UserTracker = kosmos.userTracker
+    private val mSysUiMainExecutor: Executor = kosmos.fakeExecutor
+    private val communalTransitionViewModel = kosmos.communalTransitionViewModel
+
+    private lateinit var underTest: WMShell
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val displayTracker = FakeDisplayTracker(mContext)
+
+        kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+
+        underTest =
+            WMShell(
+                mContext,
+                mShellInterface,
+                Optional.of(mPip),
+                Optional.of(mSplitScreen),
+                Optional.of(mOneHanded),
+                Optional.of(mDesktopMode),
+                Optional.of(mRecentTasks),
+                mCommandQueue,
+                mConfigurationController,
+                mKeyguardStateController,
+                mKeyguardUpdateMonitor,
+                mScreenLifecycle,
+                mSysUiState,
+                mWakefulnessLifecycle,
+                mUserTracker,
+                displayTracker,
+                mNoteTaskInitializer,
+                communalTransitionViewModel,
+                JavaAdapter(testScope.backgroundScope),
+                mSysUiMainExecutor
+            )
+    }
+
+    @Test
+    fun initPip_registersCommandQueueCallback() {
+        underTest.initPip(mPip)
+        verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+    }
+
+    @Test
+    fun initOneHanded_registersCallbacks() {
+        underTest.initOneHanded(mOneHanded)
+        verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+        verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer::class.java))
+        verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback::class.java))
+        verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback::class.java))
+    }
+
+    @Test
+    fun initDesktopMode_registersListener() {
+        underTest.initDesktopMode(mDesktopMode)
+        verify(mDesktopMode)
+            .addVisibleTasksListener(
+                any(VisibleTasksListener::class.java),
+                any(Executor::class.java)
+            )
+    }
+
+    @Test
+    fun initRecentTasks_registersListener() {
+        underTest.initRecentTasks(mRecentTasks)
+        verify(mRecentTasks).addAnimationStateListener(any(Executor::class.java), any())
+    }
+
+    @Test
+    @EnableFlags(FLAG_COMMUNAL_HUB)
+    fun initRecentTasks_setRecentsBackgroundColorWhenCommunal() =
+        testScope.runTest {
+            val black = Color.valueOf(Color.BLACK)
+            kosmos.fakeCommunalColors.setBackgroundColor(black)
+
+            kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
+
+            underTest.initRecentTasks(mRecentTasks)
+            runCurrent()
+            verify(mRecentTasks).setTransitionBackgroundColor(null)
+            verify(mRecentTasks, never()).setTransitionBackgroundColor(black)
+
+            setDocked(true)
+            // Make communal available
+            kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+
+            runCurrent()
+
+            verify(mRecentTasks).setTransitionBackgroundColor(black)
+        }
+
+    private fun TestScope.setDocked(docked: Boolean) {
+        kosmos.fakeDockManager.setIsDocked(docked)
+        val event =
+            if (docked) {
+                DockManager.STATE_DOCKED
+            } else {
+                DockManager.STATE_NONE
+            }
+        kosmos.fakeDockManager.setDockEvent(event)
+        runCurrent()
+    }
+
+    private companion object {
+        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index af8149f..0bd6d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -18,8 +18,15 @@
 
 import static android.view.WindowManager.LayoutParams;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -30,56 +37,123 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
 
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
-class FullscreenMagnificationController {
+class FullscreenMagnificationController implements ComponentCallbacks {
 
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
     private Supplier<SurfaceControlViewHost> mScvhSupplier;
-    private SurfaceControlViewHost mSurfaceControlViewHost;
+    private SurfaceControlViewHost mSurfaceControlViewHost = null;
+    private SurfaceControl mBorderSurfaceControl = null;
     private Rect mWindowBounds;
     private SurfaceControl.Transaction mTransaction;
     private View mFullscreenBorder = null;
     private int mBorderOffset;
     private final int mDisplayId;
     private static final Region sEmptyRegion = new Region();
+    private ValueAnimator mShowHideBorderAnimator;
+    private Executor mExecutor;
+    private boolean mFullscreenMagnificationActivated = false;
+    private final Configuration mConfiguration;
 
     FullscreenMagnificationController(
             @UiContext Context context,
+            Executor executor,
             AccessibilityManager accessibilityManager,
             WindowManager windowManager,
             Supplier<SurfaceControlViewHost> scvhSupplier) {
+        this(context, executor, accessibilityManager, windowManager, scvhSupplier,
+                new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
+    }
+
+    @VisibleForTesting
+    FullscreenMagnificationController(
+            @UiContext Context context,
+            @Main Executor executor,
+            AccessibilityManager accessibilityManager,
+            WindowManager windowManager,
+            Supplier<SurfaceControlViewHost> scvhSupplier,
+            SurfaceControl.Transaction transaction,
+            ValueAnimator valueAnimator) {
         mContext = context;
+        mExecutor = executor;
         mAccessibilityManager = accessibilityManager;
         mWindowManager = windowManager;
         mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mTransaction = new SurfaceControl.Transaction();
+        mTransaction = transaction;
         mScvhSupplier = scvhSupplier;
         mBorderOffset = mContext.getResources().getDimensionPixelSize(
                 R.dimen.magnifier_border_width_fullscreen_with_offset)
                 - mContext.getResources().getDimensionPixelSize(
                 R.dimen.magnifier_border_width_fullscreen);
         mDisplayId = mContext.getDisplayId();
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mShowHideBorderAnimator = valueAnimator;
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                if (isReverse) {
+                    // The animation was played in reverse, which means we are hiding the border.
+                    // We would like to perform clean up after the border is fully hidden.
+                    cleanUpBorder();
+                }
+            }
+        });
     }
 
+    private static ValueAnimator createNullTargetObjectAnimator(Context context) {
+        final ValueAnimator valueAnimator =
+                ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+        Interpolator interpolator = new AccelerateDecelerateInterpolator();
+        final long longAnimationDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_longAnimTime);
+
+        valueAnimator.setInterpolator(interpolator);
+        valueAnimator.setDuration(longAnimationDuration);
+        return valueAnimator;
+    }
+
+    /**
+     * Check the fullscreen magnification activation status, and proceed corresponding actions when
+     * there is an activation change.
+     */
     @UiThread
     void onFullscreenMagnificationActivationChanged(boolean activated) {
-        if (activated) {
-            createFullscreenMagnificationBorder();
-        } else {
-            removeFullscreenMagnificationBorder();
+        final boolean changed = (mFullscreenMagnificationActivated != activated);
+        if (changed) {
+            mFullscreenMagnificationActivated = activated;
+            if (activated) {
+                createFullscreenMagnificationBorder();
+            } else {
+                removeFullscreenMagnificationBorder();
+            }
         }
     }
 
+    /**
+     * This method should only be called when fullscreen magnification is changed from activated
+     * to inactivated.
+     */
     @UiThread
     private void removeFullscreenMagnificationBorder() {
+        mContext.unregisterComponentCallbacks(this);
+        mShowHideBorderAnimator.reverse();
+    }
+
+    private void cleanUpBorder() {
         if (mSurfaceControlViewHost != null) {
             mSurfaceControlViewHost.release();
             mSurfaceControlViewHost = null;
@@ -91,31 +165,57 @@
     }
 
     /**
-     * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
-     * and set negative offset to the border view to fill up the spaces between the border and the
-     * device corners.
+     * This method should only be called when fullscreen magnification is changed from inactivated
+     * to activated.
      */
     @UiThread
     private void createFullscreenMagnificationBorder() {
-        mFullscreenBorder = LayoutInflater.from(mContext)
-                .inflate(R.layout.fullscreen_magnification_border, null);
-        mSurfaceControlViewHost = mScvhSupplier.get();
-        mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+        onConfigurationChanged(mContext.getResources().getConfiguration());
+        mContext.registerComponentCallbacks(this);
 
-        SurfaceControl surfaceControl = mSurfaceControlViewHost
-                .getSurfacePackage().getSurfaceControl();
+        if (mSurfaceControlViewHost == null) {
+            // Create the view only if it does not exist yet. If we are trying to enable fullscreen
+            // magnification before it was fully disabled, we use the previous view instead of
+            // creating a new one.
+            mFullscreenBorder = LayoutInflater.from(mContext)
+                    .inflate(R.layout.fullscreen_magnification_border, null);
+            // Set the initial border view alpha manually so we won't show the border accidentally
+            // after we apply show() to the SurfaceControl and before the animation starts to run.
+            mFullscreenBorder.setAlpha(0f);
+            mShowHideBorderAnimator.setTarget(mFullscreenBorder);
+            mSurfaceControlViewHost = mScvhSupplier.get();
+            mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+            mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+        }
 
         mTransaction
-                .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
-                .setLayer(surfaceControl, Integer.MAX_VALUE)
-                .show(surfaceControl)
+                .addTransactionCommittedListener(
+                        mExecutor,
+                        () -> {
+                            if (mShowHideBorderAnimator.isRunning()) {
+                                // Since the method is only called when there is an activation
+                                // status change, the running animator is hiding the border.
+                                mShowHideBorderAnimator.reverse();
+                            } else {
+                                mShowHideBorderAnimator.start();
+                            }
+                        })
+                .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
+                .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
+                .show(mBorderSurfaceControl)
                 .apply();
 
-        mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+        mAccessibilityManager.attachAccessibilityOverlayToDisplay(
+                mDisplayId, mBorderSurfaceControl);
 
         applyTouchableRegion();
     }
 
+    /**
+     * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+     * and set negative offset to the border view to fill up the spaces between the border and the
+     * device corners.
+     */
     private LayoutParams getBorderLayoutParams() {
         LayoutParams params =  new LayoutParams(
                 mWindowBounds.width() + 2 * mBorderOffset,
@@ -137,4 +237,41 @@
         // all touch events to go through this view.
         surfaceControl.setTouchableRegion(sEmptyRegion);
     }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        onConfigurationChanged(configDiff);
+    }
+
+    @VisibleForTesting
+    void onConfigurationChanged(int configDiff) {
+        boolean reCreateWindow = false;
+        if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0
+                || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
+                || (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            updateDimensions();
+            mWindowBounds.set(mWindowManager.getCurrentWindowMetrics().getBounds());
+            reCreateWindow = true;
+        }
+
+        if (mFullscreenBorder != null && reCreateWindow) {
+            final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
+            final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
+            mSurfaceControlViewHost.relayout(newWidth, newHeight);
+        }
+    }
+
+    private void updateDimensions() {
+        mBorderOffset = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnifier_border_width_fullscreen_with_offset)
+                - mContext.getResources().getDimensionPixelSize(
+                        R.dimen.magnifier_border_width_fullscreen);
+    }
+
+    @Override
+    public void onLowMemory() {
+
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 70165f3..177d933 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -54,6 +54,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
@@ -71,6 +72,7 @@
     private final ModeSwitchesController mModeSwitchesController;
     private final Context mContext;
     private final Handler mHandler;
+    private final Executor mExecutor;
     private final AccessibilityManager mAccessibilityManager;
     private final CommandQueue mCommandQueue;
     private final OverviewProxyService mOverviewProxyService;
@@ -139,12 +141,13 @@
             DisplayIdIndexSupplier<FullscreenMagnificationController> {
 
         private final Context mContext;
+        private final Executor mExecutor;
 
-        FullscreenMagnificationControllerSupplier(Context context, Handler handler,
-                DisplayManager displayManager, SysUiState sysUiState,
-                SecureSettings secureSettings) {
+        FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
+                Executor executor) {
             super(displayManager);
             mContext = context;
+            mExecutor = executor;
         }
 
         @Override
@@ -156,6 +159,7 @@
             windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
             return new FullscreenMagnificationController(
                     windowContext,
+                    mExecutor,
                     windowContext.getSystemService(AccessibilityManager.class),
                     windowContext.getSystemService(WindowManager.class),
                     scvhSupplier);
@@ -200,13 +204,14 @@
     DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
 
     @Inject
-    public Magnification(Context context, @Main Handler mainHandler,
+    public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
             CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
             DisplayManager displayManager, AccessibilityLogger a11yLogger) {
         mContext = context;
         mHandler = mainHandler;
+        mExecutor = executor;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mCommandQueue = commandQueue;
         mModeSwitchesController = modeSwitchesController;
@@ -218,7 +223,7 @@
                 mHandler, mWindowMagnifierCallback,
                 displayManager, sysUiState, secureSettings);
         mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
-                context, mHandler, displayManager, sysUiState, secureSettings);
+                context, displayManager, mExecutor);
         mMagnificationSettingsSupplier = new SettingsSupplier(context,
                 mMagnificationSettingsControllerCallback, displayManager, secureSettings);
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 179fa87..eaca276 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -1,6 +1,9 @@
 package com.android.systemui.bouncer.ui.binder
 
 import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
 import androidx.compose.ui.platform.ComposeView
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
@@ -30,7 +33,24 @@
     ) {
         view.addView(
             ComposeView(view.context).apply {
-                setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+                repeatWhenAttached {
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        setViewTreeOnBackPressedDispatcherOwner(
+                            object : OnBackPressedDispatcherOwner {
+                                override val onBackPressedDispatcher =
+                                    OnBackPressedDispatcher().apply {
+                                        setOnBackInvokedDispatcher(
+                                            view.viewRootImpl.onBackInvokedDispatcher
+                                        )
+                                    }
+
+                                override val lifecycle: Lifecycle =
+                                    this@repeatWhenAttached.lifecycle
+                            }
+                        )
+                        setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+                    }
+                }
             }
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 72dcb26..27af99e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.communal.util.CommunalColorsImpl
 import com.android.systemui.communal.widgets.CommunalWidgetModule
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
@@ -60,6 +62,8 @@
     @Communal
     fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource
 
+    @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
+
     companion object {
         @Provides
         @Communal
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b3002cd..3f92223 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -63,7 +63,7 @@
 
     override val isEditMode = true
 
-    // Only widgets are editable. The CTA tile comes last in the list and remains visible.
+    // Only widgets are editable.
     override val communalContent: Flow<List<CommunalContentModel>> =
         communalInteractor.widgetContent.onEach { models ->
             logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index bdf4e72..1bee83b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.graphics.Color
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -28,6 +30,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.merge
@@ -38,6 +41,7 @@
 class CommunalTransitionViewModel
 @Inject
 constructor(
+    communalColors: CommunalColors,
     glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
     dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
@@ -68,4 +72,13 @@
             step.transitionState == TransitionState.FINISHED ||
                 step.transitionState == TransitionState.CANCELED
         }
+
+    val recentsBackgroundColor: Flow<Color?> =
+        combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor ->
+            if (showByDefault) {
+                backgroundColor
+            } else {
+                null
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
new file mode 100644
index 0000000..1e04fe7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.communal.util
+
+import android.content.Context
+import android.graphics.Color
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Wrapper around colors used for the communal UI. */
+interface CommunalColors {
+    /** The background color of the glanceable hub. */
+    val backgroundColor: StateFlow<Color>
+}
+
+@SysUISingleton
+class CommunalColorsImpl
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    private val context: Context,
+    configurationInteractor: ConfigurationInteractor,
+) : CommunalColors {
+    override val backgroundColor: StateFlow<Color> =
+        configurationInteractor.onAnyConfigurationChange
+            .map { loadBackgroundColor() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = loadBackgroundColor()
+            )
+
+    private fun loadBackgroundColor(): Color =
+        Color.valueOf(
+            Utils.getColorAttrDefaultColor(
+                context,
+                com.android.internal.R.attr.materialColorOutlineVariant
+            )
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index db6b8fe..9ada1ef 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -367,16 +367,6 @@
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
         sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false)
 
-    // TODO(b/254512728): Tracking Bug
-    @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
-
-
-    // TODO(b/270987164): Tracking Bug
-    @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
-
-    // TODO(b/273800936): Tracking Bug
-    @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
-
     // TODO(b/251205791): Tracking Bug
     @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index db4a7fa..b50ee57 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -71,8 +71,6 @@
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
@@ -219,10 +217,8 @@
     private final Region mExcludeRegion = new Region();
     private final Region mDesktopModeExcludeRegion = new Region();
     private final Region mUnrestrictedExcludeRegion = new Region();
-    private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
     private final Provider<BackGestureTfClassifierProvider>
             mBackGestureTfClassifierProviderProvider;
-    private final FeatureFlags mFeatureFlags;
     private final Provider<LightBarController> mLightBarControllerProvider;
 
     // The left side edge width where touch down is allowed
@@ -264,8 +260,6 @@
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
     private boolean mIsBackGestureAllowed;
-    private boolean mIsNewBackAffordanceEnabled;
-    private boolean mIsTrackpadGestureFeaturesEnabled;
     private boolean mIsTrackpadThreeFingerSwipe;
     private boolean mIsButtonForcedVisible;
 
@@ -413,9 +407,7 @@
             Optional<Pip> pipOptional,
             Optional<DesktopMode> desktopModeOptional,
             FalsingManager falsingManager,
-            Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
             Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
-            FeatureFlags featureFlags,
             Provider<LightBarController> lightBarControllerProvider) {
         mContext = context;
         mDisplayId = context.getDisplayId();
@@ -435,13 +427,9 @@
         mPipOptional = pipOptional;
         mDesktopModeOptional = desktopModeOptional;
         mFalsingManager = falsingManager;
-        mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
         mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
-        mFeatureFlags = featureFlags;
         mLightBarControllerProvider = lightBarControllerProvider;
         mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
-        mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
-                Flags.TRACKPAD_GESTURE_FEATURES);
         ComponentName recentsComponentName = ComponentName.unflattenFromString(
                 context.getString(com.android.internal.R.string.config_recentsComponentName));
         if (recentsComponentName != null) {
@@ -559,12 +547,10 @@
         mIsAttached = true;
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
-        if (mIsTrackpadGestureFeaturesEnabled) {
-            mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
-            int [] inputDevices = mInputManager.getInputDeviceIds();
-            for (int inputDeviceId : inputDevices) {
-                mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
-            }
+        mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
+        int [] inputDevices = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDevices) {
+            mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
         }
         updateIsEnabled();
         mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
@@ -616,9 +602,8 @@
         try {
             Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
 
-            mIsGestureHandlingEnabled =
-                    mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav
-                            && mIsTrackpadConnected);
+            mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav
+                    && mIsTrackpadConnected);
             boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
             if (isEnabled == mIsEnabled) {
                 return;
@@ -678,7 +663,6 @@
                         Choreographer.getInstance(), this::onInputEvent);
 
                 // Add a nav bar panel window
-                mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
                 resetEdgeBackPlugin();
                 mPluginManager.addPluginListener(
                         this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
@@ -701,12 +685,7 @@
     }
 
     private void resetEdgeBackPlugin() {
-        if (mIsNewBackAffordanceEnabled) {
-            setEdgeBackPlugin(
-                    mBackPanelControllerFactory.create(mContext));
-        } else {
-            setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
-        }
+        setEdgeBackPlugin(mBackPanelControllerFactory.create(mContext));
     }
 
     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -1001,8 +980,7 @@
                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
             }
 
-            mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
-                    mIsTrackpadGestureFeaturesEnabled, ev);
+            mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(ev);
 
             // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
             // ACTION_DOWN, in that case we should just reuse the old instance.
@@ -1027,7 +1005,7 @@
                     && !mGestureBlockingActivityRunning.get()
                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
                             mIsTrackpadThreeFingerSwipe)
-                    && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
+                    && !isTrackpadScroll(ev);
             if (mIsTrackpadThreeFingerSwipe) {
                 // Trackpad back gestures don't have zones, so we don't need to check if the down
                 // event is within insets.
@@ -1321,10 +1299,8 @@
         private final Optional<Pip> mPipOptional;
         private final Optional<DesktopMode> mDesktopModeOptional;
         private final FalsingManager mFalsingManager;
-        private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
         private final Provider<BackGestureTfClassifierProvider>
                 mBackGestureTfClassifierProviderProvider;
-        private final FeatureFlags mFeatureFlags;
         private final Provider<LightBarController> mLightBarControllerProvider;
 
         @Inject
@@ -1344,10 +1320,8 @@
                        Optional<Pip> pipOptional,
                        Optional<DesktopMode> desktopModeOptional,
                        FalsingManager falsingManager,
-                       Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
                        Provider<BackGestureTfClassifierProvider>
                                backGestureTfClassifierProviderProvider,
-                       FeatureFlags featureFlags,
                        Provider<LightBarController> lightBarControllerProvider) {
             mOverviewProxyService = overviewProxyService;
             mSysUiState = sysUiState;
@@ -1365,9 +1339,7 @@
             mPipOptional = pipOptional;
             mDesktopModeOptional = desktopModeOptional;
             mFalsingManager = falsingManager;
-            mNavBarEdgePanelProvider = navBarEdgePanelProvider;
             mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
-            mFeatureFlags = featureFlags;
             mLightBarControllerProvider = lightBarControllerProvider;
         }
 
@@ -1391,9 +1363,7 @@
                     mPipOptional,
                     mDesktopModeOptional,
                     mFalsingManager,
-                    mNavBarEdgePanelProvider,
                     mBackGestureTfClassifierProviderProvider,
-                    mFeatureFlags,
                     mLightBarControllerProvider);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
deleted file mode 100644
index 380846e..0000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ /dev/null
@@ -1,944 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.navigationbar.gestural;
-
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE;
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import androidx.core.graphics.ColorUtils;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.app.animation.Interpolators;
-import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.NavigationEdgeBackPlugin;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.VibratorHelper;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
-
-    private static final String TAG = "NavigationBarEdgePanel";
-
-    private static final boolean ENABLE_FAILSAFE = true;
-
-    private static final long COLOR_ANIMATION_DURATION_MS = 120;
-    private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
-    private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
-    private static final long FAILSAFE_DELAY_MS = 200;
-
-    /**
-     * The time required since the first vibration effect to automatically trigger a click
-     */
-    private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
-
-    /**
-     * The size of the protection of the arrow in px. Only used if this is not background protected
-     */
-    private static final int PROTECTION_WIDTH_PX = 2;
-
-    /**
-     * The basic translation in dp where the arrow resides
-     */
-    private static final int BASE_TRANSLATION_DP = 32;
-
-    /**
-     * The length of the arrow leg measured from the center to the end
-     */
-    private static final int ARROW_LENGTH_DP = 18;
-
-    /**
-     * The angle measured from the xAxis, where the leg is when the arrow rests
-     */
-    private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
-
-    /**
-     * The angle that is added per 1000 px speed to the angle of the leg
-     */
-    private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
-
-    /**
-     * The maximum angle offset allowed due to speed
-     */
-    private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
-
-    /**
-     * The thickness of the arrow. Adjusted to match the home handle (approximately)
-     */
-    private static final float ARROW_THICKNESS_DP = 2.5f;
-
-    /**
-     * The amount of rubber banding we do for the vertical translation
-     */
-    private static final int RUBBER_BAND_AMOUNT = 15;
-
-    /**
-     * The interpolator used to rubberband
-     */
-    private static final Interpolator RUBBER_BAND_INTERPOLATOR
-            = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
-
-    /**
-     * The amount of rubber banding we do for the translation before base translation
-     */
-    private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
-
-    /**
-     * The interpolator used to rubberband the appearing of the arrow.
-     */
-    private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR
-            = new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
-
-    private final WindowManager mWindowManager;
-    private final VibratorHelper mVibratorHelper;
-
-    /**
-     * The paint the arrow is drawn with
-     */
-    private final Paint mPaint = new Paint();
-    /**
-     * The paint the arrow protection is drawn with
-     */
-    private final Paint mProtectionPaint;
-
-    private final float mDensity;
-    private final float mBaseTranslation;
-    private final float mArrowLength;
-    private final float mArrowThickness;
-
-    /**
-     * The minimum delta needed in movement for the arrow to change direction / stop triggering back
-     */
-    private final float mMinDeltaForSwitch;
-    // The closest to y = 0 that the arrow will be displayed.
-    private int mMinArrowPosition;
-    // The amount the arrow is shifted to avoid the finger.
-    private int mFingerOffset;
-
-    private final float mSwipeTriggerThreshold;
-    private final float mSwipeProgressThreshold;
-    private final Path mArrowPath = new Path();
-    private final Point mDisplaySize = new Point();
-
-    private final SpringAnimation mAngleAnimation;
-    private final SpringAnimation mTranslationAnimation;
-    private final SpringAnimation mVerticalTranslationAnimation;
-    private final SpringForce mAngleAppearForce;
-    private final SpringForce mAngleDisappearForce;
-    private final ValueAnimator mArrowColorAnimator;
-    private final ValueAnimator mArrowDisappearAnimation;
-    private final SpringForce mRegularTranslationSpring;
-    private final SpringForce mTriggerBackSpring;
-    private final LatencyTracker mLatencyTracker;
-
-    private VelocityTracker mVelocityTracker;
-    private boolean mIsDark = false;
-    private boolean mShowProtection = false;
-    private int mProtectionColorLight;
-    private int mArrowPaddingEnd;
-    private int mArrowColorLight;
-    private int mProtectionColorDark;
-    private int mArrowColorDark;
-    private int mProtectionColor;
-    private int mArrowColor;
-    private RegionSamplingHelper mRegionSamplingHelper;
-    private final Rect mSamplingRect = new Rect();
-    private WindowManager.LayoutParams mLayoutParams;
-    private int mLeftInset;
-    private int mRightInset;
-
-    /**
-     * True if the panel is currently on the left of the screen
-     */
-    private boolean mIsLeftPanel;
-
-    private float mStartX;
-    private float mStartY;
-    private float mCurrentAngle;
-    /**
-     * The current translation of the arrow
-     */
-    private float mCurrentTranslation;
-    /**
-     * Where the arrow will be in the resting position.
-     */
-    private float mDesiredTranslation;
-
-    private boolean mDragSlopPassed;
-    private boolean mArrowsPointLeft;
-    private float mMaxTranslation;
-    private boolean mTriggerBack;
-    private float mPreviousTouchTranslation;
-    private float mTotalTouchDelta;
-    private float mVerticalTranslation;
-    private float mDesiredVerticalTranslation;
-    private float mDesiredAngle;
-    private float mAngleOffset;
-    private int mArrowStartColor;
-    private int mCurrentArrowColor;
-    private float mDisappearAmount;
-    private long mVibrationTime;
-    private int mScreenSize;
-    private boolean mTrackingBackArrowLatency = false;
-
-    private final Handler mHandler = new Handler();
-    private final Runnable mFailsafeRunnable = this::onFailsafe;
-
-    private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
-            = new DynamicAnimation.OnAnimationEndListener() {
-        @Override
-        public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
-                float velocity) {
-            animation.removeEndListener(this);
-            if (!canceled) {
-                setVisibility(GONE);
-            }
-        }
-    };
-    private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_ANGLE =
-            new FloatPropertyCompat<NavigationBarEdgePanel>("currentAngle") {
-        @Override
-        public void setValue(NavigationBarEdgePanel object, float value) {
-            object.setCurrentAngle(value);
-        }
-
-        @Override
-        public float getValue(NavigationBarEdgePanel object) {
-            return object.getCurrentAngle();
-        }
-    };
-
-    private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_TRANSLATION =
-            new FloatPropertyCompat<NavigationBarEdgePanel>("currentTranslation") {
-
-                @Override
-                public void setValue(NavigationBarEdgePanel object, float value) {
-                    object.setCurrentTranslation(value);
-                }
-
-                @Override
-                public float getValue(NavigationBarEdgePanel object) {
-                    return object.getCurrentTranslation();
-                }
-            };
-    private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_VERTICAL_TRANSLATION =
-            new FloatPropertyCompat<NavigationBarEdgePanel>("verticalTranslation") {
-
-                @Override
-                public void setValue(NavigationBarEdgePanel object, float value) {
-                    object.setVerticalTranslation(value);
-                }
-
-                @Override
-                public float getValue(NavigationBarEdgePanel object) {
-                    return object.getVerticalTranslation();
-                }
-            };
-    private BackCallback mBackCallback;
-
-    @Inject
-    public NavigationBarEdgePanel(
-            Context context,
-            LatencyTracker latencyTracker,
-            VibratorHelper vibratorHelper,
-            @Background Executor backgroundExecutor,
-            DisplayTracker displayTracker) {
-        super(context);
-
-        mWindowManager = context.getSystemService(WindowManager.class);
-        mVibratorHelper = vibratorHelper;
-
-        mDensity = context.getResources().getDisplayMetrics().density;
-
-        mBaseTranslation = dp(BASE_TRANSLATION_DP);
-        mArrowLength = dp(ARROW_LENGTH_DP);
-        mArrowThickness = dp(ARROW_THICKNESS_DP);
-        mMinDeltaForSwitch = dp(32);
-
-        mPaint.setStrokeWidth(mArrowThickness);
-        mPaint.setStrokeCap(Paint.Cap.ROUND);
-        mPaint.setAntiAlias(true);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeJoin(Paint.Join.ROUND);
-
-        mArrowColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
-        mArrowColorAnimator.setDuration(COLOR_ANIMATION_DURATION_MS);
-        mArrowColorAnimator.addUpdateListener(animation -> {
-            int newColor = ColorUtils.blendARGB(
-                    mArrowStartColor, mArrowColor, animation.getAnimatedFraction());
-            setCurrentArrowColor(newColor);
-        });
-
-        mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
-        mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
-        mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        mArrowDisappearAnimation.addUpdateListener(animation -> {
-            mDisappearAmount = (float) animation.getAnimatedValue();
-            invalidate();
-        });
-
-        mAngleAnimation =
-                new SpringAnimation(this, CURRENT_ANGLE);
-        mAngleAppearForce = new SpringForce()
-                .setStiffness(500)
-                .setDampingRatio(0.5f);
-        mAngleDisappearForce = new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
-                .setFinalPosition(90);
-        mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
-
-        mTranslationAnimation =
-                new SpringAnimation(this, CURRENT_TRANSLATION);
-        mRegularTranslationSpring = new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-        mTriggerBackSpring = new SpringForce()
-                .setStiffness(450)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-        mTranslationAnimation.setSpring(mRegularTranslationSpring);
-        mVerticalTranslationAnimation =
-                new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
-        mVerticalTranslationAnimation.setSpring(
-                new SpringForce()
-                        .setStiffness(SpringForce.STIFFNESS_MEDIUM)
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-
-        mProtectionPaint = new Paint(mPaint);
-        mProtectionPaint.setStrokeWidth(mArrowThickness + PROTECTION_WIDTH_PX);
-        loadDimens();
-
-        loadColors(context);
-        updateArrowDirection();
-
-        mSwipeTriggerThreshold = context.getResources()
-                .getDimension(R.dimen.navigation_edge_action_drag_threshold);
-        mSwipeProgressThreshold = context.getResources()
-                .getDimension(R.dimen.navigation_edge_action_progress_threshold);
-
-        setVisibility(GONE);
-
-        boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId();
-        mRegionSamplingHelper = new RegionSamplingHelper(this,
-                new RegionSamplingHelper.SamplingCallback() {
-                    @Override
-                    public void onRegionDarknessChanged(boolean isRegionDark) {
-                        setIsDark(!isRegionDark, true /* animate */);
-                    }
-
-                    @Override
-                    public Rect getSampledRegion(View sampledView) {
-                        return mSamplingRect;
-                    }
-
-                    @Override
-                    public boolean isSamplingEnabled() {
-                        return isPrimaryDisplay;
-                    }
-                }, backgroundExecutor);
-        mRegionSamplingHelper.setWindowVisible(true);
-        mShowProtection = !isPrimaryDisplay;
-        mLatencyTracker = latencyTracker;
-    }
-
-    @Override
-    public void onDestroy() {
-        cancelFailsafe();
-        mWindowManager.removeView(this);
-        mRegionSamplingHelper.stop();
-        mRegionSamplingHelper = null;
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    private void setIsDark(boolean isDark, boolean animate) {
-        mIsDark = isDark;
-        updateIsDark(animate);
-    }
-
-    @Override
-    public void setIsLeftPanel(boolean isLeftPanel) {
-        mIsLeftPanel = isLeftPanel;
-        mLayoutParams.gravity = mIsLeftPanel
-                ? (Gravity.LEFT | Gravity.TOP)
-                : (Gravity.RIGHT | Gravity.TOP);
-    }
-
-    @Override
-    public void setInsets(int leftInset, int rightInset) {
-        mLeftInset = leftInset;
-        mRightInset = rightInset;
-    }
-
-    @Override
-    public void setDisplaySize(Point displaySize) {
-        mDisplaySize.set(displaySize.x, displaySize.y);
-        mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
-    }
-
-    @Override
-    public void setBackCallback(BackCallback callback) {
-        mBackCallback = callback;
-    }
-
-    @Override
-    public void setLayoutParams(WindowManager.LayoutParams layoutParams) {
-        mLayoutParams = layoutParams;
-        mWindowManager.addView(this, mLayoutParams);
-    }
-
-    /**
-     * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
-     */
-    private void adjustSamplingRectToBoundingBox() {
-        float translation = mDesiredTranslation;
-        if (!mTriggerBack) {
-            // Let's take the resting position and bounds as the sampling rect, since we are not
-            // visible right now
-            translation = mBaseTranslation;
-            if (mIsLeftPanel && mArrowsPointLeft
-                    || (!mIsLeftPanel && !mArrowsPointLeft)) {
-                // If we're on the left we should move less, because the arrow is facing the other
-                // direction
-                translation -= getStaticArrowWidth();
-            }
-        }
-        float left = translation - mArrowThickness / 2.0f;
-        left = mIsLeftPanel ? left : mSamplingRect.width() - left;
-
-        // Let's calculate the position of the end based on the angle
-        float width = getStaticArrowWidth();
-        float height = polarToCartY(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength * 2.0f;
-        if (!mArrowsPointLeft) {
-            left -= width;
-        }
-
-        float top = (getHeight() * 0.5f) + mDesiredVerticalTranslation - height / 2.0f;
-        mSamplingRect.offset((int) left, (int) top);
-        mSamplingRect.set(mSamplingRect.left, mSamplingRect.top,
-                (int) (mSamplingRect.left + width),
-                (int) (mSamplingRect.top + height));
-        mRegionSamplingHelper.updateSamplingRect();
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent event) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(event);
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mDragSlopPassed = false;
-                resetOnDown();
-                mStartX = event.getX();
-                mStartY = event.getY();
-                setVisibility(VISIBLE);
-                updatePosition(event.getY());
-                mRegionSamplingHelper.start(mSamplingRect);
-                mWindowManager.updateViewLayout(this, mLayoutParams);
-                mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW);
-                mTrackingBackArrowLatency = true;
-                break;
-            case MotionEvent.ACTION_MOVE:
-                handleMoveEvent(event);
-                break;
-            case MotionEvent.ACTION_UP:
-                if (DEBUG_MISSING_GESTURE) {
-                    Log.d(DEBUG_MISSING_GESTURE_TAG,
-                            "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack);
-                }
-                if (mTriggerBack) {
-                    triggerBack();
-                } else {
-                    cancelBack();
-                }
-                mRegionSamplingHelper.stop();
-                mVelocityTracker.recycle();
-                mVelocityTracker = null;
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                if (DEBUG_MISSING_GESTURE) {
-                    Log.d(DEBUG_MISSING_GESTURE_TAG, "NavigationBarEdgePanel ACTION_CANCEL");
-                }
-                cancelBack();
-                mRegionSamplingHelper.stop();
-                mVelocityTracker.recycle();
-                mVelocityTracker = null;
-                break;
-        }
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        updateArrowDirection();
-        loadDimens();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
-        canvas.save();
-        canvas.translate(
-                mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
-                (getHeight() * 0.5f) + mVerticalTranslation);
-
-        // Let's calculate the position of the end based on the angle
-        float x = (polarToCartX(mCurrentAngle) * mArrowLength);
-        float y = (polarToCartY(mCurrentAngle) * mArrowLength);
-        Path arrowPath = calculatePath(x,y);
-        if (mShowProtection) {
-            canvas.drawPath(arrowPath, mProtectionPaint);
-        }
-
-        canvas.drawPath(arrowPath, mPaint);
-        canvas.restore();
-        if (mTrackingBackArrowLatency) {
-            mLatencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW);
-            mTrackingBackArrowLatency = false;
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        mMaxTranslation = getWidth() - mArrowPaddingEnd;
-    }
-
-    private void loadDimens() {
-        Resources res = getResources();
-        mArrowPaddingEnd = res.getDimensionPixelSize(R.dimen.navigation_edge_panel_padding);
-        mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y);
-        mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset);
-    }
-
-    private void updateArrowDirection() {
-        // Both panels arrow point the same way
-        mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
-        invalidate();
-    }
-
-    private void loadColors(Context context) {
-        final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme);
-        final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme);
-        Context lightContext = new ContextThemeWrapper(context, dualToneLightTheme);
-        Context darkContext = new ContextThemeWrapper(context, dualToneDarkTheme);
-        mArrowColorLight = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
-        mArrowColorDark = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
-        mProtectionColorDark = mArrowColorLight;
-        mProtectionColorLight = mArrowColorDark;
-        updateIsDark(false /* animate */);
-    }
-
-    private void updateIsDark(boolean animate) {
-        // TODO: Maybe animate protection as well
-        mProtectionColor = mIsDark ? mProtectionColorDark : mProtectionColorLight;
-        mProtectionPaint.setColor(mProtectionColor);
-        mArrowColor = mIsDark ? mArrowColorDark : mArrowColorLight;
-        mArrowColorAnimator.cancel();
-        if (!animate) {
-            setCurrentArrowColor(mArrowColor);
-        } else {
-            mArrowStartColor = mCurrentArrowColor;
-            mArrowColorAnimator.start();
-        }
-    }
-
-    private void setCurrentArrowColor(int color) {
-        mCurrentArrowColor = color;
-        mPaint.setColor(color);
-        invalidate();
-    }
-
-    private float getStaticArrowWidth() {
-        return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
-    }
-
-    private float polarToCartX(float angleInDegrees) {
-        return (float) Math.cos(Math.toRadians(angleInDegrees));
-    }
-
-    private float polarToCartY(float angleInDegrees) {
-        return (float) Math.sin(Math.toRadians(angleInDegrees));
-    }
-
-    private Path calculatePath(float x, float y) {
-        if (!mArrowsPointLeft) {
-            x = -x;
-        }
-        float extent = MathUtils.lerp(1.0f, 0.75f, mDisappearAmount);
-        x = x * extent;
-        y = y * extent;
-        mArrowPath.reset();
-        mArrowPath.moveTo(x, y);
-        mArrowPath.lineTo(0, 0);
-        mArrowPath.lineTo(x, -y);
-        return mArrowPath;
-    }
-
-    private float getCurrentAngle() {
-        return mCurrentAngle;
-    }
-
-    private float getCurrentTranslation() {
-        return mCurrentTranslation;
-    }
-
-    private void triggerBack() {
-        mBackCallback.triggerBack();
-
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.computeCurrentVelocity(1000);
-        // Only do the extra translation if we're not already flinging
-        boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
-        if (isSlow
-                || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
-        }
-
-        // Let's also snap the angle a bit
-        if (mAngleOffset > -4) {
-            mAngleOffset = Math.max(-8, mAngleOffset - 8);
-            updateAngle(true /* animated */);
-        }
-
-        // Finally, after the translation, animate back and disappear the arrow
-        Runnable translationEnd = () -> {
-            // let's snap it back
-            mAngleOffset = Math.max(0, mAngleOffset + 8);
-            updateAngle(true /* animated */);
-
-            mTranslationAnimation.setSpring(mTriggerBackSpring);
-            // Translate the arrow back a bit to make for a nice transition
-            setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
-            animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
-                    .withEndAction(() -> setVisibility(GONE));
-            mArrowDisappearAnimation.start();
-            // Schedule failsafe in case alpha end callback is not called
-            scheduleFailsafe();
-        };
-        if (mTranslationAnimation.isRunning()) {
-            mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
-                @Override
-                public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
-                        float value,
-                        float velocity) {
-                    animation.removeEndListener(this);
-                    if (!canceled) {
-                        translationEnd.run();
-                    }
-                }
-            });
-            // Schedule failsafe in case mTranslationAnimation end callback is not called
-            scheduleFailsafe();
-        } else {
-            translationEnd.run();
-        }
-    }
-
-    private void cancelBack() {
-        mBackCallback.cancelBack();
-
-        if (mTranslationAnimation.isRunning()) {
-            mTranslationAnimation.addEndListener(mSetGoneEndListener);
-            // Schedule failsafe in case mTranslationAnimation end callback is not called
-            scheduleFailsafe();
-        } else {
-            setVisibility(GONE);
-        }
-    }
-
-    private void resetOnDown() {
-        animate().cancel();
-        mAngleAnimation.cancel();
-        mTranslationAnimation.cancel();
-        mVerticalTranslationAnimation.cancel();
-        mArrowDisappearAnimation.cancel();
-        mAngleOffset = 0;
-        mTranslationAnimation.setSpring(mRegularTranslationSpring);
-        // Reset the arrow to the side
-        if (DEBUG_MISSING_GESTURE) {
-            Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false");
-        }
-        setTriggerBack(false /* triggerBack */, false /* animated */);
-        setDesiredTranslation(0, false /* animated */);
-        setCurrentTranslation(0);
-        updateAngle(false /* animate */);
-        mPreviousTouchTranslation = 0;
-        mTotalTouchDelta = 0;
-        mVibrationTime = 0;
-        setDesiredVerticalTransition(0, false /* animated */);
-        cancelFailsafe();
-    }
-
-    private void handleMoveEvent(MotionEvent event) {
-        float x = event.getX();
-        float y = event.getY();
-        float touchTranslation = MathUtils.abs(x - mStartX);
-        float yOffset = y - mStartY;
-        float delta = touchTranslation - mPreviousTouchTranslation;
-        if (Math.abs(delta) > 0) {
-            if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
-                mTotalTouchDelta += delta;
-            } else {
-                mTotalTouchDelta = delta;
-            }
-        }
-        mPreviousTouchTranslation = touchTranslation;
-
-        // Apply a haptic on drag slop passed
-        if (!mDragSlopPassed && touchTranslation > mSwipeTriggerThreshold) {
-            mDragSlopPassed = true;
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
-            mVibrationTime = SystemClock.uptimeMillis();
-
-            // Let's show the arrow and animate it in!
-            mDisappearAmount = 0.0f;
-            setAlpha(1f);
-            // And animate it go to back by default!
-            if (DEBUG_MISSING_GESTURE) {
-                Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=true");
-            }
-            setTriggerBack(true /* triggerBack */, true /* animated */);
-        }
-
-        // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
-        if (touchTranslation > mBaseTranslation) {
-            float diff = touchTranslation - mBaseTranslation;
-            float progress = MathUtils.saturate(diff / (mScreenSize - mBaseTranslation));
-            progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
-                    * (mMaxTranslation - mBaseTranslation);
-            touchTranslation = mBaseTranslation + progress;
-        } else {
-            float diff = mBaseTranslation - touchTranslation;
-            float progress = MathUtils.saturate(diff / mBaseTranslation);
-            progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
-                    * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
-            touchTranslation = mBaseTranslation - progress;
-        }
-        // By default we just assume the current direction is kept
-        boolean triggerBack = mTriggerBack;
-
-        //  First lets see if we had continuous motion in one direction for a while
-        if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
-            triggerBack = mTotalTouchDelta > 0;
-        }
-
-        // Then, let's see if our velocity tells us to change direction
-        mVelocityTracker.computeCurrentVelocity(1000);
-        float xVelocity = mVelocityTracker.getXVelocity();
-        float yVelocity = mVelocityTracker.getYVelocity();
-        float velocity = MathUtils.mag(xVelocity, yVelocity);
-        mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
-                ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
-        if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
-            mAngleOffset *= -1;
-        }
-
-        // Last if the direction in Y is bigger than X * 2 we also abort
-        if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
-            triggerBack = false;
-        }
-        if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) {
-            Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack
-                    + ", mTotalTouchDelta=" + mTotalTouchDelta
-                    + ", mMinDeltaForSwitch=" + mMinDeltaForSwitch
-                    + ", yOffset=" + yOffset
-                    + ", x=" + x
-                    + ", mStartX=" + mStartX);
-        }
-        setTriggerBack(triggerBack, true /* animated */);
-
-        if (!mTriggerBack) {
-            touchTranslation = 0;
-        } else if (mIsLeftPanel && mArrowsPointLeft
-                || (!mIsLeftPanel && !mArrowsPointLeft)) {
-            // If we're on the left we should move less, because the arrow is facing the other
-            // direction
-            touchTranslation -= getStaticArrowWidth();
-        }
-        setDesiredTranslation(touchTranslation, true /* animated */);
-        updateAngle(true /* animated */);
-
-        float maxYOffset = getHeight() / 2.0f - mArrowLength;
-        float progress = MathUtils.constrain(
-                Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT),
-                0, 1);
-        float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
-                * maxYOffset * Math.signum(yOffset);
-        setDesiredVerticalTransition(verticalTranslation, true /* animated */);
-        updateSamplingRect();
-    }
-
-    private void updatePosition(float touchY) {
-        float position = touchY - mFingerOffset;
-        position = Math.max(position, mMinArrowPosition);
-        position -= mLayoutParams.height / 2.0f;
-        mLayoutParams.y = MathUtils.constrain((int) position, 0, mDisplaySize.y);
-        updateSamplingRect();
-    }
-
-    private void updateSamplingRect() {
-        int top = mLayoutParams.y;
-        int left = mIsLeftPanel ? mLeftInset : mDisplaySize.x - mRightInset - mLayoutParams.width;
-        int right = left + mLayoutParams.width;
-        int bottom = top + mLayoutParams.height;
-        mSamplingRect.set(left, top, right, bottom);
-        adjustSamplingRectToBoundingBox();
-    }
-
-    private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
-        if (mDesiredVerticalTranslation != verticalTranslation) {
-            mDesiredVerticalTranslation = verticalTranslation;
-            if (!animated) {
-                setVerticalTranslation(verticalTranslation);
-            } else {
-                mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
-            }
-            invalidate();
-        }
-    }
-
-    private void setVerticalTranslation(float verticalTranslation) {
-        mVerticalTranslation = verticalTranslation;
-        invalidate();
-    }
-
-    private float getVerticalTranslation() {
-        return mVerticalTranslation;
-    }
-
-    private void setDesiredTranslation(float desiredTranslation, boolean animated) {
-        if (mDesiredTranslation != desiredTranslation) {
-            mDesiredTranslation = desiredTranslation;
-            if (!animated) {
-                setCurrentTranslation(desiredTranslation);
-            } else {
-                mTranslationAnimation.animateToFinalPosition(desiredTranslation);
-            }
-        }
-    }
-
-    private void setCurrentTranslation(float currentTranslation) {
-        mCurrentTranslation = currentTranslation;
-        invalidate();
-    }
-
-    private void setTriggerBack(boolean triggerBack, boolean animated) {
-        if (mTriggerBack != triggerBack) {
-            mTriggerBack = triggerBack;
-            mAngleAnimation.cancel();
-            updateAngle(animated);
-            // Whenever the trigger back state changes the existing translation animation should be
-            // cancelled
-            mTranslationAnimation.cancel();
-            mBackCallback.setTriggerBack(mTriggerBack);
-        }
-    }
-
-    private void updateAngle(boolean animated) {
-        float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
-        if (newAngle != mDesiredAngle) {
-            if (!animated) {
-                setCurrentAngle(newAngle);
-            } else {
-                mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
-                mAngleAnimation.animateToFinalPosition(newAngle);
-            }
-            mDesiredAngle = newAngle;
-        }
-    }
-
-    private void setCurrentAngle(float currentAngle) {
-        mCurrentAngle = currentAngle;
-        invalidate();
-    }
-
-    private void scheduleFailsafe() {
-        if (!ENABLE_FAILSAFE) {
-            return;
-        }
-        cancelFailsafe();
-        mHandler.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY_MS);
-    }
-
-    private void cancelFailsafe() {
-        mHandler.removeCallbacks(mFailsafeRunnable);
-    }
-
-    private void onFailsafe() {
-        setVisibility(GONE);
-    }
-
-    private float dp(float dp) {
-        return mDensity * dp;
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        pw.println("NavigationBarEdgePanel:");
-        pw.println("  mIsLeftPanel=" + mIsLeftPanel);
-        pw.println("  mTriggerBack=" + mTriggerBack);
-        pw.println("  mDragSlopPassed=" + mDragSlopPassed);
-        pw.println("  mCurrentAngle=" + mCurrentAngle);
-        pw.println("  mDesiredAngle=" + mDesiredAngle);
-        pw.println("  mCurrentTranslation=" + mCurrentTranslation);
-        pw.println("  mDesiredTranslation=" + mDesiredTranslation);
-        pw.println("  mTranslationAnimation running=" + mTranslationAnimation.isRunning());
-        mRegionSamplingHelper.dump(pw);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 10a88c8..b46f2d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -24,16 +24,12 @@
 
 public final class Utilities {
 
-    public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled,
-            MotionEvent event) {
-        return isTrackpadGestureFeaturesEnabled
-                && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
+    public static boolean isTrackpadScroll(MotionEvent event) {
+        return event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 
-    public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
-            MotionEvent event) {
-        return isTrackpadGestureFeaturesEnabled
-                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+    public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+        return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
                 && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 9698548d..54a59f30 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -38,7 +38,7 @@
 import javax.inject.Inject
 
 /** Class responsible to "glue" all note task dependencies. */
-internal class NoteTaskInitializer
+class NoteTaskInitializer
 @Inject
 constructor(
     private val controller: NoteTaskController,
@@ -138,11 +138,12 @@
      * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise.
      */
     private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? {
-        val entryPoint = when {
-            keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
-            keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
-            else -> null
-        }
+        val entryPoint =
+            when {
+                keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
+                keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
+                else -> null
+            }
         debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" }
         return entryPoint
     }
@@ -164,7 +165,9 @@
 
         // For now, trigger action immediately on UP of a single press, without waiting for
         // the multi-press timeout to expire.
-        debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" }
+        debugLog {
+            "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress"
+        }
         return !isMultiPress && !isLongPress
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ec7707c..e56a4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -22,7 +22,7 @@
 interface TakeScreenshotExecutor {
     suspend fun executeScreenshots(
         screenshotRequest: ScreenshotRequest,
-        onSaved: (Uri) -> Unit,
+        onSaved: (Uri?) -> Unit,
         requestCallback: RequestCallback
     )
     fun onCloseSystemDialogsReceived()
@@ -30,7 +30,7 @@
     fun onDestroy()
     fun executeScreenshotsAsync(
         screenshotRequest: ScreenshotRequest,
-        onSaved: Consumer<Uri>,
+        onSaved: Consumer<Uri?>,
         requestCallback: RequestCallback
     )
 }
@@ -65,7 +65,7 @@
      */
     override suspend fun executeScreenshots(
         screenshotRequest: ScreenshotRequest,
-        onSaved: (Uri) -> Unit,
+        onSaved: (Uri?) -> Unit,
         requestCallback: RequestCallback
     ) {
         val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
@@ -86,7 +86,7 @@
     /** All logging should be triggered only by this method. */
     private suspend fun dispatchToController(
         rawScreenshotData: ScreenshotData,
-        onSaved: (Uri) -> Unit,
+        onSaved: (Uri?) -> Unit,
         callback: RequestCallback
     ) {
         // Let's wait before logging "screenshot requested", as we should log the processed
@@ -185,7 +185,7 @@
     /** For java compatibility only. see [executeScreenshots] */
     override fun executeScreenshotsAsync(
         screenshotRequest: ScreenshotRequest,
-        onSaved: Consumer<Uri>,
+        onSaved: Consumer<Uri?>,
         requestCallback: RequestCallback
     ) {
         mainScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index d62ab85..1945c25 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -39,11 +39,11 @@
     override suspend fun check(content: DisplayContentModel): PolicyResult {
         // The systemUI notification shade isn't a private profile app, skip.
         if (content.systemUiState.shadeExpanded) {
-            return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+            return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
         }
 
         // Find the first visible rootTaskInfo with a child task owned by a private user
-        val (rootTask, childTask) =
+        val childTask =
             content.rootTasks
                 .filter { it.isVisible }
                 .firstNotNullOfOrNull { root ->
@@ -52,22 +52,24 @@
                         .firstOrNull {
                             profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
                         }
-                        ?.let { root to it }
                 }
-                ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible")
+                ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
 
         // If matched, return parameters needed to modify the request.
         return Matched(
             policy = NAME,
-            reason = "At least one private profile task is visible",
+            reason = PRIVATE_TASK_VISIBLE,
             CaptureParameters(
                 type = FullScreen(content.displayId),
-                component = childTask.componentName ?: rootTask.topActivity,
+                component = content.rootTasks.first { it.isVisible }.topActivity,
                 owner = UserHandle.of(childTask.userId),
             )
         )
     }
     companion object {
         const val NAME = "PrivateProfile"
+        const val SHADE_EXPANDED = "Notification shade is expanded"
+        const val NO_VISIBLE_TASKS = "No private profile tasks are visible"
+        const val PRIVATE_TASK_VISIBLE = "At least one private profile task is visible"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index 3789371..f768cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -30,3 +30,5 @@
         )
     }
 }
+
+internal fun RootTaskInfo.hasChildTasks() = childTaskUserIds.isNotEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index b781ae9..fdf16aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenshot.policy
 
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
 import android.os.UserHandle
 import com.android.systemui.screenshot.data.model.DisplayContentModel
@@ -24,6 +25,7 @@
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.window.flags.Flags
 import javax.inject.Inject
 import kotlinx.coroutines.flow.first
 
@@ -41,26 +43,36 @@
     override suspend fun check(content: DisplayContentModel): PolicyResult {
         // The systemUI notification shade isn't a work app, skip.
         if (content.systemUiState.shadeExpanded) {
-            return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+            return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
+        }
+
+        if (Flags.enableDesktopWindowingMode()) {
+            content.rootTasks.firstOrNull()?.also {
+                if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
+                    return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
+                }
+            }
         }
 
         // Find the first non PiP rootTask with a top child task owned by a work user
         val (rootTask, childTask) =
             content.rootTasks
-                .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+                .filter {
+                    it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED && it.hasChildTasks()
+                }
                 .map { it to it.childTasksTopDown().first() }
                 .firstOrNull { (_, child) ->
                     profileTypes.getProfileType(child.userId) == ProfileType.WORK
                 }
                 ?: return NotMatched(
                     policy = NAME,
-                    reason = "The top-most non-PINNED task does not belong to a work profile user"
+                    reason = WORK_TASK_NOT_TOP,
                 )
 
         // If matched, return parameters needed to modify the request.
         return PolicyResult.Matched(
             policy = NAME,
-            reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user",
+            reason = WORK_TASK_IS_TOP,
             CaptureParameters(
                 type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
                 component = childTask.componentName ?: rootTask.topActivity,
@@ -70,6 +82,13 @@
     }
 
     companion object {
-        val NAME = "WorkProfile"
+        const val NAME = "WorkProfile"
+        const val SHADE_EXPANDED = "Notification shade is expanded"
+        const val WORK_TASK_NOT_TOP =
+            "The top-most non-PINNED task does not belong to a work profile user"
+        const val WORK_TASK_IS_TOP = "The top-most non-PINNED task belongs to a work profile user"
+        const val DESKTOP_MODE_ENABLED =
+            "enable_desktop_windowing_mode is enabled and top " +
+                "RootTask has WINDOWING_MODE_FREEFORM"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index f418e7e..8f6b9d0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -64,6 +65,7 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val powerManager: PowerManager,
+    private val communalColors: CommunalColors,
     @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
 ) {
     /** The container view for the hub. This will not be initialized until [initView] is called. */
@@ -168,6 +170,7 @@
                                 PlatformTheme {
                                     CommunalContainer(
                                         viewModel = communalViewModel,
+                                        colors = communalColors,
                                         dataSourceDelegator = dataSourceDelegator,
                                         dialogFactory = dialogFactory,
                                     )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b8512f2..aa915e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -191,6 +191,7 @@
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -291,7 +292,6 @@
      */
 
     public final boolean mAnimateBack;
-    private final boolean mTrackpadGestureFeaturesEnabled;
     /**
      * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
      */
@@ -438,6 +438,7 @@
     private boolean mExpandingFromHeadsUp;
     private boolean mCollapsedOnDown;
     private boolean mClosingWithAlphaFadeOut;
+    private boolean mHeadsUpVisible;
     private boolean mHeadsUpAnimatingAway;
     private final FalsingManager mFalsingManager;
     private final FalsingCollector mFalsingCollector;
@@ -605,6 +606,7 @@
     private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
     private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
+    private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
@@ -770,6 +772,7 @@
             ActivityStarter activityStarter,
             SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
             ActiveNotificationsInteractor activeNotificationsInteractor,
+            HeadsUpNotificationInteractor headsUpNotificationInteractor,
             ShadeAnimationInteractor shadeAnimationInteractor,
             KeyguardViewConfigurator keyguardViewConfigurator,
             DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -804,6 +807,7 @@
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
         mActiveNotificationsInteractor = activeNotificationsInteractor;
+        mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
         mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -886,7 +890,6 @@
         mLayoutInflater = layoutInflater;
         mFeatureFlags = featureFlags;
         mAnimateBack = predictiveBackAnimateShade();
-        mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
         mFalsingCollector = falsingCollector;
         mWakeUpCoordinator = coordinator;
         mMainDispatcher = mainDispatcher;
@@ -1216,6 +1219,11 @@
                     }
                 },
                 mMainDispatcher);
+
+        if (NotificationsHeadsUpRefactor.isEnabled()) {
+            collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
+                    setHeadsUpVisible(), mMainDispatcher);
+        }
     }
 
     @VisibleForTesting
@@ -3055,7 +3063,21 @@
         mPanelAlphaEndAction = r;
     }
 
+    private Consumer<Boolean> setHeadsUpVisible() {
+        return (Boolean isHeadsUpVisible) -> {
+            mHeadsUpVisible = isHeadsUpVisible;
+
+            if (isHeadsUpVisible) {
+                updateNotificationTranslucency();
+            }
+            updateExpansionAndVisibility();
+            updateGestureExclusionRect();
+            mKeyguardStatusBarViewController.updateForHeadsUp();
+        };
+    }
+
     private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        NotificationsHeadsUpRefactor.assertInLegacyMode();
         mHeadsUpAnimatingAway = headsUpAnimatingAway;
         mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
         updateVisibility();
@@ -3071,13 +3093,16 @@
     }
 
     private boolean shouldPanelBeVisible() {
-        boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
+        boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+                : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
         return headsUpVisible || isExpanded() || mBouncerShowing;
     }
 
     private void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
-        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+        if (!NotificationsHeadsUpRefactor.isEnabled()) {
+            mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+        }
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(
                 headsUpManager,
                 mStatusBarService,
@@ -3165,8 +3190,9 @@
     }
 
     private boolean isPanelVisibleBecauseOfHeadsUp() {
-        return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
-                && mBarState == StatusBarState.SHADE;
+        boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+                : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway);
+        return headsUpVisible && mBarState == StatusBarState.SHADE;
     }
 
     private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
@@ -3479,6 +3505,7 @@
         ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
         ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
         ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
+        ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible);
         ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
         ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
         ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
@@ -4384,6 +4411,8 @@
     private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
         @Override
         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+            NotificationsHeadsUpRefactor.assertInLegacyMode();
+
             if (inPinnedMode) {
                 mHeadsUpExistenceChangedRunnable.run();
                 updateNotificationTranslucency();
@@ -4400,9 +4429,7 @@
 
         @Override
         public void onHeadsUpPinned(NotificationEntry entry) {
-            if (NotificationsHeadsUpRefactor.isEnabled()) {
-                return;
-            }
+            NotificationsHeadsUpRefactor.assertInLegacyMode();
 
             if (!isKeyguardShowing()) {
                 mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
@@ -4411,9 +4438,7 @@
 
         @Override
         public void onHeadsUpUnPinned(NotificationEntry entry) {
-            if (NotificationsHeadsUpRefactor.isEnabled()) {
-                return;
-            }
+            NotificationsHeadsUpRefactor.assertInLegacyMode();
 
             // When we're unpinning the notification via active edge they remain heads-upped,
             // we need to make sure that an animation happens in this case, otherwise the
@@ -4898,9 +4923,8 @@
             final float x = event.getX(pointerIndex);
             final float y = event.getY(pointerIndex);
             boolean canCollapsePanel = canCollapsePanelOnTouch();
-            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
-                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
-                    mTrackpadGestureFeaturesEnabled, event);
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+                    || isTrackpadThreeFingerSwipe(event);
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
@@ -4920,7 +4944,7 @@
 
                     mIsTrackpadReverseScroll =
                             !mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()
-                                    && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event);
+                                    && isTrackpadScroll(event);
                     if (!isTracking() || isFullyCollapsed()) {
                         mInitialExpandY = y;
                         mInitialExpandX = x;
@@ -5143,9 +5167,8 @@
                 mIgnoreXTouchSlop = true;
             }
 
-            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
-                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
-                    mTrackpadGestureFeaturesEnabled, event);
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+                    || isTrackpadThreeFingerSwipe(event);
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index a763641..907cf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.shade;
 
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
 import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -104,7 +103,6 @@
     private final PulsingGestureListener mPulsingGestureListener;
     private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
-    private final boolean mIsTrackpadCommonEnabled;
     private final FeatureFlagsClassic mFeatureFlagsClassic;
     private final SysUIKeyEventHandler mSysUIKeyEventHandler;
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -211,7 +209,6 @@
         mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
         mGlanceableHubContainerController = glanceableHubContainerController;
-        mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
         mFeatureFlagsClassic = featureFlagsClassic;
         mSysUIKeyEventHandler = sysUIKeyEventHandler;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -643,16 +640,10 @@
         if (mTouchActive) {
             final long now = mClock.uptimeMillis();
             final MotionEvent event;
-            if (mIsTrackpadCommonEnabled) {
-                event = MotionEvent.obtain(mDownEvent);
-                event.setDownTime(now);
-                event.setAction(MotionEvent.ACTION_CANCEL);
-                event.setLocation(0.0f, 0.0f);
-            } else {
-                event = MotionEvent.obtain(now, now,
-                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-            }
+            event = MotionEvent.obtain(mDownEvent);
+            event.setDownTime(now);
+            event.setAction(MotionEvent.ACTION_CANCEL);
+            event.setLocation(0.0f, 0.0f);
             Log.w(TAG, "Canceling current touch event (should be very rare)");
             mView.dispatchTouchEvent(event);
             event.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index e3db626..222b070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -33,9 +33,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -813,7 +813,7 @@
                 initialTouchX = x
                 isTrackpadReverseScroll =
                     !naturalScrollingSettingObserver.isNaturalScrollingEnabled &&
-                        isTrackpadScroll(true, event)
+                        isTrackpadScroll(event)
             }
             MotionEvent.ACTION_MOVE -> {
                 val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index 77660eb..e9306a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.data.repository
 
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * A repository of currently displayed heads up notifications.
@@ -31,11 +32,13 @@
      * True if we are exiting the headsUp pinned mode, and some notifications might still be
      * animating out. This is used to keep their view container visible.
      */
-    val isHeadsUpAnimatingAway: Flow<Boolean>
+    val isHeadsUpAnimatingAway: StateFlow<Boolean>
 
     /** The heads up row that should be displayed on top. */
     val topHeadsUpRow: Flow<HeadsUpRowRepository?>
 
     /** Set of currently active top-level heads up rows to be displayed. */
     val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
+
+    fun setHeadsUpAnimatingAway(animatingAway: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 7f94da3..98b52ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -29,7 +29,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
-class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepository) {
+class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) {
 
     val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow
 
@@ -67,6 +67,9 @@
     fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
         HeadsUpRowInteractor(key as HeadsUpRowRepository)
     fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+    fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+        repository.setHeadsUpAnimatingAway(animatingAway)
+    }
 }
 
 class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index bb65651..8a1a4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -111,6 +111,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -450,7 +451,9 @@
     private boolean mIsClipped;
     private Rect mRequestedClipBounds;
     private boolean mInHeadsUpPinnedMode;
-    private boolean mHeadsUpAnimatingAway;
+    @VisibleForTesting
+    boolean mHeadsUpAnimatingAway;
+    private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
     private int mStatusBarState;
     private int mUpcomingStatusBarState;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
@@ -4084,7 +4087,14 @@
         mSwipeHelper.setIsExpanded(isExpanded);
         if (changed) {
             mWillExpand = false;
-            if (!mIsExpanded) {
+            if (mIsExpanded) {
+                // Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by
+                // waiting for all child animations to finish.
+                // TODO(b/328390331) Do we need to reset this on QS expanded as well?
+                if (NotificationsHeadsUpRefactor.isEnabled()) {
+                    setHeadsUpAnimatingAway(false);
+                }
+            } else {
                 mGroupExpansionManager.collapseGroups();
                 mExpandHelper.cancelImmediately();
                 if (!mIsExpansionChanging) {
@@ -4190,6 +4200,9 @@
 
     void onChildAnimationFinished() {
         setAnimationRunning(false);
+        if (NotificationsHeadsUpRefactor.isEnabled()) {
+            setHeadsUpAnimatingAway(false);
+        }
         requestChildrenUpdate();
         runAnimationFinishedRunnables();
         clearTransient();
@@ -4717,6 +4730,7 @@
     }
 
     public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+        NotificationsHeadsUpRefactor.assertInLegacyMode();
         ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
         generateHeadsUpAnimation(row, isHeadsUp);
     }
@@ -4750,6 +4764,9 @@
             mNeedsAnimation = true;
             if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
                 row.setHeadsUpAnimatingAway(true);
+                if (NotificationsHeadsUpRefactor.isEnabled()) {
+                    setHeadsUpAnimatingAway(true);
+                }
             }
             requestChildrenUpdate();
         }
@@ -4939,11 +4956,28 @@
         updateClipping();
     }
 
+    /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        mHeadsUpAnimatingAway = headsUpAnimatingAway;
+        if (mHeadsUpAnimatingAway != headsUpAnimatingAway) {
+            mHeadsUpAnimatingAway = headsUpAnimatingAway;
+            if (mHeadsUpAnimatingAwayListener != null) {
+                mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway);
+            }
+        }
         updateClipping();
     }
 
+    /**
+     * Sets a listener to be notified about the heads up disappear animation state changes. If there
+     * are overlapping animations, it will receive updates when the first disappar animation has
+     * started, and when the last has finished.
+     *
+     * @param headsUpAnimatingAwayListener to be notified about disappear animation state changes.
+     */
+    public void setHeadsUpAnimatingAwayListener(
+            Consumer<Boolean> headsUpAnimatingAwayListener) {
+        mHeadsUpAnimatingAwayListener = headsUpAnimatingAwayListener;
+    }
     @VisibleForTesting
     public void setStatusBarState(int statusBarState) {
         mStatusBarState = statusBarState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 06479e5..ea72c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1482,6 +1482,7 @@
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        NotificationsHeadsUpRefactor.assertInLegacyMode();
         mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 70aa245..3a89630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -299,4 +299,8 @@
         HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
 
     fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key)
+
+    fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+        headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index b3d8430..6acb12a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -16,13 +16,17 @@
 
 package com.android.systemui.statusbar.notification.ui.viewbinder
 
+import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.launch
 
 class HeadsUpNotificationViewBinder
@@ -62,9 +66,19 @@
             launch {
                 viewModel.hasPinnedHeadsUpRow.collect { parentView.setInHeadsUpPinnedMode(it) }
             }
+            launch {
+                parentView.isHeadsUpAnimatingAway.collect { viewModel.setHeadsUpAnimatingAway(it) }
+            }
         }
 
     private fun obtainView(key: HeadsUpRowKey): ExpandableNotificationRow {
         return viewModel.elementKeyFor(key) as ExpandableNotificationRow
     }
 }
+
+private val NotificationStackScrollLayout.isHeadsUpAnimatingAway: Flow<Boolean>
+    get() =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) }
+            awaitClose { setHeadsUpAnimatingAwayListener(null) }
+        }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 9268d16..6546db9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
@@ -37,16 +36,10 @@
 constructor(
     private val statusBarStateController: SysuiStatusBarStateController,
     @Main private val mainExecutor: DelayableExecutor,
-    legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>,
-    activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
+    legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
 ) : ActivityStarter {
 
-    private val activityStarterInternal: ActivityStarterInternal =
-        if (SceneContainerFlag.isEnabled) {
-            activityStarterInternal.get()
-        } else {
-            legacyActivityStarter.get()
-        }
+    private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get()
 
     override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0ddf37d..8ec8d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -168,7 +168,10 @@
                 updateResources();
             }
         });
-        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
+        if (!NotificationsHeadsUpRefactor.isEnabled()) {
+            javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+                    this::onShadeOrQsExpanded);
+        }
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -262,6 +265,7 @@
     }
 
     private void onShadeOrQsExpanded(Boolean isExpanded) {
+        NotificationsHeadsUpRefactor.assertInLegacyMode();
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
             if (isExpanded) {
@@ -500,7 +504,7 @@
 
     @Override
     @NonNull
-    public Flow<Boolean> isHeadsUpAnimatingAway() {
+    public StateFlow<Boolean> isHeadsUpAnimatingAway() {
         return mHeadsUpAnimatingAway;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 38b3718..3343779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -658,6 +658,7 @@
         updateForHeadsUp(true);
     }
 
+    // TODO(b/328579846) bind the StatusBar visibility to heads up events
     void updateForHeadsUp(boolean animate) {
         boolean showingKeyguardHeadsUp =
                 isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 87139ac..da5877b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -98,15 +99,21 @@
                 // we need to keep the panel open artificially, let's wait until the
                 //animation
                 // is finished.
-                mHeadsUpManager.setHeadsUpAnimatingAway(true);
+                setHeadsAnimatingAway(true);
                 mNsslController.runAfterAnimationFinished(() -> {
                     if (!mHeadsUpManager.hasPinnedHeadsUp()) {
                         mNotificationShadeWindowController.setHeadsUpShowing(false);
-                        mHeadsUpManager.setHeadsUpAnimatingAway(false);
+                        setHeadsAnimatingAway(false);
                     }
                     mNotificationRemoteInputManager.onPanelCollapsed();
                 });
             }
         }
     }
+
+    private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
+        if (!NotificationsHeadsUpRefactor.isEnabled()) {
+            mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 6b237f8e..f19fa20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -34,6 +35,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -53,32 +55,40 @@
     private val uiEventLogger: UiEventLogger,
 ) {
 
-    private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+    private val sessionWithPlayback: StateFlow<Result<SessionWithPlayback?>> =
         interactor.defaultActiveMediaSession
             .flatMapLatest { session ->
                 if (session == null) {
-                    flowOf(null)
+                    flowOf(Result.Data<SessionWithPlayback?>(null))
                 } else {
-                    mediaDeviceSessionInteractor.playbackState(session).map { playback ->
-                        playback?.let { SessionWithPlayback(session, it) }
-                    }
+                    mediaDeviceSessionInteractor
+                        .playbackState(session)
+                        .map { playback ->
+                            playback?.let {
+                                Result.Data<SessionWithPlayback?>(SessionWithPlayback(session, it))
+                            }
+                        }
+                        .filterNotNull()
                 }
             }
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
-                null,
+                Result.Loading(),
             )
 
     val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
         combine(sessionWithPlayback, interactor.currentConnectedDevice) {
                 mediaDeviceSession,
                 currentConnectedDevice ->
+                if (mediaDeviceSession !is Result.Data) {
+                    return@combine null
+                }
                 ConnectedDeviceViewModel(
-                    if (mediaDeviceSession?.playback?.isActive == true) {
+                    if (mediaDeviceSession.data?.playback?.isActive == true) {
                         context.getString(
                             R.string.media_output_label_title,
-                            mediaDeviceSession.session.appLabel
+                            mediaDeviceSession.data.session.appLabel
                         )
                     } else {
                         context.getString(R.string.media_output_title_without_playing)
@@ -96,7 +106,10 @@
         combine(sessionWithPlayback, interactor.currentConnectedDevice) {
                 mediaDeviceSession,
                 currentConnectedDevice ->
-                if (mediaDeviceSession?.playback?.isActive == true) {
+                if (mediaDeviceSession !is Result.Data) {
+                    return@combine null
+                }
+                if (mediaDeviceSession.data?.playback?.isActive == true) {
                     val icon =
                         currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
                             ?: Icon.Resource(
@@ -130,6 +143,7 @@
 
     fun onBarClick(expandable: Expandable) {
         uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
-        actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
+        val result = sessionWithPlayback.value
+        actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
new file mode 100644
index 0000000..8793538
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.volume.panel.shared.model
+
+/** Models a loadable result */
+sealed interface Result<T> {
+
+    /** The data is still loading */
+    class Loading<T> : Result<T>
+
+    /** The data is loaded successfully */
+    data class Data<T>(val data: T) : Result<T>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e48b639..263ddc1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -55,6 +56,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -124,6 +126,8 @@
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
+    private final CommunalTransitionViewModel mCommunalTransitionViewModel;
+    private final JavaAdapter mJavaAdapter;
     private final Executor mSysUiMainExecutor;
 
     // Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -187,6 +191,8 @@
             UserTracker userTracker,
             DisplayTracker displayTracker,
             NoteTaskInitializer noteTaskInitializer,
+            CommunalTransitionViewModel communalTransitionViewModel,
+            JavaAdapter javaAdapter,
             @Main Executor sysUiMainExecutor) {
         mContext = context;
         mShell = shell;
@@ -205,6 +211,8 @@
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
+        mCommunalTransitionViewModel = communalTransitionViewModel;
+        mJavaAdapter = javaAdapter;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
 
@@ -381,6 +389,8 @@
     void initRecentTasks(RecentTasks recentTasks) {
         recentTasks.addAnimationStateListener(mSysUiMainExecutor,
                 mCommandQueue::onRecentsAnimationStateChanged);
+        mJavaAdapter.alwaysCollectFlow(mCommunalTransitionViewModel.getRecentsBackgroundColor(),
+                recentTasks::setTransitionBackgroundColor);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 12f334b..5bc9aa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -16,49 +16,84 @@
 
 package com.android.systemui.accessibility;
 
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
+import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
 import android.window.InputTransferToken;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
 
 @SmallTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class FullscreenMagnificationControllerTest extends SysuiTestCase {
-
+    private static final long ANIMATION_DURATION_MS = 100L;
+    private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
+    private static final long ANIMATION_TIMEOUT_MS =
+            5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
     private FullscreenMagnificationController mFullscreenMagnificationController;
     private SurfaceControlViewHost mSurfaceControlViewHost;
+    private ValueAnimator mShowHideBorderAnimator;
+    private SurfaceControl.Transaction mTransaction;
+    private TestableWindowManager mWindowManager;
 
     @Before
     public void setUp() {
         getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
-                new SurfaceControlViewHost(mContext, mContext.getDisplay(),
-                        new InputTransferToken(), "FullscreenMagnification"));
-
+                spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+                        new InputTransferToken(), "FullscreenMagnification")));
         Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+        final WindowManager wm = mContext.getSystemService(WindowManager.class);
+        mWindowManager = new TestableWindowManager(wm);
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
 
+        mTransaction = new SurfaceControl.Transaction();
+        mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
         mFullscreenMagnificationController = new FullscreenMagnificationController(
                 mContext,
+                mContext.getMainExecutor(),
                 mContext.getSystemService(AccessibilityManager.class),
                 mContext.getSystemService(WindowManager.class),
-                scvhSupplier);
+                scvhSupplier,
+                mTransaction,
+                mShowHideBorderAnimator);
     }
 
     @After
@@ -69,29 +104,143 @@
     }
 
     @Test
-    public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
-        getInstrumentation().runOnMainSync(
-                () -> mFullscreenMagnificationController
-                        .onFullscreenMagnificationActivationChanged(true)
-        );
-
-        // Wait for Rects updated.
-        waitForIdleSync();
+    public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        CountDownLatch animationEndLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animationEndLatch.countDown();
+            }
+        });
+        getInstrumentation().runOnMainSync(() ->
+                //Enable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true));
+        assertTrue("Failed to wait for transaction committed",
+                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        assertTrue("Failed to wait for animation to be finished",
+                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        verify(mShowHideBorderAnimator).start();
         assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
     }
 
     @Test
-    public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
-        getInstrumentation().runOnMainSync(
-                () -> {
-                    mFullscreenMagnificationController
-                            .onFullscreenMagnificationActivationChanged(true);
-                    mFullscreenMagnificationController
-                            .onFullscreenMagnificationActivationChanged(false);
+    public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
+            throws InterruptedException {
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
+        CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                if (isReverse) {
+                    disableAnimationEndLatch.countDown();
+                } else {
+                    enableAnimationEndLatch.countDown();
                 }
-        );
+            }
+        });
+        getInstrumentation().runOnMainSync(() ->
+                //Enable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true));
+        assertTrue("Failed to wait for transaction committed",
+                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        assertTrue("Failed to wait for enabling animation to be finished",
+                enableAnimationEndLatch.await(
+                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        verify(mShowHideBorderAnimator).start();
 
-        assertThat(mSurfaceControlViewHost.getView()).isNull();
+        getInstrumentation().runOnMainSync(() ->
+                // Disable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(false));
+
+        assertTrue("Failed to wait for disabling animation to be finished",
+                disableAnimationEndLatch.await(
+                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        verify(mShowHideBorderAnimator).reverse();
+        verify(mSurfaceControlViewHost).release();
     }
 
+    @Test
+    public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
+            throws InterruptedException {
+        // Simulate the hiding border animation is running
+        when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        CountDownLatch animationEndLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animationEndLatch.countDown();
+            }
+        });
+
+        getInstrumentation().runOnMainSync(
+                () -> mFullscreenMagnificationController
+                            .onFullscreenMagnificationActivationChanged(true));
+
+        assertTrue("Failed to wait for transaction committed",
+                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        assertTrue("Failed to wait for animation to be finished",
+                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        verify(mShowHideBorderAnimator).reverse();
+    }
+
+    @Test
+    public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
+            throws InterruptedException {
+        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+        CountDownLatch animationEndLatch = new CountDownLatch(1);
+        mTransaction.addTransactionCommittedListener(
+                Runnable::run, transactionCommittedLatch::countDown);
+        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animationEndLatch.countDown();
+            }
+        });
+        getInstrumentation().runOnMainSync(() ->
+                //Enable fullscreen magnification
+                mFullscreenMagnificationController
+                        .onFullscreenMagnificationActivationChanged(true));
+        assertTrue("Failed to wait for transaction committed",
+                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        assertTrue("Failed to wait for animation to be finished",
+                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mWindowManager.setWindowBounds(testWindowBounds);
+
+        getInstrumentation().runOnMainSync(() ->
+                mFullscreenMagnificationController.onConfigurationChanged(
+                        ActivityInfo.CONFIG_SCREEN_SIZE));
+
+        int borderOffset = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnifier_border_width_fullscreen_with_offset)
+                - mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnifier_border_width_fullscreen);
+        final int newWidth = testWindowBounds.width() + 2 * borderOffset;
+        final int newHeight = testWindowBounds.height() + 2 * borderOffset;
+        verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
+    }
+
+    private ValueAnimator newNullTargetObjectAnimator() {
+        final ValueAnimator animator =
+                ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+        Interpolator interpolator = new DecelerateInterpolator(2.5f);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(ANIMATION_DURATION_MS);
+        return animator;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 41d5d5d..25e5470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -101,7 +101,7 @@
         }).when(mAccessibilityManager).setMagnificationConnection(
                 any(IMagnificationConnection.class));
         mMagnification = new Magnification(getContext(),
-                getContext().getMainThreadHandler(), mCommandQueue,
+                getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                 mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
         mMagnification.mWindowMagnificationControllerSupplier =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 3b5cbea..6dc5b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -121,7 +121,8 @@
 
         mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
         mMagnification = new Magnification(getContext(),
-                getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+                getContext().getMainThreadHandler(), getContext().getMainExecutor(),
+                mCommandQueue, mModeSwitchesController,
                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
                 getContext().getSystemService(DisplayManager.class), mA11yLogger);
         mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index c900463..0f37143 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.lang.IllegalStateException
+import java.util.function.Consumer
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
@@ -78,7 +79,7 @@
     fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             verify(controllerFactory).create(eq(0), any())
@@ -107,7 +108,7 @@
     fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(
                 createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
                 onSaved,
@@ -136,7 +137,7 @@
     fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
         testScope.runTest {
             setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             verifyNoMoreInteractions(controllerFactory)
@@ -154,7 +155,7 @@
                 display(TYPE_OVERLAY, id = 2),
                 display(TYPE_WIFI, id = 3)
             )
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             verify(controller0, times(4)).handleScreenshot(any(), any(), any())
@@ -165,7 +166,7 @@
     fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -190,7 +191,7 @@
     fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -217,7 +218,7 @@
     fun executeScreenshots_allDisplaysFail_reportsFail() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -244,7 +245,7 @@
     fun onDestroy_propagatedToControllers() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onDestroy()
@@ -256,7 +257,7 @@
     fun removeWindows_propagatedToControllers() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.removeWindows()
@@ -270,7 +271,7 @@
     fun onCloseSystemDialogsReceived_propagatedToControllers() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onCloseSystemDialogsReceived()
@@ -286,7 +287,7 @@
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
             whenever(controller0.isPendingSharedTransition).thenReturn(true)
             whenever(controller1.isPendingSharedTransition).thenReturn(false)
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
 
             screenshotExecutor.onCloseSystemDialogsReceived()
@@ -304,7 +305,7 @@
             val toBeReturnedByProcessor = ScreenshotData.forTesting()
             requestProcessor.toReturn = toBeReturnedByProcessor
 
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback)
 
             assertThat(requestProcessor.processed)
@@ -321,7 +322,7 @@
     fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             requestProcessor.shouldThrowException = true
 
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -338,7 +339,7 @@
     fun executeScreenshots_errorFromProcessor_logsUiError() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             requestProcessor.shouldThrowException = true
 
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -355,7 +356,7 @@
     fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             requestProcessor.shouldThrowException = true
 
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -368,7 +369,7 @@
     fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             requestProcessor.shouldThrowException = true
 
             screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -381,7 +382,7 @@
     fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             whenever(controller0.handleScreenshot(any(), any(), any()))
                 .thenThrow(IllegalStateException::class.java)
             whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -401,7 +402,7 @@
     fun executeScreenshots_errorFromScreenshotController_reportsError() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             whenever(controller0.handleScreenshot(any(), any(), any()))
                 .thenThrow(IllegalStateException::class.java)
             whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -421,7 +422,7 @@
     fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
-            val onSaved = { _: Uri -> }
+            val onSaved = { _: Uri? -> }
             whenever(controller0.handleScreenshot(any(), any(), any()))
                 .thenThrow(IllegalStateException::class.java)
             whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -434,6 +435,25 @@
             screenshotExecutor.onDestroy()
         }
 
+    @Test
+    fun executeScreenshots_finisherCalledWithNullUri_succeeds() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0))
+            var onSavedCallCount = 0
+            val onSaved: (Uri?) -> Unit = {
+                assertThat(it).isNull()
+                onSavedCallCount += 1
+            }
+            whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer {
+                (it.getArgument(1) as Consumer<Uri?>).accept(null)
+            }
+
+            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+            assertThat(onSavedCallCount).isEqualTo(1)
+
+            screenshotExecutor.onDestroy()
+        }
+
     private suspend fun TestScope.setDisplays(vararg displays: Display) {
         fakeDisplayRepository.emit(displays.toSet())
         runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 0776aa7..77b5c91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -232,7 +232,7 @@
     override fun onCloseSystemDialogsReceived() {}
     override suspend fun executeScreenshots(
         screenshotRequest: ScreenshotRequest,
-        onSaved: (Uri) -> Unit,
+        onSaved: (Uri?) -> Unit,
         requestCallback: RequestCallback,
     ) {
         requestReceived = screenshotRequest
@@ -248,7 +248,7 @@
 
     override fun executeScreenshotsAsync(
         screenshotRequest: ScreenshotRequest,
-        onSaved: Consumer<Uri>,
+        onSaved: Consumer<Uri?>,
         requestCallback: RequestCallback,
     ) {
         runBlocking {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 621b058..254f1e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -188,6 +188,18 @@
      * actual values returned by ActivityTaskManager
      */
     object RootTasks {
+        /** An empty RootTaskInfo with no child tasks. */
+        val emptyWithNoChildTasks =
+            newRootTaskInfo(
+                taskId = 2,
+                visible = true,
+                running = true,
+                numActivities = 0,
+                bounds = FULL_SCREEN,
+            ) {
+                emptyList()
+            }
+
         /**
          * The empty RootTaskInfo that is always at the end of a list from ActivityTaskManager when
          * no other visible activities are in split mode
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
new file mode 100644
index 0000000..9e3ae05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2024 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE_PIP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class PrivateProfilePolicyTest {
+    private val kosmos = Kosmos()
+    private val policy = PrivateProfilePolicy(kosmos.profileTypeRepository)
+
+    // TODO:
+    // private app in PIP
+    // private app below personal PIP app
+    // Freeform windows
+
+    @Test
+    fun shadeExpanded_notMatched() = runTest {
+        val result =
+            policy.check(
+                singleFullScreen(
+                    spec = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+                    shadeExpanded = true
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.SHADE_EXPANDED))
+    }
+
+    @Test
+    fun noPrivate_notMatched() = runTest {
+        val result =
+            policy.check(
+                singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+            )
+
+        assertThat(result)
+            .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.NO_VISIBLE_TASKS))
+    }
+
+    @Test
+    fun withPrivateFullScreen_isMatched() = runTest {
+        val result =
+            policy.check(
+                singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                Matched(
+                    PrivateProfilePolicy.NAME,
+                    PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+                    CaptureParameters(
+                        type = FullScreen(displayId = 0),
+                        component = ComponentName.unflattenFromString(YOUTUBE),
+                        owner = UserHandle.of(PRIVATE)
+                    )
+                )
+            )
+    }
+
+    @Test
+    fun withPrivateNotVisible_notMatched() = runTest {
+        val result =
+            policy.check(
+                DisplayContentModel(
+                    displayId = 0,
+                    systemUiState = SystemUiState(shadeExpanded = false),
+                    rootTasks =
+                        listOf(
+                            fullScreen(
+                                TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+                                visible = true
+                            ),
+                            fullScreen(
+                                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                                visible = false
+                            ),
+                            launcher(visible = false),
+                            emptyRootSplit,
+                        )
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                NotMatched(
+                    PrivateProfilePolicy.NAME,
+                    PrivateProfilePolicy.NO_VISIBLE_TASKS,
+                )
+            )
+    }
+
+    @Test
+    fun withPrivateFocusedInSplitScreen_isMatched() = runTest {
+        val result =
+            policy.check(
+                splitScreenApps(
+                    top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+                    bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                    focusedTaskId = 1003
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                Matched(
+                    PrivateProfilePolicy.NAME,
+                    PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+                    CaptureParameters(
+                        type = FullScreen(displayId = 0),
+                        component = ComponentName.unflattenFromString(YOUTUBE),
+                        owner = UserHandle.of(PRIVATE)
+                    )
+                )
+            )
+    }
+
+    @Test
+    fun withPrivateNotFocusedInSplitScreen_isMatched() = runTest {
+        val result =
+            policy.check(
+                splitScreenApps(
+                    top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+                    bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                    focusedTaskId = 1002
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                Matched(
+                    PrivateProfilePolicy.NAME,
+                    PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+                    CaptureParameters(
+                        type = FullScreen(displayId = 0),
+                        component = ComponentName.unflattenFromString(FILES),
+                        owner = UserHandle.of(PRIVATE)
+                    )
+                )
+            )
+    }
+
+    @Test
+    fun withPrivatePictureInPictureApp_isMatched() = runTest {
+        val result =
+            policy.check(
+                pictureInPictureApp(TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PRIVATE))
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                Matched(
+                    PrivateProfilePolicy.NAME,
+                    PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+                    CaptureParameters(
+                        type = FullScreen(displayId = 0),
+                        component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+                        owner = UserHandle.of(PRIVATE)
+                    )
+                )
+            )
+    }
+
+    @Test
+    fun withPrivateAppBelowPictureInPictureApp_isMatched() = runTest {
+        val result =
+            policy.check(
+                pictureInPictureApp(
+                    pip = TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PERSONAL),
+                    fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PRIVATE),
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                Matched(
+                    PrivateProfilePolicy.NAME,
+                    PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+                    CaptureParameters(
+                        type = FullScreen(displayId = 0),
+                        component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+                        owner = UserHandle.of(PRIVATE)
+                    )
+                )
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
new file mode 100644
index 0000000..5d35528
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFormApps
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.WORK
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.DESKTOP_MODE_ENABLED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.SHADE_EXPANDED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_IS_TOP
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_NOT_TOP
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class WorkProfilePolicyTest {
+    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+    private val kosmos = Kosmos()
+    private val policy = WorkProfilePolicy(kosmos.profileTypeRepository)
+
+    /**
+     * There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test
+     * the case where the RootTaskInfo would match but child tasks are empty.
+     */
+    @Test
+    fun withEmptyChildTasks_notMatched() = runTest {
+        val result =
+            policy.check(
+                DisplayContentModel(
+                    displayId = 0,
+                    systemUiState = SystemUiState(shadeExpanded = false),
+                    rootTasks = listOf(RootTasks.emptyWithNoChildTasks)
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                NotMatched(
+                    WorkProfilePolicy.NAME,
+                    WORK_TASK_NOT_TOP,
+                )
+            )
+    }
+
+    @Test
+    fun noWorkApp_notMatched() = runTest {
+        val result =
+            policy.check(
+                singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                NotMatched(
+                    WorkProfilePolicy.NAME,
+                    WORK_TASK_NOT_TOP,
+                )
+            )
+    }
+
+    @Test
+    fun withWorkFullScreen_shadeExpanded_notMatched() = runTest {
+        val result =
+            policy.check(
+                singleFullScreen(
+                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                    shadeExpanded = true
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                NotMatched(
+                    WorkProfilePolicy.NAME,
+                    SHADE_EXPANDED,
+                )
+            )
+    }
+
+    @Test
+    fun withWorkFullScreen_matched() = runTest {
+        val result =
+            policy.check(singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)))
+
+        assertThat(result)
+            .isEqualTo(
+                PolicyResult.Matched(
+                    policy = WorkProfilePolicy.NAME,
+                    reason = WORK_TASK_IS_TOP,
+                    CaptureParameters(
+                        type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
+                        component = ComponentName.unflattenFromString(FILES),
+                        owner = UserHandle.of(WORK),
+                    )
+                )
+            )
+    }
+
+    @Test
+    fun withWorkFocusedInSplitScreen_matched() = runTest {
+        val result =
+            policy.check(
+                splitScreenApps(
+                    top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                    bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                    focusedTaskId = 1002
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                PolicyResult.Matched(
+                    policy = WorkProfilePolicy.NAME,
+                    reason = WORK_TASK_IS_TOP,
+                    CaptureParameters(
+                        type = IsolatedTask(taskId = 1002, taskBounds = SPLIT_TOP),
+                        component = ComponentName.unflattenFromString(FILES),
+                        owner = UserHandle.of(WORK),
+                    )
+                )
+            )
+    }
+
+    @Test
+    fun withWorkNotFocusedInSplitScreen_notMatched() = runTest {
+        val result =
+            policy.check(
+                splitScreenApps(
+                    top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                    bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                    focusedTaskId = 1003
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                NotMatched(
+                    WorkProfilePolicy.NAME,
+                    WORK_TASK_NOT_TOP,
+                )
+            )
+    }
+
+    @Test
+    fun withWorkBelowPersonalPictureInPicture_matched() = runTest {
+        val result =
+            policy.check(
+                pictureInPictureApp(
+                    pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+                    fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                PolicyResult.Matched(
+                    policy = WorkProfilePolicy.NAME,
+                    reason = WORK_TASK_IS_TOP,
+                    CaptureParameters(
+                        type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
+                        component = ComponentName.unflattenFromString(FILES),
+                        owner = UserHandle.of(WORK),
+                    )
+                )
+            )
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun withWorkFocusedInFreeForm_matched() = runTest {
+        val result =
+            policy.check(
+                freeFormApps(
+                    TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+                    TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+                    focusedTaskId = 1003
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                PolicyResult.Matched(
+                    policy = WorkProfilePolicy.NAME,
+                    reason = WORK_TASK_IS_TOP,
+                    CaptureParameters(
+                        type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
+                        component = ComponentName.unflattenFromString(FILES),
+                        owner = UserHandle.of(WORK),
+                    )
+                )
+            )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun withWorkFocusedInFreeForm_desktopModeEnabled_notMatched() = runTest {
+        val result =
+            policy.check(
+                freeFormApps(
+                    TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+                    TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+                    focusedTaskId = 1003
+                )
+            )
+
+        assertThat(result)
+            .isEqualTo(
+                NotMatched(
+                    WorkProfilePolicy.NAME,
+                    DESKTOP_MODE_ENABLED,
+                )
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index e611da0..ee03236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -85,6 +86,7 @@
     @Mock private lateinit var communalViewModel: CommunalViewModel
     @Mock private lateinit var powerManager: PowerManager
     @Mock private lateinit var dialogFactory: SystemUIDialogFactory
+    @Mock private lateinit var communalColors: CommunalColors
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var shadeInteractor: ShadeInteractor
     private lateinit var keyguardInteractor: KeyguardInteractor
@@ -116,6 +118,7 @@
                 keyguardInteractor,
                 shadeInteractor,
                 powerManager,
+                communalColors,
                 kosmos.sceneDataSourceDelegator,
             )
         testableLooper = TestableLooper.get(this)
@@ -156,6 +159,7 @@
                         keyguardInteractor,
                         shadeInteractor,
                         powerManager,
+                        communalColors,
                         kosmos.sceneDataSourceDelegator,
                     )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 0a8e470..7a39a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -154,6 +154,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
 import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -161,6 +162,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
+import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository;
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -358,6 +360,10 @@
     protected TestScope mTestScope = mKosmos.getTestScope();
     protected ShadeInteractor mShadeInteractor;
     protected PowerInteractor mPowerInteractor;
+    protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
+            new FakeHeadsUpNotificationRepository();
+    protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor =
+            new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository);
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
     protected SysuiStatusBarStateController mStatusBarStateController;
@@ -384,7 +390,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
         mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
         mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
 
@@ -730,6 +735,7 @@
                 mActivityStarter,
                 mSharedNotificationContainerInteractor,
                 mActiveNotificationsInteractor,
+                mHeadsUpNotificationInteractor,
                 mShadeAnimationInteractor,
                 mKeyguardViewConfigurator,
                 mDeviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 650c45b..81e20c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -46,6 +46,7 @@
 import android.animation.ValueAnimator;
 import android.graphics.Point;
 import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -62,6 +63,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
@@ -1287,6 +1289,7 @@
     }
 
     @Test
+    @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
     public void shadeExpanded_whenHunIsPresent() {
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 4df7ef5..6631d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.shade
 
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.HapticFeedbackConstants
@@ -29,10 +32,14 @@
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.advanceUntilIdle
@@ -235,4 +242,41 @@
         val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
         assertThat(bottomAreaAlpha).isEqualTo(1f)
     }
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    fun shadeExpanded_whenHunIsPresent() = runTest {
+        launch(mainDispatcher) {
+            givenViewAttached()
+
+            // WHEN a pinned heads up is present
+            mFakeHeadsUpNotificationRepository.setNotifications(
+                fakeHeadsUpRowRepository("key", isPinned = true)
+            )
+        }
+        advanceUntilIdle()
+
+        // THEN the panel should be visible
+        assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    fun shadeExpanded_whenHunIsAnimatingAway() = runTest {
+        launch(mainDispatcher) {
+            givenViewAttached()
+
+            // WHEN a heads up is animating away
+            mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
+        }
+        advanceUntilIdle()
+
+        // THEN the panel should be visible
+        assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+    }
+
+    private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
+        FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
+            this.isPinned.value = isPinned
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 8c5a4d0..da09579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -38,8 +38,6 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
-import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
-import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -166,8 +164,6 @@
             .thenReturn(emptyFlow<TransitionStep>())
 
         featureFlagsClassic = FakeFeatureFlagsClassic()
-        featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
-        featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
         featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
         mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index ba8eb6f..f380b6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -155,8 +155,6 @@
             .thenReturn(emptyFlow())
 
         val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
-        featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
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 1e058ca..89ae9f4 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
@@ -90,6 +90,7 @@
 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;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -106,6 +107,7 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 /**
  * Tests for {@link NotificationStackScrollLayout}.
@@ -1044,6 +1046,96 @@
         assertFalse(mStackScroller.getIsBeingDragged());
     }
 
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() {
+        // GIVEN NSSL is ready for HUN animations
+        Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+        // WHEN we generate a disappear event
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+        // THEN headsUpAnimatingAway is true
+        verify(headsUpAnimatingAwayListener).accept(true);
+        assertTrue(mStackScroller.mHeadsUpAnimatingAway);
+    }
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
+        // GIVEN NSSL would be ready for HUN animations, BUT it is expanded
+        Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        assertTrue("Should be expanded by default.", mStackScroller.isExpanded());
+        mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+        mStackScroller.setAnimationsEnabled(true);
+        mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+
+        // WHEN we generate a disappear event
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+        // THEN nothing happens
+        verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+        assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+    }
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() {
+        // GIVEN NSSL is ready for HUN animations
+        Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+        // BUT there is a pending appear event
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+        // WHEN we generate a disappear event
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+        // THEN nothing happens
+        verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+        assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+    }
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() {
+        // GIVEN NSSL is ready for HUN animations
+        Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+        // WHEN we generate a disappear event
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+        // THEN headsUpAnimatingWay is not set
+        verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+        assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+    }
+
+    @Test
+    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
+        // GIVEN NSSL is ready for HUN animations
+        Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+        ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+        // AND there is a HUN animating away
+        mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+        assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
+
+        // WHEN the child animations are finished
+        mStackScroller.onChildAnimationFinished();
+
+        // THEN headsUpAnimatingAway is false
+        verify(headsUpAnimatingAwayListener).accept(false);
+        assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+    }
+
     private MotionEvent captureTouchSentToSceneFramework() {
         ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
         verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
@@ -1056,6 +1148,14 @@
         mStackScroller.setStatusBarState(state);
     }
 
+    private void prepareStackScrollerForHunAnimations(
+            Consumer<Boolean> headsUpAnimatingAwayListener) {
+        mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+        mStackScroller.setIsExpanded(false);
+        mStackScroller.setAnimationsEnabled(true);
+        mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+    }
+
     private ExpandableNotificationRow createClearableRow() {
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         NotificationEntry entry = mock(NotificationEntry.class);
@@ -1116,4 +1216,6 @@
             assertThat(mActual.getY()).isEqualTo(expected.getY());
         }
     }
+
+    private abstract static class BooleanConsumer implements Consumer<Boolean> { }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
deleted file mode 100644
index d2c8aea..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.wmshell;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.notetask.NoteTaskInitializer;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedEventCallback;
-import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.sysui.ShellInterface;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-/**
- * Tests for {@link WMShell}.
- *
- * Build/Install/Run:
- *  atest SystemUITests:WMShellTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WMShellTest extends SysuiTestCase {
-    WMShell mWMShell;
-
-    @Mock ShellInterface mShellInterface;
-    @Mock CommandQueue mCommandQueue;
-    @Mock ConfigurationController mConfigurationController;
-    @Mock KeyguardStateController mKeyguardStateController;
-    @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock ScreenLifecycle mScreenLifecycle;
-    @Mock SysUiState mSysUiState;
-    @Mock Pip mPip;
-    @Mock SplitScreen mSplitScreen;
-    @Mock OneHanded mOneHanded;
-    @Mock WakefulnessLifecycle mWakefulnessLifecycle;
-    @Mock UserTracker mUserTracker;
-    @Mock ShellExecutor mSysUiMainExecutor;
-    @Mock NoteTaskInitializer mNoteTaskInitializer;
-    @Mock DesktopMode mDesktopMode;
-    @Mock RecentTasks mRecentTasks;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
-        mWMShell = new WMShell(
-                mContext,
-                mShellInterface,
-                Optional.of(mPip),
-                Optional.of(mSplitScreen),
-                Optional.of(mOneHanded),
-                Optional.of(mDesktopMode),
-                Optional.of(mRecentTasks),
-                mCommandQueue,
-                mConfigurationController,
-                mKeyguardStateController,
-                mKeyguardUpdateMonitor,
-                mScreenLifecycle,
-                mSysUiState,
-                mWakefulnessLifecycle,
-                mUserTracker,
-                displayTracker,
-                mNoteTaskInitializer,
-                mSysUiMainExecutor
-        );
-    }
-
-    @Test
-    public void initPip_registersCommandQueueCallback() {
-        mWMShell.initPip(mPip);
-
-        verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
-    }
-
-    @Test
-    public void initOneHanded_registersCallbacks() {
-        mWMShell.initOneHanded(mOneHanded);
-
-        verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
-        verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
-        verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
-        verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
-    }
-
-    @Test
-    public void initDesktopMode_registersListener() {
-        mWMShell.initDesktopMode(mDesktopMode);
-        verify(mDesktopMode).addVisibleTasksListener(
-                any(DesktopModeTaskRepository.VisibleTasksListener.class),
-                any(Executor.class));
-    }
-
-    @Test
-    public void initRecentTasks_registersListener() {
-        mWMShell.initRecentTasks(mRecentTasks);
-        verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any());
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index e36ddc1..e3c218d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.util.communalColors
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel
@@ -37,5 +38,6 @@
             glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
             communalInteractor = communalInteractor,
             keyguardTransitionInteractor = keyguardTransitionInteractor,
+            communalColors = communalColors,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
new file mode 100644
index 0000000..e76cf68
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.communal.util
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalColors: CommunalColors by Kosmos.Fixture { fakeCommunalColors }
+val Kosmos.fakeCommunalColors by Kosmos.Fixture { FakeCommunalColors() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
new file mode 100644
index 0000000..7046658
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.communal.util
+
+import android.graphics.Color
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCommunalColors : CommunalColors {
+    private val _backgroundColor = MutableStateFlow(Color.valueOf(Color.BLACK))
+
+    override val backgroundColor: StateFlow<Color>
+        get() = _backgroundColor.asStateFlow()
+
+    fun setBackgroundColor(color: Color) {
+        _backgroundColor.value = color
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index dc1b9fe..7bf77e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,4 +30,7 @@
     override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
     override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
         MutableStateFlow(emptySet())
+    override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+        isHeadsUpAnimatingAway.value = animatingAway
+    }
 }
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8ab2e0f..5846c92 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -91,6 +91,16 @@
 }
 
 flag {
+    name: "focus_click_point_window_bounds_from_a11y_window_info"
+    namespace: "accessibility"
+    description: "Uses A11yWindowInfo bounds for focus click point bounds checking"
+    bug: "317166487"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "fullscreen_fling_gesture"
     namespace: "accessibility"
     description: "When true, adds a fling gesture animation for fullscreen magnification"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ccf9a90..cadbd5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4623,6 +4623,20 @@
                 android.Manifest.permission.STATUS_BAR_SERVICE);
 
         getMagnificationConnectionManager().setConnection(connection);
+
+        if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+                && connection == null
+                && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+            // Since the connection does not exist, the system ui cannot provide the border
+            // implementation for fullscreen magnification. So we call reset to deactivate the
+            // fullscreen magnification to prevent the magnified but no border situation.
+            final ArrayList<Display> displays = getValidDisplayList();
+            for (int i = 0; i < displays.size(); i++) {
+                final Display display = displays.get(i);
+                getMagnificationController().getFullScreenMagnificationController()
+                        .reset(display.getDisplayId(), false);
+            }
+        }
     }
 
     /**
@@ -5234,7 +5248,14 @@
 
                 //Clip to the window bounds.
                 Rect windowBounds = mTempRect1;
-                getWindowBounds(focus.getWindowId(), windowBounds);
+                if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) {
+                    AccessibilityWindowInfo window = focus.getWindow();
+                    if (window != null) {
+                        window.getBoundsInScreen(windowBounds);
+                    }
+                } else {
+                    getWindowBounds(focus.getWindowId(), windowBounds);
+                }
                 if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) {
                     return false;
                 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index b119d7d..853b824 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import android.accessibilityservice.AccessibilityService;
+import android.annotation.RequiresNoPermission;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,7 +35,6 @@
  * If we are stripping and/or replacing the actions from a window, we need to intercept the
  * nodes heading back to the service and swap out the actions.
  */
-@SuppressWarnings("MissingPermissionAnnotation")
 public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub {
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "ActionReplacingCallback";
@@ -97,6 +97,7 @@
     }
 
     @Override
+    @RequiresNoPermission
     public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
         synchronized (mLock) {
             if (interactionId == mInteractionId) {
@@ -114,6 +115,7 @@
     }
 
     @Override
+    @RequiresNoPermission
     public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
             int interactionId) {
         synchronized (mLock) {
@@ -132,6 +134,7 @@
     }
 
     @Override
+    @RequiresNoPermission
     public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
                                                        int interactionId)
             throws RemoteException {
@@ -163,6 +166,7 @@
     }
 
     @Override
+    @RequiresNoPermission
     public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId)
             throws RemoteException {
         // There's no reason to use this class when performing actions. Do something reasonable.
@@ -170,6 +174,7 @@
     }
 
     @Override
+    @RequiresNoPermission
     public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
             throws RemoteException {
         mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
@@ -285,6 +290,7 @@
     }
 
     @Override
+    @RequiresNoPermission
     public void sendAttachOverlayResult(
             @AccessibilityService.AttachOverlayResult int result, int interactionId)
             throws RemoteException {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a5bbc7e..aa0af7e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@
 
     private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
     @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
+    @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
 
     /**
      * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -682,6 +683,12 @@
             if (!mRegistered) {
                 return false;
             }
+            // If the border implementation is on system ui side but the connection is not
+            // established, the fullscreen magnification should not work.
+            if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+                    && !mMagnificationConnectionStateSupplier.get()) {
+                return false;
+            }
             if (DEBUG) {
                 Slog.i(LOG_TAG,
                         "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
@@ -941,7 +948,8 @@
             @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
             @NonNull MagnificationScaleProvider scaleProvider,
-            @NonNull Executor backgroundExecutor) {
+            @NonNull Executor backgroundExecutor,
+            @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
         this(
                 new ControllerContext(
                         context,
@@ -955,7 +963,8 @@
                 /* thumbnailSupplier= */ null,
                 backgroundExecutor,
                 () -> new Scroller(context),
-                TimeAnimator::new);
+                TimeAnimator::new,
+                magnificationConnectionStateSupplier);
     }
 
     /** Constructor for tests */
@@ -968,11 +977,13 @@
             Supplier<MagnificationThumbnail> thumbnailSupplier,
             @NonNull Executor backgroundExecutor,
             Supplier<Scroller> scrollerSupplier,
-            Supplier<TimeAnimator> timeAnimatorSupplier) {
+            Supplier<TimeAnimator> timeAnimatorSupplier,
+            @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
         mControllerCtx = ctx;
         mLock = lock;
         mScrollerSupplier = scrollerSupplier;
         mTimeAnimatorSupplier = timeAnimatorSupplier;
+        mMagnificationConnectionStateSupplier = magnificationConnectionStateSupplier;
         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
         mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
         addInfoChangedCallback(magnificationInfoChangedCallback);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 20bec59..76367a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -798,7 +798,9 @@
                         mLock,
                         this,
                         mScaleProvider,
-                        mBackgroundExecutor
+                        mBackgroundExecutor,
+                        () -> (isMagnificationConnectionManagerInitialized()
+                                && getMagnificationConnectionManager().isConnected())
                 );
             }
         }
@@ -831,6 +833,12 @@
         }
     }
 
+    private boolean isMagnificationConnectionManagerInitialized() {
+        synchronized (mLock) {
+            return mMagnificationConnectionManager != null;
+        }
+    }
+
     private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
             if (mMagnificationConnectionManager == null
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 70ecc05..590a1ef 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -43,8 +43,15 @@
 }
 
 flag {
-  name: "include_last_focused_id_and_session_id_in_client_state"
+  name: "add_last_focused_id_to_client_state"
   namespace: "autofill"
-  description: "Include the current view id and session id into the FillEventHistory as part of ClientState"
+  description: "Include the current view id into the FillEventHistory events as part of ClientState"
   bug: "334141398"
 }
+
+flag {
+  name: "add_session_id_to_client_state"
+  namespace: "autofill"
+  description: "Include the session id into the FillEventHistory events as part of ClientState"
+  bug: "333927465"
+}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 82e9a26..7a2106b 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -175,7 +175,7 @@
 
             // Create a new association reassigned to this user and a valid association ID
             final String packageName = restored.getPackageName();
-            final int newId = mAssociationStore.getNextId(userId);
+            final int newId = mAssociationStore.getNextId();
             AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName,
                     restored).build();
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 9cfb535..c892b84 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -85,7 +85,7 @@
                     final int userId = getNextIntArgRequired();
                     final List<AssociationInfo> associationsForUser =
                             mAssociationStore.getActiveAssociationsByUser(userId);
-                    final int maxId = mAssociationStore.getMaxId(userId);
+                    final int maxId = mAssociationStore.getMaxId();
                     out.println("Max ID: " + maxId);
                     out.println("Association ID | Package Name | Mac Address");
                     for (AssociationInfo association : associationsForUser) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a18776e..1f09d4d 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -284,7 +284,7 @@
             @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
             boolean selfManaged, @Nullable IAssociationRequestCallback callback,
             @Nullable ResultReceiver resultReceiver) {
-        final int id = mAssociationStore.getNextId(userId);
+        final int id = mAssociationStore.getNextId();
         final long timestamp = System.currentTimeMillis();
 
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index ae2b708..29e8095 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -132,7 +132,7 @@
     @GuardedBy("mLock")
     private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>();
     @GuardedBy("mLock")
-    private final Map<Integer, Integer> mUserToMaxId = new HashMap<>();
+    private int mMaxId = 0;
 
     @GuardedBy("mLocalListeners")
     private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>();
@@ -162,7 +162,7 @@
                 mPersisted = false;
 
                 mIdToAssociationMap.clear();
-                mUserToMaxId.clear();
+                mMaxId = 0;
 
                 // The data is stored in DE directories, so we can read the data for all users now
                 // (which would not be possible if the data was stored to CE directories).
@@ -172,7 +172,7 @@
                     for (AssociationInfo association : entry.getValue().getAssociations()) {
                         mIdToAssociationMap.put(association.getId(), association);
                     }
-                    mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId());
+                    mMaxId = Math.max(mMaxId, entry.getValue().getMaxId());
                 }
 
                 mPersisted = true;
@@ -183,18 +183,18 @@
     /**
      * Get the current max association id.
      */
-    public int getMaxId(int userId) {
+    public int getMaxId() {
         synchronized (mLock) {
-            return mUserToMaxId.getOrDefault(userId, 0);
+            return mMaxId;
         }
     }
 
     /**
      * Get the next available association id.
      */
-    public int getNextId(int userId) {
+    public int getNextId() {
         synchronized (mLock) {
-            return getMaxId(userId) + 1;
+            return getMaxId() + 1;
         }
     }
 
@@ -214,7 +214,7 @@
             }
 
             mIdToAssociationMap.put(id, association);
-            mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id));
+            mMaxId = Math.max(mMaxId, id);
 
             writeCacheToDisk(userId);
 
@@ -305,7 +305,7 @@
         mExecutor.execute(() -> {
             Associations associations = new Associations();
             synchronized (mLock) {
-                associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0));
+                associations.setMaxId(mMaxId);
                 associations.setAssociations(
                         CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(),
                                 a -> a.getUserId() == userId));
diff --git a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
rename to services/core/java/com/android/server/ExplicitHealthCheckController.java
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index bdc4a7a..2545620 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -22,6 +22,7 @@
 per-file *Battery* = file:/BATTERY_STATS_OWNERS
 per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
 per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
+per-file ExplicitHealthCheckController.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
 per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
 per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
 per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
@@ -35,9 +36,9 @@
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
-per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
+per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
 per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS
-per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com
+per-file RescueParty.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
 per-file SensitiveContentProtectionManagerService.java = file:/core/java/android/permission/OWNERS
 per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
 per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
rename to services/core/java/com/android/server/PackageWatchdog.java
index 75a8bdf..6f20adf 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
-import android.utils.BackgroundThread;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/RescueParty.java
rename to services/core/java/com/android/server/RescueParty.java
index f86eb61..271d552 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -31,6 +31,7 @@
 import android.crashrecovery.flags.Flags;
 import android.os.Build;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
@@ -43,11 +44,10 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
-import android.utils.ArrayUtils;
-import android.utils.FileUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 1e7bc39..6c7546e 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.permission.flags.Flags.sensitiveContentImprovements;
 import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
 import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
@@ -24,6 +25,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS;
 import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
 import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -91,9 +93,11 @@
     private boolean mProjectionActive = false;
 
     private static class MediaProjectionSession {
-        final int mUid;
-        final long mSessionId;
-        final boolean mIsExempted;
+        private final int mUid;
+        private final long mSessionId;
+        private final boolean mIsExempted;
+        private final ArraySet<String> mAllSeenNotificationKeys = new ArraySet<>();
+        private final ArraySet<String> mSeenOtpNotificationKeys = new ArraySet<>();
 
         MediaProjectionSession(int uid, boolean isExempted, long sessionId) {
             mUid = uid;
@@ -123,6 +127,14 @@
             );
         }
 
+        public void logAppNotificationsProtected() {
+            FrameworkStatsLog.write(
+                    SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION,
+                    mSessionId,
+                    mAllSeenNotificationKeys.size(),
+                    mSeenOtpNotificationKeys.size());
+        }
+
         public void logAppBlocked(int uid) {
             FrameworkStatsLog.write(
                     FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
@@ -142,6 +154,32 @@
                     FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
             );
         }
+
+        private void addSeenNotificationKey(String key) {
+            mAllSeenNotificationKeys.add(key);
+        }
+
+        private void addSeenOtpNotificationKey(String key) {
+            mAllSeenNotificationKeys.add(key);
+            mSeenOtpNotificationKeys.add(key);
+        }
+
+        public void addSeenNotifications(
+                @NonNull StatusBarNotification[] notifications,
+                @NonNull RankingMap rankingMap) {
+            for (StatusBarNotification sbn : notifications) {
+                if (sbn == null) {
+                    Log.w(TAG, "Unable to parse null notification");
+                    continue;
+                }
+
+                if (notificationHasSensitiveContent(sbn, rankingMap)) {
+                    addSeenOtpNotificationKey(sbn.getKey());
+                } else {
+                    addSeenNotificationKey(sbn.getKey());
+                }
+            }
+        }
     }
 
     private final MediaProjectionManager.Callback mProjectionCallback =
@@ -297,6 +335,9 @@
             mProjectionActive = false;
             if (mMediaProjectionSession != null) {
                 mMediaProjectionSession.logProjectionSessionStop();
+                if (sensitiveContentImprovements()) {
+                    mMediaProjectionSession.logAppNotificationsProtected();
+                }
                 mMediaProjectionSession = null;
             }
 
@@ -334,9 +375,14 @@
             notifications = new StatusBarNotification[0];
         }
 
+        if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+            mMediaProjectionSession.addSeenNotifications(notifications, rankingMap);
+        }
+
         // notify windowmanager of any currently posted sensitive content notifications
         ArraySet<PackageInfo> packageInfos =
                 getSensitivePackagesFromNotifications(notifications, rankingMap);
+
         if (packageInfos.size() > 0) {
             mWindowManager.addBlockScreenCaptureForApps(packageInfos);
         }
@@ -420,6 +466,14 @@
                         mWindowManager.addBlockScreenCaptureForApps(
                                 new ArraySet(Set.of(packageInfo)));
                     }
+
+                    if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+                        if (packageInfo != null) {
+                            mMediaProjectionSession.addSeenOtpNotificationKey(sbn.getKey());
+                        } else {
+                            mMediaProjectionSession.addSeenNotificationKey(sbn.getKey());
+                        }
+                    }
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 67e18ca..4dd3a8f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -149,7 +149,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -3278,7 +3277,7 @@
             throws RemoteException {
         super.setCeStorageProtection_enforcePermission();
 
-        mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
+        mVold.setCeStorageProtection(userId, secret);
     }
 
     /* Only for use by LockSettingsService */
@@ -3288,7 +3287,7 @@
         super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
-            mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
+            mVold.unlockCeStorage(userId, secret);
         }
         synchronized (mLock) {
             mCeUnlockedUsers.append(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ea82b0..e47d416 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5725,14 +5725,11 @@
                 bringDownServiceLocked(r, enqueueOomAdj);
                 return msg;
             }
-            mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
-                    true);
+            mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app,
+                    r);
             if (isolated) {
                 r.isolationHostProc = app;
             }
-        } else {
-            mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
-                    false);
         }
 
         if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7739847..4568624 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -37,6 +37,9 @@
 import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
 import static android.app.ActivityManager.StopUserOnSwitch;
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
 import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
@@ -497,6 +500,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
@@ -683,8 +688,6 @@
 
     public final IntentFirewall mIntentFirewall;
 
-    public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
-
     /**
      * The global lock for AMS, it's de-facto the ActivityManagerService object as of now.
      */
@@ -2596,7 +2599,6 @@
                 BackgroundThread.getHandler(), this);
         mOnBattery = DEBUG_POWER ? true
                 : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
-        mOomAdjProfiler.batteryPowerChanged(mOnBattery);
 
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
@@ -2845,13 +2847,12 @@
         updateCpuStatsNow();
         synchronized (mProcLock) {
             mOnBattery = DEBUG_POWER ? true : onBattery;
-            mOomAdjProfiler.batteryPowerChanged(onBattery);
         }
     }
 
     @Override
     public void batteryStatsReset() {
-        mOomAdjProfiler.reset();
+        // Empty for now.
     }
 
     @Override
@@ -5107,10 +5108,19 @@
                 } // else, stopped packages in private space may still hit the logic below
             }
         }
+
+        final boolean wasForceStopped = app.wasForceStopped()
+                || app.getWindowProcessController().wasForceStopped();
+        if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
+            noteAppRestrictionEnabled(app.info.packageName, app.uid,
+                    RESTRICTION_LEVEL_FORCE_STOPPED, false,
+                    RESTRICTION_REASON_USAGE, "unknown", 0L);
+        }
+
         if (!sendBroadcast) {
             if (!android.content.pm.Flags.stayStopped()) return;
             // Nothing to do if it wasn't previously stopped
-            if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+            if (!wasForceStopped) {
                 return;
             }
         }
@@ -7417,7 +7427,6 @@
                 mServices.updateScreenStateLocked(isAwake);
                 reportCurWakefulnessUsageEvent();
                 mActivityTaskManager.onScreenAwakeChanged(isAwake);
-                mOomAdjProfiler.onWakefulnessChanged(wakefulness);
                 mOomAdjuster.onWakefulnessChanged(wakefulness);
 
                 updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
@@ -8949,8 +8958,10 @@
                     com.android.internal.R.integer.config_multiuserMaxRunningUsers);
             final boolean delayUserDataLocking = res.getBoolean(
                     com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
+            final int backgroundUserScheduledStopTimeSecs = res.getInteger(
+                    com.android.internal.R.integer.config_backgroundUserScheduledStopTimeSecs);
             mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
-                    delayUserDataLocking);
+                    delayUserDataLocking, backgroundUserScheduledStopTimeSecs);
         }
         mAppErrors.loadAppsNotReportingCrashesFromConfig(res.getString(
                 com.android.internal.R.string.config_appsNotReportingCrashes));
@@ -9845,6 +9856,11 @@
                 sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
             }
         }
+        if (eventType.equals("crash")) {
+            String formattedTime = DROPBOX_TIME_FORMATTER.format(
+                    Instant.now().atZone(ZoneId.systemDefault()));
+            sb.append("Timestamp: ").append(formattedTime).append("\n");
+        }
         if (activityShortComponentName != null) {
             sb.append("Activity: ").append(activityShortComponentName).append("\n");
         }
@@ -10542,12 +10558,6 @@
                     pw.println(
                             "-------------------------------------------------------------------------------");
                 }
-                mOomAdjProfiler.dump(pw);
-                pw.println();
-                if (dumpAll) {
-                    pw.println(
-                            "-------------------------------------------------------------------------------");
-                }
                 dumpLmkLocked(pw);
             }
             pw.println();
@@ -14331,6 +14341,20 @@
         int newBackupUid;
 
         synchronized(this) {
+            if (android.app.Flags.appRestrictionsApi()) {
+                try {
+                    final boolean wasStopped = mPackageManagerInt.isPackageStopped(app.packageName,
+                            UserHandle.getUserId(app.uid));
+                    if (wasStopped) {
+                        noteAppRestrictionEnabled(app.packageName, app.uid,
+                                RESTRICTION_LEVEL_FORCE_STOPPED, false,
+                                RESTRICTION_REASON_DEFAULT, "restore", 0L);
+                    }
+                } catch (NameNotFoundException e) {
+                    Slog.w(TAG, "No such package", e);
+                }
+            }
+
             // !!! TODO: currently no check here that we're already bound
             // Backup agent is now in use, its package can't be stopped.
             try {
@@ -20116,6 +20140,34 @@
     }
 
     /**
+     * Log the reason for changing an app restriction. Purely used for logging purposes and does not
+     * cause any change to app state.
+     *
+     * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long)
+     */
+    @Override
+    public void noteAppRestrictionEnabled(String packageName, int uid,
+            @RestrictionLevel int restrictionType, boolean enabled,
+            @ActivityManager.RestrictionReason int reason, String subReason, long threshold) {
+        if (!android.app.Flags.appRestrictionsApi()) return;
+
+        enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+                "noteAppRestrictionEnabled()");
+
+        final int userId = UserHandle.getCallingUserId();
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            if (uid == -1) {
+                uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+            }
+            mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
+                    enabled, reason, subReason, threshold);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
      * Get an app's background restriction level.
      * This interface is intended for the shell command to use.
      */
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 372ec45..5b15c37 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4006,8 +4006,12 @@
                 return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
             case "background_restricted":
                 return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
-            case "hibernation":
-                return ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+            case "force_stopped":
+                return ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+            case "user_launch_only":
+                return ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+            case "custom":
+                return ActivityManager.RESTRICTION_LEVEL_CUSTOM;
             default:
                 return ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 117221f..c5cad14 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -23,11 +23,20 @@
 import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
-import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
 import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT;
+import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER;
+import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED;
+import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH;
 import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
 import static android.app.ActivityManager.UID_OBSERVER_GONE;
 import static android.app.ActivityManager.UID_OBSERVER_IDLE;
@@ -93,6 +102,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManager.RestrictionReason;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
 import android.app.AppOpsManager;
@@ -344,6 +354,7 @@
     static final int TRACKER_TYPE_PERMISSION = 5;
     static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
     static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
+
     @IntDef(prefix = { "TRACKER_TYPE_" }, value = {
             TRACKER_TYPE_UNKNOWN,
             TRACKER_TYPE_BATTERY,
@@ -1714,7 +1725,7 @@
             String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
             boolean allowRequestBgRestricted, boolean calcTrackers) {
         if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
-            return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo);
+            return new Pair<>(RESTRICTION_LEVEL_FORCE_STOPPED, mEmptyTrackerInfo);
         }
         @RestrictionLevel int level;
         TrackerInfo trackerInfo = null;
@@ -2034,7 +2045,7 @@
                 return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
             case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
                 return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
-            case RESTRICTION_LEVEL_HIBERNATION:
+            case RESTRICTION_LEVEL_FORCE_STOPPED:
                 return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
             default:
                 return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
@@ -2354,6 +2365,85 @@
         }
     }
 
+    /**
+     * Log a change in restriction state with a reason and threshold.
+     * @param packageName
+     * @param uid
+     * @param restrictionType
+     * @param enabled
+     * @param reason
+     * @param subReason Eg: settings, cli, long_wakelock, crash, binder_spam, cpu, threads
+     *                  Length should not exceed RESTRICTON_SUBREASON_MAX_LENGTH
+     * @param threshold
+     */
+    public void noteAppRestrictionEnabled(String packageName, int uid,
+            @RestrictionLevel int restrictionType, boolean enabled,
+            @RestrictionReason int reason, String subReason, long threshold) {
+        if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+            Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to "
+                    + restrictionType + " reason=" + reason + ", subReason=" + subReason
+                    + ", threshold=" + threshold);
+        }
+
+        // Limit the length of the free-form subReason string
+        if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+            subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
+            Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
+        }
+
+        // Log the restriction reason
+        FrameworkStatsLog.write(FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED, uid,
+                getRestrictionTypeStatsd(restrictionType),
+                enabled,
+                getRestrictionChangeReasonStatsd(reason, subReason),
+                subReason,
+                threshold);
+    }
+
+    private int getRestrictionTypeStatsd(@RestrictionLevel int level) {
+        return switch (level) {
+            case RESTRICTION_LEVEL_UNKNOWN ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNKNOWN;
+            case RESTRICTION_LEVEL_UNRESTRICTED ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNRESTRICTED;
+            case RESTRICTION_LEVEL_EXEMPTED ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_EXEMPTED;
+            case RESTRICTION_LEVEL_ADAPTIVE_BUCKET ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_ADAPTIVE;
+            case RESTRICTION_LEVEL_RESTRICTED_BUCKET ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_RESTRICTED_BUCKET;
+            case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_BACKGROUND_RESTRICTED;
+            case RESTRICTION_LEVEL_FORCE_STOPPED ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_FORCE_STOPPED;
+            case RESTRICTION_LEVEL_USER_LAUNCH_ONLY ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_USER_LAUNCH_ONLY;
+            default ->
+                FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_CUSTOM;
+        };
+    }
+
+    private int getRestrictionChangeReasonStatsd(int reason, String subReason) {
+        return switch (reason) {
+            case RESTRICTION_REASON_DEFAULT ->
+                FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DEFAULT;
+            case RESTRICTION_REASON_DORMANT ->
+                FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DORMANT;
+            case RESTRICTION_REASON_USAGE ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE;
+            case RESTRICTION_REASON_USER ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER;
+            case RESTRICTION_REASON_USER_NUDGED ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED;
+            case RESTRICTION_REASON_SYSTEM_HEALTH ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH;
+            case RESTRICTION_REASON_REMOTE_TRIGGER ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER;
+            default ->
+                    FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER;
+        };
+    }
+
     static class NotificationHelper {
         static final String PACKAGE_SCHEME = "package";
         static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 0728ea8..2be1fe2 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -196,6 +196,8 @@
             start.setIntent(intent);
             start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
             start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+
+            // TODO: handle possible alarm activity start.
             if (intent != null && intent.getCategories() != null
                     && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
                 start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
@@ -313,7 +315,7 @@
     }
 
     public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
-                ServiceRecord serviceRecord, boolean cold) {
+                ServiceRecord serviceRecord) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
@@ -323,8 +325,9 @@
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
                     ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
-            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
-                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+
+            // TODO: handle possible alarm service start.
             start.setReason(serviceRecord.permission != null
                     && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
                     ? ApplicationStartInfo.START_REASON_JOB
@@ -336,8 +339,9 @@
         }
     }
 
-    public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
-                BroadcastRecord broadcast, boolean cold) {
+    /** Process a broadcast triggered app start. */
+    public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent,
+                boolean isAlarm) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
@@ -347,26 +351,19 @@
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
                     ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
-            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
-                    : ApplicationStartInfo.START_TYPE_WARM);
-            if (broadcast == null) {
-                start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
-            } else if (broadcast.alarm) {
+            start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+            if (isAlarm) {
                 start.setReason(ApplicationStartInfo.START_REASON_ALARM);
-            } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
-                start.setReason(ApplicationStartInfo.START_REASON_PUSH);
-            } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
-                start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
             } else {
                 start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
             }
-            start.setIntent(broadcast != null ? broadcast.intent : null);
+            start.setIntent(intent);
             addStartInfoLocked(start);
         }
     }
 
-    public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
-                boolean cold) {
+    /** Process a content provider triggered app start. */
+    public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
@@ -376,8 +373,7 @@
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
                     ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
-            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
-                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
             start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
             addStartInfoLocked(start);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c082889..48dd039 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1030,6 +1030,10 @@
                     "startProcessLocked failed");
             return true;
         }
+        // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
+        mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
+                SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver),
+                r.alarm /* isAlarm */);
         return false;
     }
 
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f76bf37..28fd197 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -179,6 +179,7 @@
         final int expectedUserId = userId;
         synchronized (mService) {
             long startTime = SystemClock.uptimeMillis();
+            long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos();
 
             ProcessRecord r = null;
             if (caller != null) {
@@ -585,6 +586,8 @@
                                     callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
                                     firstLaunch,
                                     0L /* TODO: stoppedDuration */);
+                            mService.mProcessList.getAppStartInfoTracker()
+                                    .handleProcessContentProviderStart(startElapsedTimeNs, proc);
                         }
                         cpr.launchingApp = proc;
                         mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
deleted file mode 100644
index 0869114..0000000
--- a/services/core/java/com/android/server/am/OomAdjProfiler.java
+++ /dev/null
@@ -1,226 +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.
- */
-
-package com.android.server.am;
-
-import android.os.Message;
-import android.os.PowerManagerInternal;
-import android.os.Process;
-import android.os.SystemClock;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.ProcessCpuTracker;
-import com.android.internal.util.RingBuffer;
-import com.android.internal.util.function.pooled.PooledLambda;
-
-import java.io.PrintWriter;
-
-public class OomAdjProfiler {
-    private static final int MSG_UPDATE_CPU_TIME = 42;
-
-    @GuardedBy("this")
-    private boolean mOnBattery;
-    @GuardedBy("this")
-    private boolean mScreenOff;
-
-    /** The value of {@link #mOnBattery} when the CPU time update was last scheduled. */
-    @GuardedBy("this")
-    private boolean mLastScheduledOnBattery;
-    /** The value of {@link #mScreenOff} when the CPU time update was last scheduled. */
-    @GuardedBy("this")
-    private boolean mLastScheduledScreenOff;
-
-    @GuardedBy("this")
-    private long mOomAdjStartTimeUs;
-    @GuardedBy("this")
-    private boolean mOomAdjStarted;
-
-    @GuardedBy("this")
-    private CpuTimes mOomAdjRunTime = new CpuTimes();
-    @GuardedBy("this")
-    private CpuTimes mSystemServerCpuTime = new CpuTimes();
-
-    @GuardedBy("this")
-    private long mLastSystemServerCpuTimeMs;
-    @GuardedBy("this")
-    private boolean mSystemServerCpuTimeUpdateScheduled;
-    private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
-
-    @GuardedBy("this")
-    final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
-    @GuardedBy("this")
-    final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
-
-    @GuardedBy("this")
-    private long mTotalOomAdjRunTimeUs;
-    @GuardedBy("this")
-    private int mTotalOomAdjCalls;
-
-    void batteryPowerChanged(boolean onBattery) {
-        synchronized (this) {
-            scheduleSystemServerCpuTimeUpdate();
-            mOnBattery = onBattery;
-        }
-    }
-
-    void onWakefulnessChanged(int wakefulness) {
-        synchronized (this) {
-            scheduleSystemServerCpuTimeUpdate();
-            mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
-        }
-    }
-
-    void oomAdjStarted() {
-        synchronized (this) {
-            mOomAdjStartTimeUs = SystemClock.currentThreadTimeMicro();
-            mOomAdjStarted = true;
-        }
-    }
-
-    void oomAdjEnded() {
-        synchronized (this) {
-            if (!mOomAdjStarted) {
-                return;
-            }
-            long elapsedUs = SystemClock.currentThreadTimeMicro() - mOomAdjStartTimeUs;
-            mOomAdjRunTime.addCpuTimeUs(elapsedUs);
-            mTotalOomAdjRunTimeUs += elapsedUs;
-            mTotalOomAdjCalls++;
-        }
-    }
-
-    private void scheduleSystemServerCpuTimeUpdate() {
-        synchronized (this) {
-            if (mSystemServerCpuTimeUpdateScheduled) {
-                return;
-            }
-            mLastScheduledOnBattery = mOnBattery;
-            mLastScheduledScreenOff = mScreenOff;
-            mSystemServerCpuTimeUpdateScheduled = true;
-            Message scheduledMessage = PooledLambda.obtainMessage(
-                    OomAdjProfiler::updateSystemServerCpuTime,
-                    this, mLastScheduledOnBattery, mLastScheduledScreenOff, true);
-            scheduledMessage.setWhat(MSG_UPDATE_CPU_TIME);
-
-            BackgroundThread.getHandler().sendMessage(scheduledMessage);
-        }
-    }
-
-    private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff,
-            boolean onlyIfScheduled) {
-        final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
-        synchronized (this) {
-            if (onlyIfScheduled && !mSystemServerCpuTimeUpdateScheduled) {
-                return;
-            }
-            mSystemServerCpuTime.addCpuTimeMs(
-                    cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
-            mLastSystemServerCpuTimeMs = cpuTimeMs;
-            mSystemServerCpuTimeUpdateScheduled = false;
-        }
-    }
-
-    void reset() {
-        synchronized (this) {
-            if (mSystemServerCpuTime.isEmpty()) {
-                return;
-            }
-            mOomAdjRunTimesHist.append(mOomAdjRunTime);
-            mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
-            mOomAdjRunTime = new CpuTimes();
-            mSystemServerCpuTime = new CpuTimes();
-        }
-    }
-
-    void dump(PrintWriter pw) {
-        synchronized (this) {
-            if (mSystemServerCpuTimeUpdateScheduled) {
-                // Cancel the scheduled update since we're going to update it here instead.
-                BackgroundThread.getHandler().removeMessages(MSG_UPDATE_CPU_TIME);
-                // Make sure the values are attributed to the right states.
-                updateSystemServerCpuTime(mLastScheduledOnBattery, mLastScheduledScreenOff, false);
-            } else {
-                updateSystemServerCpuTime(mOnBattery, mScreenOff, false);
-            }
-
-            pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
-                    + "(most recent first):");
-            if (!mSystemServerCpuTime.isEmpty()) {
-                pw.print("  ");
-                pw.print("system_server=");
-                pw.print(mSystemServerCpuTime);
-                pw.print("  ");
-                pw.print("oom_adj=");
-                pw.println(mOomAdjRunTime);
-            }
-            final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
-            final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
-            for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
-                pw.print("  ");
-                pw.print("system_server=");
-                pw.print(systemServerCpuTimes[i]);
-                pw.print("  ");
-                pw.print("oom_adj=");
-                pw.println(oomAdjRunTimes[i]);
-            }
-            if (mTotalOomAdjCalls != 0) {
-                pw.println("System server total oomAdj runtimes (us) since boot:");
-                pw.print("  cpu time spent=");
-                pw.print(mTotalOomAdjRunTimeUs);
-                pw.print("  number of calls=");
-                pw.print(mTotalOomAdjCalls);
-                pw.print("  average=");
-                pw.println(mTotalOomAdjRunTimeUs / mTotalOomAdjCalls);
-            }
-        }
-    }
-
-    private class CpuTimes {
-        private long mOnBatteryTimeUs;
-        private long mOnBatteryScreenOffTimeUs;
-
-        public void addCpuTimeMs(long cpuTimeMs) {
-            addCpuTimeUs(cpuTimeMs * 1000, mOnBattery, mScreenOff);
-        }
-
-        public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
-            addCpuTimeUs(cpuTimeMs * 1000, onBattery, screenOff);
-        }
-
-        public void addCpuTimeUs(long cpuTimeUs) {
-            addCpuTimeUs(cpuTimeUs, mOnBattery, mScreenOff);
-        }
-
-        public void addCpuTimeUs(long cpuTimeUs, boolean onBattery, boolean screenOff) {
-            if (onBattery) {
-                mOnBatteryTimeUs += cpuTimeUs;
-                if (screenOff) {
-                    mOnBatteryScreenOffTimeUs += cpuTimeUs;
-                }
-            }
-        }
-
-        public boolean isEmpty() {
-            return mOnBatteryTimeUs == 0 && mOnBatteryScreenOffTimeUs == 0;
-        }
-
-        public String toString() {
-            return "[" + (mOnBatteryTimeUs / 1000) + ","
-                    + (mOnBatteryScreenOffTimeUs / 1000) + "]";
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ea7a21d..9b72db8 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -600,7 +600,6 @@
 
         mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-        mService.mOomAdjProfiler.oomAdjStarted();
 
         final ProcessStateRecord state = app.mState;
 
@@ -630,7 +629,6 @@
         }
         mTmpProcessList.clear();
         mService.clearPendingTopAppLocked();
-        mService.mOomAdjProfiler.oomAdjEnded();
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         return true;
     }
@@ -849,7 +847,6 @@
 
         mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-        mService.mOomAdjProfiler.oomAdjStarted();
         mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
 
         final ArrayList<ProcessRecord> processes = mTmpProcessList;
@@ -862,7 +859,6 @@
         processes.clear();
         mService.clearPendingTopAppLocked();
 
-        mService.mOomAdjProfiler.oomAdjEnded();
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -895,7 +891,6 @@
         mLastReason = oomAdjReason;
         if (startProfiling) {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-            mService.mOomAdjProfiler.oomAdjStarted();
         }
         final long now = SystemClock.uptimeMillis();
         final long nowElapsed = SystemClock.elapsedRealtime();
@@ -989,7 +984,6 @@
         postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
 
         if (startProfiling) {
-            mService.mOomAdjProfiler.oomAdjEnded();
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 00e1482..3268b66 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -744,11 +744,9 @@
 
         mLastReason = oomAdjReason;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-        mService.mOomAdjProfiler.oomAdjStarted();
 
         fullUpdateLSP(oomAdjReason);
 
-        mService.mOomAdjProfiler.oomAdjEnded();
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -766,14 +764,12 @@
         mLastReason = oomAdjReason;
         mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-        mService.mOomAdjProfiler.oomAdjStarted();
 
         synchronized (mProcLock) {
             partialUpdateLSP(oomAdjReason, mPendingProcessSet);
         }
         mPendingProcessSet.clear();
 
-        mService.mOomAdjProfiler.oomAdjEnded();
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index dd4cee4..b703076 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -59,6 +59,7 @@
 import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
 import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
@@ -200,6 +201,7 @@
     static final int START_USER_SWITCH_FG_MSG = 120;
     static final int COMPLETE_USER_SWITCH_MSG = 130;
     static final int USER_COMPLETED_EVENT_MSG = 140;
+    static final int SCHEDULED_STOP_BACKGROUND_USER_MSG = 150;
 
     private static final int NO_ARG2 = 0;
 
@@ -251,6 +253,14 @@
     @GuardedBy("mLock")
     private int mMaxRunningUsers;
 
+    /**
+     * Number of seconds of uptime after a full user enters the background before we attempt
+     * to stop it due to inactivity. Set to -1 to disable scheduling stopping background users.
+     *
+     * Typically set by config_backgroundUserScheduledStopTimeSecs.
+     */
+    private int mBackgroundUserScheduledStopTimeSecs = -1;
+
     // Lock for internal state.
     private final Object mLock = new Object();
 
@@ -453,11 +463,12 @@
     }
 
     void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
-            boolean delayUserDataLocking) {
+            boolean delayUserDataLocking, int backgroundUserScheduledStopTimeSecs) {
         synchronized (mLock) {
             mUserSwitchUiEnabled = userSwitchUiEnabled;
             mMaxRunningUsers = maxRunningUsers;
             mDelayUserDataLocking = delayUserDataLocking;
+            mBackgroundUserScheduledStopTimeSecs = backgroundUserScheduledStopTimeSecs;
             mInitialized = true;
         }
     }
@@ -1091,6 +1102,10 @@
             final IStopUserCallback stopUserCallback,
             KeyEvictedCallback keyEvictedCallback) {
         Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
+        if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+            mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+                    Integer.valueOf(userId));
+        }
         final UserState uss = mStartedUsers.get(userId);
         if (uss == null) {  // User is not started
             // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
@@ -1879,6 +1894,10 @@
             // No matter what, the fact that we're requested to start the user (even if it is
             // already running) puts it towards the end of the mUserLru list.
             addUserToUserLru(userId);
+            if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+                mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+                        Integer.valueOf(userId));
+            }
 
             if (unlockListener != null) {
                 uss.mUnlockProgress.addListener(unlockListener);
@@ -1923,6 +1942,9 @@
                 // of mUserLru, so we need to ensure that the foreground user isn't displaced.
                 addUserToUserLru(mCurrentUserId);
             }
+            if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) {
+                scheduleStopOfBackgroundUser(userId);
+            }
             t.traceEnd();
 
             // Make sure user is in the started state.  If it is currently
@@ -2294,6 +2316,65 @@
         }
     }
 
+    /**
+     * Possibly schedules the user to be stopped at a future point. To be used to stop background
+     * users that haven't been actively used in a long time.
+     * This is only intended for full users that are currently in the background.
+     */
+    private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId) {
+        if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+            return;
+        }
+        final int delayUptimeSecs = mBackgroundUserScheduledStopTimeSecs;
+        if (delayUptimeSecs <= 0 || UserManager.isVisibleBackgroundUsersEnabled()) {
+            // Feature is not enabled on this device.
+            return;
+        }
+        if (oldUserId == UserHandle.USER_SYSTEM) {
+            // Never stop system user
+            return;
+        }
+        if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) {
+            // MainUser is currently special for things like Docking, so we'll exempt it for now.
+            Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue "
+                    + "of it being the main user", oldUserId);
+            return;
+        }
+        Slogf.d(TAG, "Scheduling to stop user %d in %d seconds", oldUserId, delayUptimeSecs);
+        final int delayUptimeMs = delayUptimeSecs * 1000;
+        final Object msgObj = oldUserId;
+        mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj);
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj),
+                delayUptimeMs);
+    }
+
+    /**
+     * Possibly stops the given full user due to it having been in the background for a long time.
+     * There is no guarantee of stopping the user; it is done discretionarily.
+     *
+     * This should never be called for background visible users; devices that support this should
+     * not use {@link #scheduleStopOfBackgroundUser(int)}.
+     *
+     * @param userIdInteger a full user to be stopped if it is still in the background
+     */
+    @VisibleForTesting
+    void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
+        final int userId = userIdInteger;
+        Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);
+        synchronized (mLock) {
+            if (getCurrentOrTargetUserIdLU() == userId) {
+                return;
+            }
+            if (mPendingTargetUserIds.contains(userIdInteger)) {
+                // We'll soon want to switch to this user, so don't kill it now.
+                return;
+            }
+            Slogf.i(TAG, "Stopping background user %d due to inactivity", userId);
+            stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null);
+        }
+    }
+
     private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
         t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
@@ -2428,6 +2509,7 @@
         uss.switching = false;
         stopGuestOrEphemeralUserIfBackground(oldUserId);
         stopUserOnSwitchIfEnforced(oldUserId);
+        scheduleStopOfBackgroundUser(oldUserId);
 
         t.traceEnd(); // end continueUserSwitch
     }
@@ -3309,6 +3391,8 @@
             pw.println("  shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
             pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
             pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
+            pw.println("  mBackgroundUserScheduledStopTimeSecs:"
+                    + mBackgroundUserScheduledStopTimeSecs);
             pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
             pw.println("  mInitialized:" + mInitialized);
             pw.println("  mIsBroadcastSentForSystemUserStarted:"
@@ -3435,6 +3519,9 @@
             case COMPLETE_USER_SWITCH_MSG:
                 completeUserSwitch(msg.arg1, msg.arg2);
                 break;
+            case SCHEDULED_STOP_BACKGROUND_USER_MSG:
+                processScheduledStopOfBackgroundUser((Integer) msg.obj);
+                break;
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index a17b3d5..ce41079 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -461,6 +461,9 @@
                     packageName, PACKAGE_MATCH_FLAGS, userId);
             StorageStats stats = mStorageStatsManager.queryStatsForPackage(
                     info.storageUuid, packageName, new UserHandle(userId));
+            if (android.app.Flags.appRestrictionsApi()) {
+                noteHibernationChange(packageName, info.uid, true);
+            }
             mIActivityManager.forceStopPackage(packageName, userId);
             mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
                     null /* observer */);
@@ -490,6 +493,11 @@
         // Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
         // their alarms/jobs/etc.
         try {
+            if (android.app.Flags.appRestrictionsApi()) {
+                ApplicationInfo info = mIPackageManager.getApplicationInfo(
+                        packageName, PACKAGE_MATCH_FLAGS, userId);
+                noteHibernationChange(packageName, info.uid, false);
+            }
             Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
                     .setPackage(packageName);
             final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
@@ -555,6 +563,26 @@
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
     }
 
+    /** Inform ActivityManager that the app being stopped or unstopped due to hibernation */
+    private void noteHibernationChange(String packageName, int uid, boolean hibernated) {
+        try {
+            if (hibernated) {
+                // TODO: Switch to an ActivityManagerInternal API
+                mIActivityManager.noteAppRestrictionEnabled(
+                        packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+                        true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
+                        /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
+            } else {
+                mIActivityManager.noteAppRestrictionEnabled(
+                        packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+                        false, ActivityManager.RESTRICTION_REASON_USAGE, null,
+                        0L);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Couldn't set restriction state change");
+        }
+    }
+
     /**
      * Initializes in-memory store of user-level hibernation states for the given user
      *
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a1f80d0..1bd93e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -5543,6 +5543,20 @@
                     }
                     return 0;
                 }
+                case "note": {
+                    int res = shell.parseUserPackageOp(true, err);
+                    if (res < 0) {
+                        return res;
+                    }
+                    if (shell.packageName != null) {
+                        shell.mInterface.noteOperation(shell.op, shell.packageUid,
+                                shell.packageName, shell.attributionTag, true,
+                                "appops note shell command", true);
+                    } else {
+                        return -1;
+                    }
+                    return 0;
+                }
                 case "start": {
                     int res = shell.parseUserPackageOp(true, err);
                     if (res < 0) {
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 9f4b3d2..c393e92 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1651,7 +1651,7 @@
                 return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
             case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
                 return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
-            case ActivityManager.RESTRICTION_LEVEL_HIBERNATION:
+            case ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED:
                 return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
             default:
                 return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index df5f007..68e2bd6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5271,13 +5271,5 @@
         public ExternalDisplayStatsService getExternalDisplayStatsService() {
             return mExternalDisplayStatsService;
         }
-
-        /**
-         * Called on external display is ready to be enabled.
-         */
-        @Override
-        public void onExternalDisplayReadyToBeEnabled(int displayId) {
-            mDisplayModeDirector.onExternalDisplayReadyToBeEnabled(displayId);
-        }
     }
 }
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 3c2918f..b24caf4 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -91,8 +91,6 @@
 
         @NonNull
         ExternalDisplayStatsService getExternalDisplayStatsService();
-
-        void onExternalDisplayReadyToBeEnabled(int displayId);
     }
 
     @NonNull
@@ -187,10 +185,6 @@
             return;
         }
 
-        if (enabled) {
-            mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked());
-        }
-
         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
     }
 
@@ -223,7 +217,6 @@
         if ((Build.IS_ENG || Build.IS_USERDEBUG)
                 && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
             Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
-            mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked());
             mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
             return;
         } else {
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 90d3345..fa42316 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -700,13 +700,6 @@
     }
 
     /**
-     * Called when external display is ready to be enabled.
-     */
-    public void onExternalDisplayReadyToBeEnabled(int displayId) {
-        mDisplayObserver.onExternalDisplayReadyToBeEnabled(displayId);
-    }
-
-    /**
      * Provides access to DisplayDeviceConfig for specific display
      */
     public interface DisplayDeviceConfigProvider {
@@ -1372,13 +1365,6 @@
             }
         }
 
-
-        void onExternalDisplayReadyToBeEnabled(int displayId) {
-            DisplayInfo displayInfo = getDisplayInfo(displayId);
-            updateDisplaysPeakRefreshRateAndResolution(displayInfo);
-            addDisplaysSynchronizedPeakRefreshRate(displayInfo);
-        }
-
         @Override
         public void onDisplayAdded(int displayId) {
             updateDisplayDeviceConfig(displayId);
@@ -1386,6 +1372,8 @@
             updateDisplayModes(displayId, displayInfo);
             updateLayoutLimitedFrameRate(displayId, displayInfo);
             updateUserSettingDisplayPreferredSize(displayInfo);
+            updateDisplaysPeakRefreshRateAndResolution(displayInfo);
+            addDisplaysSynchronizedPeakRefreshRate(displayInfo);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2583d73..aa18175 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1099,7 +1099,7 @@
                         imesToClearAdditionalSubtypes.add(imiId);
                     }
                     int change = isPackageDisappearing(imi.getPackageName());
-                    if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
+                    if (change == PACKAGE_PERMANENT_CHANGE) {
                         Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
                         if (isCurrentUser) {
                             setInputMethodEnabledLocked(imi.getId(), false);
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 71a9f54..32a0ef4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -38,6 +38,7 @@
 import android.util.Log;
 
 import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.server.FgThread;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -200,7 +201,12 @@
 
         SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
         if (subManager != null) {
-            subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+            if (Flags.subscriptionsListenerThread()) {
+                subManager.addOnSubscriptionsChangedListener(FgThread.getExecutor(),
+                        mOnSubscriptionsChangeListener);
+            } else {
+                subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+            }
         }
 
         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
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 bd0501d..20c5b5f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -462,6 +462,11 @@
     }
 
     @Override
+    public int getNumRegisteredAttributionSources(int uid) {
+        return mAttributionSourceRegistry.getNumRegisteredAttributionSources(uid);
+    }
+
+    @Override
     public List<String> getAutoRevokeExemptionRequestedPackages(int userId) {
         return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId);
     }
@@ -938,6 +943,26 @@
             }
         }
 
+        public int getNumRegisteredAttributionSources(int uid) {
+            mContext.enforceCallingOrSelfPermission(UPDATE_APP_OPS_STATS,
+                    "getting the number of registered AttributionSources requires "
+                            + "UPDATE_APP_OPS_STATS");
+            // Influence the system to perform a garbage collection, so the provided number is as
+            // accurate as possible
+            System.gc();
+            System.gc();
+            synchronized (mLock) {
+                int[] numForUid = { 0 };
+                mAttributions.forEach((key, value) -> {
+                    if (value.getUid() == uid) {
+                        numForUid[0]++;
+                    }
+
+                });
+                return numForUid[0];
+            }
+        }
+
         private int resolveUid(int uid) {
             final VoiceInteractionManagerInternal vimi = LocalServices
                     .getService(VoiceInteractionManagerInternal.class);
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
rename to services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
rename to services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5b501e1..587be07 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.security;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -27,6 +28,7 @@
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -79,7 +81,11 @@
         return LocalServices.getService(FileIntegrityService.class);
     }
 
-    private final IBinder mService = new IFileIntegrityService.Stub() {
+    private final class BinderService extends IFileIntegrityService.Stub {
+        BinderService(Context context) {
+            super(PermissionEnforcer.fromContext(context));
+        }
+
         @Override
         public boolean isApkVeritySupported() {
             return VerityUtils.isFsVeritySupported();
@@ -168,12 +174,10 @@
         }
 
         @Override
+        @EnforcePermission(android.Manifest.permission.SETUP_FSVERITY)
         public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken,
                 String filePath, String packageName) throws RemoteException {
-            getContext().enforceCallingPermission(android.Manifest.permission.SETUP_FSVERITY,
-                    "Permission android.permission.SETUP_FSVERITY not grantted to access "
-                    + "FileIntegrityManager#setupFsverity");
-
+            setupFsverity_enforcePermission();
             Objects.requireNonNull(authToken);
             Objects.requireNonNull(filePath);
             Objects.requireNonNull(packageName);
@@ -185,10 +189,12 @@
                 throw new RemoteException(e);
             }
         }
-    };
+    }
+    private final IBinder mService;
 
     public FileIntegrityService(final Context context) {
         super(context);
+        mService = new BinderService(context);
         try {
             sCertFactory = CertificateFactory.getInstance("X.509");
         } catch (CertificateException e) {
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
index e89ddfd..34d18ce 100644
--- a/services/core/java/com/android/server/selinux/QuotaLimiter.java
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -34,10 +34,10 @@
 
     private final Clock mClock;
     private final Duration mWindowSize;
-    private final int mMaxPermits;
 
-    private long mCurrentWindow = 0;
-    private int mPermitsGranted = 0;
+    private int mMaxPermits;
+    private long mCurrentWindow;
+    private int mPermitsGranted;
 
     @VisibleForTesting
     QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
@@ -75,4 +75,8 @@
 
         return false;
     }
+
+    public void setMaxPermits(int maxPermits) {
+        this.mMaxPermits = maxPermits;
+    }
 }
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
index 8d8d596..d69150d 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -15,35 +15,66 @@
  */
 package com.android.server.selinux;
 
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 import java.util.stream.Stream;
 
 /** Builder for SelinuxAuditLogs. */
 class SelinuxAuditLogBuilder {
 
-    // Currently logs collection is hardcoded for the sdk_sandbox_audit.
-    private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
-    static final Matcher SCONTEXT_MATCHER =
-            Pattern.compile(
-                            "u:r:(?<stype>"
-                                    + SDK_SANDBOX_AUDIT
-                                    + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
-                    .matcher("");
+    private static final String TAG = "SelinuxAuditLogs";
 
-    static final Matcher TCONTEXT_MATCHER =
-            Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
-                    .matcher("");
+    // This config indicates which Selinux logs for source domains to collect. The string will be
+    // inserted into a regex, so it must follow the regex syntax. For example, a valid value would
+    // be "system_server|untrusted_app".
+    @VisibleForTesting static final String CONFIG_SELINUX_AUDIT_DOMAIN = "selinux_audit_domain";
+    private static final Matcher NO_OP_MATCHER = Pattern.compile("no-op^").matcher("");
+    private static final String TCONTEXT_PATTERN =
+            "u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*";
+    private static final String PATH_PATTERN = "\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"";
 
-    static final Matcher PATH_MATCHER =
-            Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+    @VisibleForTesting final Matcher mScontextMatcher;
+    @VisibleForTesting final Matcher mTcontextMatcher;
+    @VisibleForTesting final Matcher mPathMatcher;
 
     private Iterator<String> mTokens;
     private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
 
+    SelinuxAuditLogBuilder() {
+        Matcher scontextMatcher = NO_OP_MATCHER;
+        Matcher tcontextMatcher = NO_OP_MATCHER;
+        Matcher pathMatcher = NO_OP_MATCHER;
+        try {
+            scontextMatcher =
+                    Pattern.compile(
+                                    TextUtils.formatSimple(
+                                            "u:r:(?<stype>%s):s0(:c)?(?<scategories>((,c)?\\d+)+)*",
+                                            DeviceConfig.getString(
+                                                    DeviceConfig.NAMESPACE_ADSERVICES,
+                                                    CONFIG_SELINUX_AUDIT_DOMAIN,
+                                                    "no_match^")))
+                            .matcher("");
+            tcontextMatcher = Pattern.compile(TCONTEXT_PATTERN).matcher("");
+            pathMatcher = Pattern.compile(PATH_PATTERN).matcher("");
+        } catch (PatternSyntaxException e) {
+            Slog.e(TAG, "Invalid pattern, setting every matcher to no-op.", e);
+        }
+
+        mScontextMatcher = scontextMatcher;
+        mTcontextMatcher = tcontextMatcher;
+        mPathMatcher = pathMatcher;
+    }
+
     void reset(String denialString) {
         mTokens =
                 Arrays.asList(
@@ -82,18 +113,18 @@
                     mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
                     break;
                 case "scontext":
-                    if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+                    if (!nextTokenMatches(mScontextMatcher)) {
                         return null;
                     }
-                    mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
-                    mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+                    mAuditLog.mSType = mScontextMatcher.group("stype");
+                    mAuditLog.mSCategories = toCategories(mScontextMatcher.group("scategories"));
                     break;
                 case "tcontext":
-                    if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+                    if (!nextTokenMatches(mTcontextMatcher)) {
                         return null;
                     }
-                    mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
-                    mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+                    mAuditLog.mTType = mTcontextMatcher.group("ttype");
+                    mAuditLog.mTCategories = toCategories(mTcontextMatcher.group("tcategories"));
                     break;
                 case "tclass":
                     if (!mTokens.hasNext()) {
@@ -102,8 +133,8 @@
                     mAuditLog.mTClass = mTokens.next();
                     break;
                 case "path":
-                    if (nextTokenMatches(PATH_MATCHER)) {
-                        mAuditLog.mPath = PATH_MATCHER.group("path");
+                    if (nextTokenMatches(mPathMatcher)) {
+                        mAuditLog.mPath = mPathMatcher.group("path");
                     }
                     break;
                 case "permissive":
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
index 03822aa..c655d46 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -18,10 +18,12 @@
 import android.util.EventLog;
 import android.util.EventLog.Event;
 import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import com.android.server.utils.Slogf;
 
 import java.io.IOException;
 import java.time.Instant;
@@ -37,6 +39,7 @@
 class SelinuxAuditLogsCollector {
 
     private static final String TAG = "SelinuxAuditLogs";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
 
@@ -48,13 +51,17 @@
 
     @VisibleForTesting Instant mLastWrite = Instant.MIN;
 
-    final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+    AtomicBoolean mStopRequested = new AtomicBoolean(false);
 
     SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
         mRateLimiter = rateLimiter;
         mQuotaLimiter = quotaLimiter;
     }
 
+    public void setStopRequested(boolean stopRequested) {
+        mStopRequested.set(stopRequested);
+    }
+
     /**
      * Collect and push SELinux audit logs for the provided {@code tagCode}.
      *
@@ -66,7 +73,7 @@
 
         boolean quotaExceeded = writeAuditLogs(logLines);
         if (quotaExceeded) {
-            Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+            Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
             mLastWrite = latestTimestamp; // next run we will ignore all these logs.
             logLines.clear();
         }
@@ -79,7 +86,7 @@
         try {
             EventLog.readEvents(new int[] {tagCode}, events);
         } catch (IOException e) {
-            Log.e(TAG, "Error reading event logs", e);
+            Slog.e(TAG, "Error reading event logs", e);
         }
 
         Instant latestTimestamp = mLastWrite;
@@ -102,6 +109,7 @@
 
     private boolean writeAuditLogs(Queue<Event> logLines) {
         final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+        int auditsWritten = 0;
 
         while (!mStopRequested.get() && !logLines.isEmpty()) {
             Event event = logLines.poll();
@@ -118,6 +126,9 @@
             }
 
             if (!mQuotaLimiter.acquire()) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten);
+                }
                 return true;
             }
             mRateLimiter.acquire();
@@ -133,12 +144,16 @@
                     auditLog.mTClass,
                     auditLog.mPath,
                     auditLog.mPermissive);
+            auditsWritten++;
 
             if (logTime.isAfter(mLastWrite)) {
                 mLastWrite = logTime;
             }
         }
 
+        if (DEBUG) {
+            Slogf.d(TAG, "Written %d logs", auditsWritten);
+        }
         return false;
     }
 }
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
new file mode 100644
index 0000000..0092c37
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class handles the start and stop requests for the logs collector job, in particular making
+ * sure that at most one job is running at any given moment.
+ */
+final class SelinuxAuditLogsJob {
+
+    private static final String TAG = "SelinuxAuditLogs";
+
+    private final AtomicBoolean mIsRunning = new AtomicBoolean(false);
+    private final SelinuxAuditLogsCollector mAuditLogsCollector;
+
+    SelinuxAuditLogsJob(SelinuxAuditLogsCollector auditLogsCollector) {
+        mAuditLogsCollector = auditLogsCollector;
+    }
+
+    void requestStop() {
+        mAuditLogsCollector.mStopRequested.set(true);
+    }
+
+    boolean isRunning() {
+        return mIsRunning.get();
+    }
+
+    public void start(JobService jobService, JobParameters params) {
+        mAuditLogsCollector.mStopRequested.set(false);
+        if (mIsRunning.get()) {
+            Slog.i(TAG, "Selinux audit job is already running, ignore start request.");
+            return;
+        }
+        mIsRunning.set(true);
+        boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE);
+        if (done) {
+            jobService.jobFinished(params, /* wantsReschedule= */ false);
+        }
+        mIsRunning.set(false);
+    }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
index 8a661bc..d46e891 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -23,14 +23,16 @@
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
 import android.util.EventLog;
-import android.util.Log;
+import android.util.Slog;
 
 import java.time.Duration;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
@@ -43,58 +45,68 @@
 
     static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
 
+    private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS =
+            "selinux_audit_job_frequency_hours";
+    private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job";
+    private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap";
+    private static final int MAX_PERMITS_CAP_DEFAULT = 50000;
+
     private static final int SELINUX_AUDIT_JOB_ID = 25327386;
-    private static final JobInfo SELINUX_AUDIT_JOB =
-            new JobInfo.Builder(
-                            SELINUX_AUDIT_JOB_ID,
-                            new ComponentName("android", SelinuxAuditLogsService.class.getName()))
-                    .setPeriodic(TimeUnit.DAYS.toMillis(1))
-                    .setRequiresDeviceIdle(true)
-                    .setRequiresCharging(true)
-                    .setRequiresBatteryNotLow(true)
-                    .build();
+    private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT =
+            new ComponentName("android", SelinuxAuditLogsService.class.getName());
 
     private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
-    private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
 
-    // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
-    // milliseconds, and no more than 50K atoms can be pushed each day.
-    private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
-            new SelinuxAuditLogsCollector(
-                    new RateLimiter(/* window= */ Duration.ofMillis(10)),
-                    new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+    // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure
+    // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the
+    // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static
+    // because new job executions happen in a new instance of this class. Making the quota limiter
+    // an instance reference would reset the quota limitations between jobs executions.
+    private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
+    private static final QuotaLimiter QUOTA_LIMITER =
+            new QuotaLimiter(
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_ADSERVICES,
+                            CONFIG_SELINUX_AUDIT_CAP,
+                            MAX_PERMITS_CAP_DEFAULT));
+    private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB =
+            new SelinuxAuditLogsJob(
+                    new SelinuxAuditLogsCollector(
+                            new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER));
 
     /** Schedule jobs with the {@link JobScheduler}. */
     public static void schedule(Context context) {
         if (!selinuxSdkSandboxAudit()) {
-            Log.d(TAG, "SelinuxAuditLogsService not enabled");
+            Slog.d(TAG, "SelinuxAuditLogsService not enabled");
             return;
         }
 
         if (AUDITD_TAG_CODE == -1) {
-            Log.e(TAG, "auditd is not a registered tag on this system");
+            Slog.e(TAG, "auditd is not a registered tag on this system");
             return;
         }
 
-        if (context.getSystemService(JobScheduler.class)
-                        .forNamespace(SELINUX_AUDIT_NAMESPACE)
-                        .schedule(SELINUX_AUDIT_JOB)
-                == JobScheduler.RESULT_FAILURE) {
-            Log.e(TAG, "SelinuxAuditLogsService could not be started.");
-        }
+        LogsCollectorJobScheduler propertiesListener =
+                new LogsCollectorJobScheduler(
+                        context.getSystemService(JobScheduler.class)
+                                .forNamespace(SELINUX_AUDIT_NAMESPACE));
+        propertiesListener.schedule();
+        DeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener);
     }
 
     @Override
     public boolean onStartJob(JobParameters params) {
         if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
-            Log.e(TAG, "The job id does not match the expected selinux job id.");
+            Slog.e(TAG, "The job id does not match the expected selinux job id.");
+            return false;
+        }
+        if (!selinuxSdkSandboxAudit()) {
+            Slog.i(TAG, "Selinux audit job disabled.");
             return false;
         }
 
-        AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
-        IS_RUNNING.set(true);
-        EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
-
+        EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params));
         return true; // the job is running
     }
 
@@ -104,29 +116,69 @@
             return false;
         }
 
-        AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
-        return IS_RUNNING.get();
+        if (LOGS_COLLECTOR_JOB.isRunning()) {
+            LOGS_COLLECTOR_JOB.requestStop();
+            return true;
+        }
+        return false;
     }
 
-    private static class LogsCollectorJob implements Runnable {
-        private final JobService mAuditLogService;
-        private final JobParameters mParams;
+    /**
+     * This class is in charge of scheduling the job service, and keeping the scheduling up to date
+     * when the parameters change.
+     */
+    private static final class LogsCollectorJobScheduler
+            implements DeviceConfig.OnPropertiesChangedListener {
 
-        LogsCollectorJob(JobService auditLogService, JobParameters params) {
-            mAuditLogService = auditLogService;
-            mParams = params;
+        private final JobScheduler mJobScheduler;
+
+        private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
+            mJobScheduler = jobScheduler;
         }
 
         @Override
-        public void run() {
-            IS_RUNNING.updateAndGet(
-                    isRunning -> {
-                        boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
-                        if (done) {
-                            mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
-                        }
-                        return !done;
-                    });
+        public void onPropertiesChanged(Properties changedProperties) {
+            Set<String> keyset = changedProperties.getKeyset();
+
+            if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) {
+                QUOTA_LIMITER.setMaxPermits(
+                        changedProperties.getInt(
+                                CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT));
+            }
+
+            if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) {
+                boolean enabled =
+                        changedProperties.getBoolean(
+                                CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false);
+                if (enabled) {
+                    schedule();
+                } else {
+                    mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
+                }
+            } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) {
+                // The job frequency changed, reschedule.
+                schedule();
+            }
+        }
+
+        private void schedule() {
+            long frequencyMillis =
+                    TimeUnit.HOURS.toMillis(
+                            DeviceConfig.getInt(
+                                    DeviceConfig.NAMESPACE_ADSERVICES,
+                                    CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS,
+                                    24));
+            if (mJobScheduler.schedule(
+                            new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT)
+                                    .setPeriodic(frequencyMillis)
+                                    .setRequiresDeviceIdle(true)
+                                    .setRequiresBatteryNotLow(true)
+                                    .build())
+                    == JobScheduler.RESULT_FAILURE) {
+                Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled.");
+            } else {
+                Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully.");
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index 4a81c95..440d251 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -89,8 +89,34 @@
      * @param ownerId the removing client id of the owner.
      */
     public void removeOwner(int ownerId) {
-        mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
-        mOwnerClientIdsToSessionNum.remove(ownerId);
+        if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+            mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+            mOwnerClientIdsToSessionNum.remove(ownerId);
+        }
+    }
+
+    /**
+     * Remove a single session from resource
+     *
+     * @param ownerId the client Id of the owner of the session
+     */
+    public void removeSession(int ownerId) {
+        if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+            int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId);
+            if (sessionNum > 0) {
+                mOwnerClientIdsToSessionNum.put(ownerId, --sessionNum);
+                mAvailableSessionNum++;
+            }
+        }
+    }
+
+    /**
+     * Check if there are any open sessions owned by a client
+     *
+     * @param ownerId the client Id of the owner of the sessions
+     */
+    public boolean hasOpenSessions(int ownerId) {
+        return mOwnerClientIdsToSessionNum.get(ownerId) > 0;
     }
 
     public Set<Integer> getOwnerClientIds() {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index cddc79d..0afb049 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1924,11 +1924,13 @@
         ownerProfile.useCiCam(grantingId);
     }
 
-    private void updateCasClientMappingOnRelease(
-            @NonNull CasResource releasingCas, int ownerClientId) {
-        ClientProfile ownerProfile = getClientProfile(ownerClientId);
-        releasingCas.removeOwner(ownerClientId);
-        ownerProfile.releaseCas();
+    private void updateCasClientMappingOnRelease(@NonNull CasResource cas, int ownerClientId) {
+        cas.removeSession(ownerClientId);
+        if (!cas.hasOpenSessions(ownerClientId)) {
+            ClientProfile ownerProfile = getClientProfile(ownerClientId);
+            cas.removeOwner(ownerClientId);
+            ownerProfile.releaseCas();
+        }
     }
 
     private void updateCiCamClientMappingOnRelease(
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index b7d8cfc..e944eca 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -193,6 +193,10 @@
     @GuardedBy("mLock")
     private int mTotalStarted = 0;
 
+    /** The total number of timers that were restarted without an explicit cancel. */
+    @GuardedBy("mLock")
+    private int mTotalRestarted = 0;
+
     /** The total number of errors detected. */
     @GuardedBy("mLock")
     private int mTotalErrors = 0;
@@ -434,10 +438,10 @@
         @Override
         void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
             synchronized (mLock) {
-                if (mTimerIdMap.containsKey(arg)) {
-                    // There is an existing timer.  Cancel it.
-                    cancel(arg);
-                }
+                // If there is an existing timer, cancel it.  This is a nop if the timer does not
+                // exist.
+                if (cancel(arg)) mTotalRestarted++;
+
                 int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend);
                 if (timerId > 0) {
                     mTimerIdMap.put(arg, timerId);
@@ -546,9 +550,7 @@
         private Integer removeLocked(V arg) {
             Integer r = mTimerIdMap.remove(arg);
             if (r != null) {
-                synchronized (mTimerArgMap) {
-                    mTimerArgMap.remove(r);
-                }
+                mTimerArgMap.remove(r);
             }
             return r;
         }
@@ -672,8 +674,8 @@
         synchronized (mLock) {
             pw.format("timer: %s\n", mLabel);
             pw.increaseIndent();
-            pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
-                    mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+            pw.format("started=%d maxStarted=%d  restarted=%d running=%d expired=%d errors=%d\n",
+                    mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
                     mTotalExpired, mTotalErrors);
             pw.decreaseIndent();
             mFeature.dump(pw, false);
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 1383708..6a4c9c2 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.IpSecTransformState;
 import android.net.vcn.FeatureFlags;
 import android.net.vcn.FeatureFlagsImpl;
 import android.os.Looper;
@@ -34,7 +35,6 @@
     @NonNull private final Looper mLooper;
     @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
     @NonNull private final FeatureFlags mFeatureFlags;
-    @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
     private final boolean mIsInTestMode;
 
     public VcnContext(
@@ -49,7 +49,6 @@
 
         // Auto-generated class
         mFeatureFlags = new FeatureFlagsImpl();
-        mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
     }
 
     @NonNull
@@ -76,7 +75,16 @@
     }
 
     public boolean isFlagIpSecTransformStateEnabled() {
-        return mCoreNetFeatureFlags.ipsecTransformState();
+        // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+        // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
+        // right now. We should either update the code when the flag is accessible or remove the
+        // legacy behavior after VIC SDK finalization
+        try {
+            new IpSecTransformState.Builder();
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index b33fa6f..f82ff67 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -101,7 +101,9 @@
     }
 
     @Override
-    public void registerVibratorController(IVibratorController controller) {
+    public void registerVibratorController(@NonNull IVibratorController controller) {
+        Objects.requireNonNull(controller);
+
         synchronized (mLock) {
             mVibratorControllerHolder.setVibratorController(controller);
         }
@@ -134,6 +136,7 @@
     public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
             @NonNull IVibratorController token) {
         Objects.requireNonNull(token);
+        requireContainsNoNullElement(params);
 
         synchronized (mLock) {
             if (mVibratorControllerHolder.getVibratorController() == null) {
@@ -148,6 +151,13 @@
                         + "controller doesn't match the registered one. " + this);
                 return;
             }
+            if (params == null) {
+                // Adaptive haptics scales cannot be set to null. Ignoring request.
+                Slog.d(TAG,
+                        "New vibration params received but are null. New vibration "
+                                + "params ignored.");
+                return;
+            }
 
             updateAdaptiveHapticsScales(params);
             recordUpdateVibrationParams(params, /* fromRequest= */ false);
@@ -181,6 +191,7 @@
     public void onRequestVibrationParamsComplete(
             @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
         Objects.requireNonNull(requestToken);
+        requireContainsNoNullElement(result);
 
         synchronized (mLock) {
             if (mVibrationParamRequest == null) {
@@ -202,6 +213,13 @@
             long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
             mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
 
+            if (result == null) {
+                Slog.d(TAG,
+                        "New vibration params received but are null. New vibration "
+                                + "params ignored.");
+                return;
+            }
+
             updateAdaptiveHapticsScales(result);
             endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
             recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -401,10 +419,9 @@
      *
      * @param params the new vibration params.
      */
-    private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
-        if (params == null) {
-            return;
-        }
+    private void updateAdaptiveHapticsScales(@NonNull VibrationParam[] params) {
+        Objects.requireNonNull(params);
+
         for (VibrationParam param : params) {
             if (param.getTag() != VibrationParam.scale) {
                 Slog.e(TAG, "Unsupported vibration param: " + param);
@@ -448,11 +465,10 @@
         mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
     }
 
-    private void recordUpdateVibrationParams(@Nullable VibrationParam[] params,
+    private void recordUpdateVibrationParams(@NonNull VibrationParam[] params,
             boolean fromRequest) {
-        if (params == null) {
-            return;
-        }
+        Objects.requireNonNull(params);
+
         VibrationParamsRecords.Operation operation =
                 fromRequest ? VibrationParamsRecords.Operation.PULL
                         : VibrationParamsRecords.Operation.PUSH;
@@ -474,6 +490,13 @@
                 VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE));
     }
 
+    private void requireContainsNoNullElement(VibrationParam[] params) {
+        if (ArrayUtils.contains(params, null)) {
+            throw new IllegalArgumentException(
+                    "Invalid vibration params received: null values are not permitted.");
+        }
+    }
+
     /**
      * Keep records of {@link VibrationParam} values received by this service from a registered
      * {@link VibratorController} and provide debug information for this service.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index bf094ed..54e932a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,6 +658,8 @@
      */
     private CompatDisplayInsets mCompatDisplayInsets;
 
+    private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
+
     private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
 
     boolean pendingVoiceInteractionStart;   // Waiting for activity-invoked voice session
@@ -819,6 +821,12 @@
     @Nullable
     private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
 
+    // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+    // aspect ratio. If not null, they are used as parent container in
+    // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+    @Nullable
+    private Rect mLetterboxBoundsForAspectRatio;
+
     // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
     // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
     // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -2110,6 +2118,10 @@
         mLetterboxUiController = new LetterboxUiController(mWmService, this);
         mCameraCompatControlEnabled = mWmService.mContext.getResources()
                 .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+        mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
+        mResolveConfigHint.mUseLegacyInsetsForStableBounds =
+                mWmService.mFlags.mInsetsDecoupledConfiguration
+                        && !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
 
         mTargetSdk = info.applicationInfo.targetSdkVersion;
 
@@ -8436,10 +8448,15 @@
                     fullConfig.windowConfiguration.getRotation());
         }
 
+        final Rect letterboxedContainerBounds =
+                mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+                        ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+                        : mLetterboxBoundsForAspectRatio;
         // The role of CompatDisplayInsets is like the override bounds.
         mCompatDisplayInsets =
                 new CompatDisplayInsets(
-                        mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
+                        mDisplayContent, this, letterboxedContainerBounds,
+                        mResolveConfigHint.mUseLegacyInsetsForStableBounds);
     }
 
     private void clearSizeCompatModeAttributes() {
@@ -8511,6 +8528,7 @@
         mIsAspectRatioApplied = false;
         mIsEligibleForFixedOrientationLetterbox = false;
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+        mLetterboxBoundsForAspectRatio = null;
 
         // Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
         // different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -8546,13 +8564,14 @@
             // If the activity has requested override bounds, the configuration needs to be
             // computed accordingly.
             if (!matchParentBounds()) {
-                getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
-                        newParentConfiguration);
+                computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
             }
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
-        // are already calculated in resolveFixedOrientationConfiguration.
+        // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+        // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+        }
+        if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
                 && !mLetterboxUiController.hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
@@ -8636,16 +8655,15 @@
         if (mDisplayContent == null) {
             return;
         }
-        final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
         int rotation = newParentConfiguration.windowConfiguration.getRotation();
         if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
             rotation = mDisplayContent.getRotation();
         }
-        if (!mWmService.mFlags.mInsetsDecoupledConfiguration
-                || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+        if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds
                 || getCompatDisplayInsets() != null
-                || isFloating(parentWindowingMode) || fullBounds == null
-                || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+                || isFloating(parentWindowingMode) || parentAppBounds == null
+                || parentAppBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
             // If the insets configuration decoupled logic is not enabled for the app, or the app
             // already has a compat override, or the context doesn't contain enough info to
             // calculate the override, skip the override.
@@ -8653,15 +8671,20 @@
         }
 
         // Override starts here.
-        final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
-                rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+        final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+                : mDisplayContent.mBaseDisplayWidth;
+        final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+                : mDisplayContent.mBaseDisplayHeight;
+        final  Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
+                .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
         // This should be the only place override the configuration for ActivityRecord. Override
         // the value if not calculated yet.
         Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
         if (outAppBounds == null || outAppBounds.isEmpty()) {
-            inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+            inOutConfig.windowConfiguration.setAppBounds(parentAppBounds);
             outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(stableInsets);
+            outAppBounds.inset(nonDecorInsets);
         }
         float density = inOutConfig.densityDpi;
         if (density == Configuration.DENSITY_DPI_UNDEFINED) {
@@ -8685,11 +8708,10 @@
             // For the case of PIP transition and multi-window environment, the
             // smallestScreenWidthDp is handled already. Override only if the app is in
             // fullscreen.
-            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
             DisplayInfo info = new DisplayInfo();
             mDisplayContent.getDisplay().getDisplayInfo(info);
-            mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
-                    info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+            mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
+                    mDisplayContent.getDisplayMetrics().density,
                     inOutConfig, true /* overrideConfig */);
         }
 
@@ -8702,14 +8724,12 @@
         }
     }
 
-    /**
-     * @return The orientation to use to understand if reachability is enabled.
-     */
-    @Configuration.Orientation
-    int getOrientationForReachability() {
-        return mLetterboxUiController.hasInheritedLetterboxBehavior()
-                ? mLetterboxUiController.getInheritedOrientation()
-                : getRequestedConfigurationOrientation();
+    private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+            @NonNull Configuration parentConfig) {
+        task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
+        // Reset the temp info which should only take effect for the specified computation.
+        mResolveConfigHint.mTmpCompatInsets = null;
+        mResolveConfigHint.mTmpOverrideDisplayInfo = null;
     }
 
     /**
@@ -8852,7 +8872,7 @@
         }
 
         // Since bounds has changed, the configuration needs to be computed accordingly.
-        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+        computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
 
         // The position of configuration bounds were calculated in screen space because that is
         // easier to resolve the relative position in parent container. However, if the activity is
@@ -8946,17 +8966,18 @@
      *                     to compute the stable bounds.
      * @param outStableBounds will store the stable bounds, which are the bounds with insets
      *                        applied, if orientation is not respected when insets are applied.
-     *                        Otherwise outStableBounds will be empty. Stable bounds should be used
-     *                        to compute letterboxed bounds if orientation is not respected when
-     *                        insets are applied.
+     *                        Stable bounds should be used to compute letterboxed bounds if
+     *                        orientation is not respected when insets are applied.
+     * @param outNonDecorBounds will store the non decor bounds, which are the bounds with non
+     *                          decor insets applied, like display cutout and nav bar.
      */
-    private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+    private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds,
+            Rect outNonDecorBounds) {
         outStableBounds.setEmpty();
         if (mDisplayContent == null) {
             return true;
         }
-        if (mWmService.mFlags.mInsetsDecoupledConfiguration
-                && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+        if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds) {
             // No insets should be considered any more.
             return true;
         }
@@ -8973,8 +8994,9 @@
                 ? getFixedRotationTransformDisplayInfo()
                 : mDisplayContent.getDisplayInfo();
         final Task task = getTask();
-        task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
-                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+        task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
+                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+                mResolveConfigHint.mUseLegacyInsetsForStableBounds);
         final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
@@ -8984,9 +9006,6 @@
         // have the desired orientation.
         final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
                 || orientationWithInsets == requestedOrientation;
-        if (orientationRespectedWithInsets) {
-            outStableBounds.setEmpty();
-        }
         return orientationRespectedWithInsets;
     }
 
@@ -9012,9 +9031,10 @@
     private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
         final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
         final Rect stableBounds = new Rect();
+        final Rect outNonDecorBounds = mTmpBounds;
         // If orientation is respected when insets are applied, then stableBounds will be empty.
         boolean orientationRespectedWithInsets =
-                orientationRespectedWithInsets(parentBounds, stableBounds);
+                orientationRespectedWithInsets(parentBounds, stableBounds, outNonDecorBounds);
         if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant(
                 getOverrideOrientation())) {
             // No need to letterbox because of fixed orientation. Display will handle
@@ -9031,7 +9051,10 @@
 
         final Rect resolvedBounds =
                 getResolvedOverrideConfiguration().windowConfiguration.getBounds();
-        final int parentOrientation = newParentConfig.orientation;
+        final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
+                ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+        final int parentOrientation = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+                ? stableBoundsOrientation : newParentConfig.orientation;
 
         // If the activity requires a different orientation (either by override or activityInfo),
         // make it fit the available bounds by scaling down its bounds.
@@ -9046,7 +9069,8 @@
         }
         final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
 
-        if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
+        if (compatDisplayInsets != null
+                && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
             // App prefers to keep its original size.
             // If the size compat is from previous fixed orientation letterboxing, we may want to
             // have fixed orientation letterbox again, otherwise it will show the size compat
@@ -9054,10 +9078,12 @@
             return;
         }
 
+        final Rect parentAppBounds = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+                ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
         // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
         // bounds or stable bounds to unify aspect ratio logic.
         final Rect parentBoundsWithInsets = orientationRespectedWithInsets
-                ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
+                ? parentAppBounds : stableBounds;
         final Rect containingBounds = new Rect();
         final Rect containingBoundsWithInsets = new Rect();
         // Need to shrink the containing bounds into a square because the parent orientation
@@ -9134,8 +9160,8 @@
 
         // Calculate app bounds using fixed orientation bounds because they will be needed later
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
-        getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
-                newParentConfig, compatDisplayInsets);
+        mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+        computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
         mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
     }
 
@@ -9176,8 +9202,9 @@
         if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
             // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
             // restrict, the bounds should be the requested override bounds.
-            getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                    getFixedRotationTransformDisplayInfo());
+            mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
+            computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+            mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
         }
     }
 
@@ -9241,8 +9268,8 @@
         // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
         // are calculated in compat container space. The actual position on screen will be applied
         // later, so the calculation is simpler that doesn't need to involve offset from parent.
-        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                compatDisplayInsets);
+        mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+        computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
         // Use current screen layout as source because the size of app is independent to parent.
         resolvedConfig.screenLayout = computeScreenLayout(
                 getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
@@ -10741,10 +10768,10 @@
         /** Whether the {@link Task} windowingMode represents a floating window*/
         final boolean mIsFloating;
         /**
-         * Whether is letterboxed because of fixed orientation when the unresizable activity is
-         * first shown.
+         * Whether is letterboxed because of fixed orientation or aspect ratio when
+         * the unresizable activity is first shown.
          */
-        final boolean mIsInFixedOrientationLetterbox;
+        final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
         /**
          * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
          * is used to compute the appBounds.
@@ -10759,7 +10786,7 @@
 
         /** Constructs the environment to simulate the bounds behavior of the given container. */
         CompatDisplayInsets(DisplayContent display, ActivityRecord container,
-                @Nullable Rect fixedOrientationBounds) {
+                @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
             mOriginalRotation = display.getRotation();
             mIsFloating = container.getWindowConfiguration().tasksAreFloating();
             mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
@@ -10774,22 +10801,22 @@
                     mNonDecorInsets[rotation] = emptyRect;
                     mStableInsets[rotation] = emptyRect;
                 }
-                mIsInFixedOrientationLetterbox = false;
+                mIsInFixedOrientationOrAspectRatioLetterbox = false;
                 return;
             }
 
             final Task task = container.getTask();
 
-            mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
+            mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
 
             // Store the bounds of the Task for the non-resizable activity to use in size compat
             // mode so that the activity will not be resized regardless the windowing mode it is
             // currently in.
-            // When an activity needs to be letterboxed because of fixed orientation, use fixed
-            // orientation bounds instead of task bounds since the activity will be displayed
-            // within these even if it is in size compat mode.
-            final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
-                    ? fixedOrientationBounds
+            // When an activity needs to be letterboxed because of fixed orientation or aspect
+            // ratio, use resolved bounds instead of task bounds since the activity will be
+            // displayed within these even if it is in size compat mode.
+            final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+                    ? letterboxedContainerBounds
                     : task != null ? task.getBounds() : display.getBounds();
             final int filledContainerRotation = task != null
                     ? task.getConfiguration().windowConfiguration.getRotation()
@@ -10811,8 +10838,13 @@
                 final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
                 final DisplayPolicy.DecorInsets.Info decorInfo =
                         policy.getDecorInsetsInfo(rotation, dw, dh);
-                mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
-                mStableInsets[rotation].set(decorInfo.mConfigInsets);
+                if (useOverrideInsets) {
+                    mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
+                    mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
+                } else {
+                    mStableInsets[rotation].set(decorInfo.mConfigInsets);
+                    mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+                }
 
                 if (unfilledContainerBounds == null) {
                     continue;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5e0d4f9..84dadab 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -41,7 +41,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -986,19 +985,6 @@
                                 + " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString(
                                         attrs.getFitInsetsTypes()));
                     }
-                    if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
-                            && attrs.layoutInDisplayCutoutMode
-                                    != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
-                        // A non-translucent main window of the app enforced to go edge-to-edge
-                        // isn't allowed to fit display cutout, or it will cause software bezels.
-                        throw new IllegalArgumentException("Illegal attributes: Main window of "
-                                + win.mActivityRecord.getName() + " that isn't translucent and"
-                                + " targets SDK level " + win.mActivityRecord.mTargetSdk
-                                + " (>= 35) trying to specify layoutInDisplayCutoutMode as '"
-                                + WindowManager.LayoutParams.layoutInDisplayCutoutModeToString(
-                                        attrs.layoutInDisplayCutoutMode)
-                                + "' instead of 'always'");
-                    }
                 }
                 break;
         }
@@ -1939,6 +1925,11 @@
              */
             final Rect mOverrideConfigInsets = new Rect();
 
+            /**
+             * Override value of mNonDecorInsets for app compatibility purpose.
+             */
+            final Rect mOverrideNonDecorInsets = new Rect();
+
             /** The display frame available after excluding {@link #mNonDecorInsets}. */
             final Rect mNonDecorFrame = new Rect();
 
@@ -1954,6 +1945,11 @@
              */
             final Rect mOverrideConfigFrame = new Rect();
 
+            /**
+             * Override value of mNonDecorFrame for app compatibility purpose.
+             */
+            final Rect mOverrideNonDecorFrame = new Rect();
+
             private boolean mNeedUpdate = true;
 
             InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1973,17 +1969,26 @@
                         ? configInsets
                         : insetsState.calculateInsets(displayFrame,
                                 dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
+                final Insets overrideDecorInsets = dc.mWmService.mDecorTypes
+                        == dc.mWmService.mOverrideDecorTypes
+                        ? decor
+                        : insetsState.calculateInsets(displayFrame,
+                                dc.mWmService.mOverrideDecorTypes, true /* ignoreVisibility */);
                 mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
                 mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
                         configInsets.bottom);
                 mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
                         overrideConfigInsets.right, overrideConfigInsets.bottom);
+                mOverrideNonDecorInsets.set(overrideDecorInsets.left, overrideDecorInsets.top,
+                        overrideDecorInsets.right, overrideDecorInsets.bottom);
                 mNonDecorFrame.set(displayFrame);
                 mNonDecorFrame.inset(mNonDecorInsets);
                 mConfigFrame.set(displayFrame);
                 mConfigFrame.inset(mConfigInsets);
                 mOverrideConfigFrame.set(displayFrame);
                 mOverrideConfigFrame.inset(mOverrideConfigInsets);
+                mOverrideNonDecorFrame.set(displayFrame);
+                mOverrideNonDecorFrame.inset(mOverrideNonDecorInsets);
                 mNeedUpdate = false;
                 return insetsState;
             }
@@ -1992,9 +1997,11 @@
                 mNonDecorInsets.set(other.mNonDecorInsets);
                 mConfigInsets.set(other.mConfigInsets);
                 mOverrideConfigInsets.set(other.mOverrideConfigInsets);
+                mOverrideNonDecorInsets.set(other.mOverrideNonDecorInsets);
                 mNonDecorFrame.set(other.mNonDecorFrame);
                 mConfigFrame.set(other.mConfigFrame);
                 mOverrideConfigFrame.set(other.mOverrideConfigFrame);
+                mOverrideNonDecorFrame.set(other.mOverrideNonDecorFrame);
                 mNeedUpdate = false;
             }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 218fb7f..8042010 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2188,38 +2188,20 @@
         return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
     }
 
+    static class ConfigOverrideHint {
+        @Nullable DisplayInfo mTmpOverrideDisplayInfo;
+        @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+        boolean mUseLegacyInsetsForStableBounds;
+    }
+
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig) {
-        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                null /* compatInsets */);
-    }
-
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
-        if (overrideDisplayInfo != null) {
-            // Make sure the screen related configs can be computed by the provided display info.
-            inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
-            invalidateAppBoundsConfig(inOutConfig);
-        }
-        computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
-                null /* compatInsets */);
-    }
-
-    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig,
-            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
-        if (compatInsets != null) {
-            // Make sure the app bounds can be computed by the compat insets.
-            invalidateAppBoundsConfig(inOutConfig);
-        }
-        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
-                compatInsets);
+        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* configOverrideHint */);
     }
 
     /**
      * Forces the app bounds related configuration can be computed by
-     * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
-     * ActivityRecord.CompatDisplayInsets)}.
+     * {@link #computeConfigResourceOverrides(Configuration, Configuration, ConfigOverrideHint)}.
      */
     private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
         final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2239,8 +2221,24 @@
      * just be inherited from the parent configuration.
      **/
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
-            @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
-            @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+            @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
+        DisplayInfo overrideDisplayInfo = null;
+        ActivityRecord.CompatDisplayInsets compatInsets = null;
+        boolean useLegacyInsetsForStableBounds = false;
+        if (overrideHint != null) {
+            overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
+            compatInsets = overrideHint.mTmpCompatInsets;
+            useLegacyInsetsForStableBounds = overrideHint.mUseLegacyInsetsForStableBounds;
+            if (overrideDisplayInfo != null) {
+                // Make sure the screen related configs can be computed by the provided
+                // display info.
+                inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
+            }
+            if (overrideDisplayInfo != null || compatInsets != null) {
+                // Make sure the app bounds can be computed by the compat insets.
+                invalidateAppBoundsConfig(inOutConfig);
+            }
+        }
         int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
         if (windowingMode == WINDOWING_MODE_UNDEFINED) {
             windowingMode = parentConfig.windowConfiguration.getWindowingMode();
@@ -2309,7 +2307,8 @@
                 // area, i.e. the screen area without the system bars.
                 // The non decor inset are areas that could never be removed in Honeycomb. See
                 // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
-                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+                        useLegacyInsetsForStableBounds);
             } else {
                 // Apply the given non-decor and stable insets to calculate the corresponding bounds
                 // for screen size of configuration.
@@ -2407,9 +2406,11 @@
      * @param outNonDecorBounds where to place bounds with non-decor insets applied.
      * @param outStableBounds where to place bounds with stable insets applied.
      * @param bounds the bounds to inset.
+     * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+     *                for apps targeting U or before when calculating stable bounds.
      */
     void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
-            DisplayInfo displayInfo) {
+            DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
         outNonDecorBounds.set(bounds);
         outStableBounds.set(bounds);
         if (mDisplayContent == null) {
@@ -2420,8 +2421,13 @@
         final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
         final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
                 displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
-        intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
-        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+        if (!useLegacyInsetsForStableBounds) {
+            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+            intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
+        } else {
+            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
+            intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mOverrideNonDecorInsets);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 143605a..1496ae0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -570,6 +570,8 @@
 
     final int mOverrideConfigTypes;
 
+    final int mOverrideDecorTypes;
+
     final boolean mLimitedAlphaCompositing;
     final int mMaxUiWidth;
 
@@ -1255,10 +1257,13 @@
         if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
             // If the global new behavior is not there, but the partial decouple flag is on.
             mOverrideConfigTypes = 0;
+            mOverrideDecorTypes = 0;
         } else {
             mOverrideConfigTypes =
                     WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
                             | WindowInsets.Type.navigationBars();
+            mOverrideDecorTypes = WindowInsets.Type.displayCutout()
+                    | WindowInsets.Type.navigationBars();
         }
 
         mLetterboxConfiguration = new LetterboxConfiguration(
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index b999305f..736b051 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,12 +1,8 @@
-# Display
-per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
-
 # Input
 per-file com_android_server_input_* = file:/INPUT_OWNERS
 
 # Power
-per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
-per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
+per-file com_android_server_HardwarePropertiesManagerService.cpp = file:/services/core/java/com/android/server/power/OWNERS
 
 # BatteryStats
 per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
@@ -16,6 +12,7 @@
 per-file com_android_server_Usb* = file:/services/usb/OWNERS
 per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS
+per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS
 per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
 per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS
 per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 8ca5333..da95666 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -487,7 +487,6 @@
         timer_id_t front = headTimerId();
         auto found = running_.find(key);
         if (found != running_.end()) running_.erase(found);
-        if (front != headTimerId()) restartLocked();
     }
 
     // Remove every timer associated with the service.
@@ -501,7 +500,6 @@
                 i++;
             }
         }
-        if (front != headTimerId()) restartLocked();
     }
 
     // Return the number of timers still running.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb63757..7e083ba 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -107,6 +107,8 @@
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
+import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND;
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
@@ -886,10 +888,6 @@
             "enable_permission_based_access";
     private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
 
-    // TODO(b/266831522) remove the flag after rollout.
-    private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
-    private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
-
     private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
 
     /**
@@ -3689,26 +3687,6 @@
         mDevicePolicyEngine.handleStartUser(userId);
     }
 
-    void pushUserControlDisabledPackagesLocked(int userId) {
-        final int targetUserId;
-        final ActiveAdmin owner;
-        if (getDeviceOwnerUserIdUncheckedLocked() == userId) {
-            owner = getDeviceOwnerAdminLocked();
-            targetUserId = UserHandle.USER_ALL;
-        } else {
-            owner = getProfileOwnerAdminLocked(userId);
-            targetUserId = userId;
-        }
-
-        List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
-                ? null : owner.protectedPackages;
-        mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
-                        targetUserId, protectedPackages));
-        mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
-                targetUserId);
-    }
-
     void handleUnlockUser(int userId) {
         startOwnerService(userId, "unlock-user");
         mDevicePolicyEngine.handleUnlockUser(userId);
@@ -3957,7 +3935,7 @@
         if (policy.mPasswordOwner == oldAdminUid) {
             policy.mPasswordOwner = adminToTransfer.getUid();
         }
-
+        transferSubscriptionOwnership(outgoingReceiver, incomingReceiver);
         saveSettingsLocked(userHandle);
         sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
                 null, null);
@@ -15913,14 +15891,6 @@
         }
 
         @Override
-        public boolean isApplicationExemptionsFlagEnabled() {
-            return DeviceConfig.getBoolean(
-                    NAMESPACE_DEVICE_POLICY_MANAGER,
-                    APPLICATION_EXEMPTIONS_FLAG,
-                    DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
-        }
-
-        @Override
         public List<Bundle> getApplicationRestrictionsPerAdminForUser(
                 String packageName, @UserIdInt int userId) {
             if (UserHandle.getCallingUserId() != userId
@@ -19501,6 +19471,21 @@
                 .write();
     }
 
+    private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
+        if (Flags.esimManagementEnabled()) {
+            SubscriptionManager subscriptionManager = mContext.getSystemService(
+                    SubscriptionManager.class);
+            for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+                try {
+                    subscriptionManager.setGroupOwner(subId, target.getPackageName());
+                } catch (Exception e) {
+                    // Shouldn't happen.
+                    Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
+                }
+            }
+        }
+    }
+
     private void prepareTransfer(ComponentName admin, ComponentName target,
             PersistableBundle bundle, int callingUserId, String adminType) {
         saveTransferOwnershipBundleLocked(bundle, callingUserId);
@@ -20378,34 +20363,47 @@
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
-        final ApplicationInfo packageInfo;
-        packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+        final AppOpsManager appOpsMgr = mInjector.getAppOpsManager();
+        final ApplicationInfo appInfo = getPackageInfoWithNullCheck(packageName, caller);
+        final int uid = appInfo.uid;
 
-        for (Map.Entry<Integer, String> entry :
-                APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
-            int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
-                    entry.getValue(), packageInfo.uid, packageInfo.packageName);
-            int newMode = ArrayUtils.contains(exemptions, entry.getKey())
-                    ? MODE_ALLOWED : MODE_DEFAULT;
-            mInjector.binderWithCleanCallingIdentity(() -> {
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.forEach((exemption, appOp) -> {
+                int currentMode = appOpsMgr.unsafeCheckOpNoThrow(appOp, uid, packageName);
+                int newMode = ArrayUtils.contains(exemptions, exemption)
+                        ? MODE_ALLOWED : MODE_DEFAULT;
                 if (currentMode != newMode) {
-                    mInjector.getAppOpsManager()
-                            .setMode(entry.getValue(),
-                                    packageInfo.uid,
-                                    packageName,
-                                    newMode);
+                    appOpsMgr.setMode(appOp, uid, packageName, newMode);
+
+                    // If the user has already disabled background usage for the package, it won't
+                    // have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The
+                    // code below grants that app op, and once the exemption is in place, the user
+                    // won't be able to disable background usage anymore.
+                    if (Flags.powerExemptionBgUsageFix()
+                            && exemption == EXEMPT_FROM_POWER_RESTRICTIONS
+                            && newMode == MODE_ALLOWED) {
+                        setBgUsageAppOp(appOpsMgr, appInfo);
+                    }
                 }
             });
-        }
+        });
+
         String[] appOpExemptions = new String[exemptions.length];
         for (int i = 0; i < exemptions.length; i++) {
             appOpExemptions[i] = APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.get(exemptions[i]);
         }
         DevicePolicyEventLogger
-            .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
-            .setAdmin(caller.getPackageName())
-            .setStrings(packageName, appOpExemptions)
-            .write();
+                .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
+                .setAdmin(caller.getPackageName())
+                .setStrings(packageName, appOpExemptions)
+                .write();
+    }
+
+    static void setBgUsageAppOp(AppOpsManager appOpsMgr, ApplicationInfo appInfo) {
+        appOpsMgr.setMode(OP_RUN_ANY_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+        if (appInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            appOpsMgr.setMode(OP_RUN_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+        }
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 42ac998..d02cfee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -357,7 +357,8 @@
 
         @Override
         boolean shouldWrite() {
-            return (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
+            return Flags.alwaysPersistDo()
+                    || (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
                     || (mSystemUpdateInfo != null);
         }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e713a82..7a9fa0f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -163,8 +163,7 @@
                     new NoArgsPolicyKey(
                             DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
                     new StringSetUnion(),
-                    (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
-                            PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
+                    PolicyEnforcerCallbacks::setUserControlDisabledPackages,
                     new StringSetPolicySerializer());
 
     // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index a7adc5b..a0d9be54 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -29,6 +30,7 @@
 import android.app.admin.PackagePolicyKey;
 import android.app.admin.PolicyKey;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -37,6 +39,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -183,15 +186,31 @@
     }
 
     static boolean setUserControlDisabledPackages(
-            @Nullable Set<String> packages, int userId) {
+            @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
-            LocalServices.getService(PackageManagerInternal.class)
-                    .setOwnerProtectedPackages(
-                            userId,
-                            packages == null ? null : packages.stream().toList());
+            PackageManagerInternal pmi =
+                    LocalServices.getService(PackageManagerInternal.class);
+            pmi.setOwnerProtectedPackages(userId,
+                    packages == null ? null : packages.stream().toList());
             LocalServices.getService(UsageStatsManagerInternal.class)
-                            .setAdminProtectedPackages(
+                    .setAdminProtectedPackages(
                             packages == null ? null : new ArraySet<>(packages), userId);
+
+            if (Flags.disallowUserControlBgUsageFix()) {
+                if (packages == null) {
+                    return;
+                }
+                final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+                for (var pkg : packages) {
+                    final var appInfo = pmi.getApplicationInfo(pkg,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                            Process.myUid(), userId);
+                    if (appInfo != null) {
+                        DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo);
+                    }
+                }
+            }
         });
         return true;
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index fe7bbe0..ea08be4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -157,7 +157,6 @@
         verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
         verify(mMockedDisplayNotificationManager, times(2))
                 .onHighTemperatureExternalDisplayNotAllowed();
-        verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
     }
 
     @Test
@@ -168,7 +167,6 @@
         verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
         verify(mMockedDisplayNotificationManager, never())
                 .onHighTemperatureExternalDisplayNotAllowed();
-        verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
     }
 
     @Test
@@ -186,7 +184,6 @@
         // Expected only 1 invocation, upon critical temperature.
         verify(mMockedDisplayNotificationManager).onHighTemperatureExternalDisplayNotAllowed();
         verify(mMockedExternalDisplayStatsService).onDisplayDisabled(eq(EXTERNAL_DISPLAY_ID));
-        verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
     }
 
     @Test
@@ -194,7 +191,6 @@
         mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay,
                 /*enabled=*/ true);
         assertDisplaySetEnabled(/*enabled=*/ true);
-        verify(mMockedInjector).onExternalDisplayReadyToBeEnabled(eq(EXTERNAL_DISPLAY_ID));
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 040ae96..4591d91 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1952,7 +1952,7 @@
         SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
         votesByDisplay.put(DISPLAY_ID_2, votes);
 
-        director.getDisplayObserver().onExternalDisplayReadyToBeEnabled(DISPLAY_ID_2);
+        director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2);
         director.injectVotesByDisplay(votesByDisplay);
 
         var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID_2);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 7cfc1dd..2d317af 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -38,6 +38,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
@@ -108,7 +109,7 @@
     private Context mContext;
     private DisplayModeDirector.Injector mInjector;
     private Handler mHandler;
-    private DisplayModeDirector.DisplayObserver mObserver;
+    private DisplayManager.DisplayListener mObserver;
     private Resources mResources;
     @Mock
     private DisplayManagerFlags mDisplayManagerFlags;
@@ -162,7 +163,6 @@
                 .isEqualTo(null);
 
         // Testing that the vote is not added when display is added because feature is disabled
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(null);
@@ -196,7 +196,6 @@
         init();
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(null);
@@ -248,7 +247,6 @@
         init();
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(null);
@@ -281,7 +279,6 @@
         init();
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY);
         mObserver.onDisplayAdded(DEFAULT_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
                 .isEqualTo(expectedResolutionVote);
@@ -303,7 +300,6 @@
                 .thenReturn(MAX_HEIGHT);
         init();
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY);
         mObserver.onDisplayAdded(DEFAULT_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
@@ -323,7 +319,6 @@
                 .thenReturn(MAX_HEIGHT);
         init();
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(
@@ -343,7 +338,6 @@
         when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
         init();
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
         assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
@@ -366,7 +360,6 @@
                 .thenReturn(true);
         init();
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(
                 Vote.forPhysicalRefreshRates(
@@ -390,7 +383,6 @@
                 .thenReturn(true);
         init();
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
     }
@@ -405,7 +397,6 @@
         when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true);
         init();
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
-        mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
         mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
         assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 7d3a110..c1f4fee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -321,8 +321,7 @@
                 app1PackageName);            // packageName
         ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
 
-        mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
-                false);
+        mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service);
         list.clear();
         mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
         assertEquals(list.size(), 2);
@@ -336,7 +335,7 @@
                 app1ProcessName,                                      // processName
                 ApplicationStartInfo.START_REASON_SERVICE,            // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
-                ApplicationStartInfo.START_TYPE_WARM,                 // state type
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
                 ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
 
         // Case 5: Create an instance of app1 with a different user started for a broadcast
@@ -350,7 +349,7 @@
                 app1PackageName);                // packageName
 
         mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
-                null, true /* isColdStart */);
+                buildIntent(COMPONENT), false /* isAlarm */);
         list.clear();
         mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
         assertEquals(list.size(), 1);
@@ -395,7 +394,7 @@
                 app2PackageName);                // packageName
 
         mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
-                app, false);
+                app);
         list.clear();
         mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
         assertEquals(list.size(), 1);
@@ -409,7 +408,7 @@
                 app2ProcessName,                                      // processName
                 ApplicationStartInfo.START_REASON_CONTENT_PROVIDER,   // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
-                ApplicationStartInfo.START_TYPE_WARM,                 // state type
+                ApplicationStartInfo.START_TYPE_COLD,                 // state type
                 ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
 
         // Case 8: Save and load again
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index ce281da..5861917 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -112,6 +112,9 @@
     @Mock
     ProcessList mProcessList;
 
+    @Mock
+    AppStartInfoTracker mAppStartInfoTracker;
+
     Context mContext;
     ActivityManagerService mAms;
     BroadcastConstants mConstants;
@@ -172,6 +175,8 @@
         mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
         doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
         doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+
+        doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
     }
 
     public void tearDown() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 97ae0bd..13ba1e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2393,6 +2393,20 @@
         assertNull(mAms.getProcessRecordLocked(PACKAGE_BLUE, getUidForPackage(PACKAGE_BLUE)));
     }
 
+    @Test
+    public void testBroadcastAppStartInfoReported() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        final Intent timezone = new Intent(Intent.ACTION_TIME_TICK);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+        waitForIdle();
+
+        verify(mAppStartInfoTracker, times(1)).handleProcessBroadcastStart(anyLong(), any(), any(),
+                anyBoolean());
+    }
+
     private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
         for (int i = 0; i < r.receivers.size(); ++i) {
             if (isReceiverEquals(receiver, r.receivers.get(i))) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 4c7a8fe..9590783 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -205,8 +205,6 @@
                 new ProcessStatsService(sService, new File(sContext.getFilesDir(), "procstats")));
         setFieldValue(ActivityManagerService.class, sService, "mBackupTargets",
                 mock(SparseArray.class));
-        setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler",
-                mock(OomAdjProfiler.class));
         setFieldValue(ActivityManagerService.class, sService, "mUserController",
                 mock(UserController.class));
         setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
index f387238..12a7038 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -52,6 +52,7 @@
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
         "androidx.test.runner",
+        "compatibility-device-util-axt",
         "services.core",
     ],
     test_suites: [
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
index b36c9bd..e86108d 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -15,98 +15,144 @@
  */
 package com.android.server.selinux;
 
-import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.provider.DeviceConfig;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.regex.Matcher;
+
 @RunWith(AndroidJUnit4.class)
 public class SelinuxAuditLogsBuilderTest {
 
-    private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+    private static final String TEST_DOMAIN = "test_domain";
+
+    private SelinuxAuditLogBuilder mAuditLogBuilder;
+    private Matcher mScontextMatcher;
+    private Matcher mTcontextMatcher;
+    private Matcher mPathMatcher;
+
+    @Before
+    public void setUp() {
+        runWithShellPermissionIdentity(
+                () ->
+                        DeviceConfig.setLocalOverride(
+                                DeviceConfig.NAMESPACE_ADSERVICES,
+                                SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+                                TEST_DOMAIN));
+
+        mAuditLogBuilder = new SelinuxAuditLogBuilder();
+        mScontextMatcher = mAuditLogBuilder.mScontextMatcher;
+        mTcontextMatcher = mAuditLogBuilder.mTcontextMatcher;
+        mPathMatcher = mAuditLogBuilder.mPathMatcher;
+    }
+
+    @After
+    public void tearDown() {
+        runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
+    }
 
     @Test
     public void testMatcher_scontext() {
-        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
-        assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
-        assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+        assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isTrue();
+        assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+        assertThat(mScontextMatcher.group("scategories")).isNull();
 
-        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
-        assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
-        assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+        assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+                .isTrue();
+        assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+        assertThat(toCategories(mScontextMatcher.group("scategories")))
                 .isEqualTo(new int[] {123, 456});
 
-        assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
-        assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
-        assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+        assertThat(mScontextMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
+        assertThat(mScontextMatcher.reset("u:object_r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+        assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:p123").matches()).isFalse();
     }
 
     @Test
     public void testMatcher_tcontext() {
-        assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
-        assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
-        assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+        assertThat(mTcontextMatcher.reset("u:object_r:target_type:s0").matches()).isTrue();
+        assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type");
+        assertThat(mTcontextMatcher.group("tcategories")).isNull();
 
-        assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
-        assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
-        assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+        assertThat(mTcontextMatcher.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+        assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type2");
+        assertThat(toCategories(mTcontextMatcher.group("tcategories"))).isEqualTo(new int[] {666});
 
-        assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
-        assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+        assertThat(mTcontextMatcher.reset("u:r:target_type:s0").matches()).isFalse();
+        assertThat(mTcontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:x456").matches()).isFalse();
     }
 
     @Test
     public void testMatcher_path() {
-        assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
-        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
-        assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
-        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
-        assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
-        assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+        assertThat(mPathMatcher.reset("\"/data\"").matches()).isTrue();
+        assertThat(mPathMatcher.group("path")).isEqualTo("/data");
+        assertThat(mPathMatcher.reset("\"/data/local\"").matches()).isTrue();
+        assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
+        assertThat(mPathMatcher.reset("\"/data/local/tmp\"").matches()).isTrue();
+        assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
 
-        assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
-        assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+        assertThat(mPathMatcher.reset("\"/data/local").matches()).isFalse();
+        assertThat(mPathMatcher.reset("\"_data_local\"").matches()).isFalse();
+    }
+
+    @Test
+    public void testMatcher_scontextDefaultConfig() {
+        runWithShellPermissionIdentity(
+                () ->
+                        DeviceConfig.clearLocalOverride(
+                                DeviceConfig.NAMESPACE_ADSERVICES,
+                                SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN));
+
+        Matcher scontexMatcher = new SelinuxAuditLogBuilder().mScontextMatcher;
+
+        assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+        assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+                .isFalse();
+        assertThat(scontexMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
     }
 
     @Test
     public void testSelinuxAuditLogsBuilder_noOptionals() {
         mAuditLogBuilder.reset(
-                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 tcontext=u:object_r:t:s0"
                         + " tclass=c");
-        assertAuditLog(
-                mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+        assertAuditLog(mAuditLogBuilder.build(), true, new String[] {"p"}, TEST_DOMAIN, "t", "c");
 
         mAuditLogBuilder.reset(
                 "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
-                        + " scontext=u:r:sdk_sandbox_audit:s0");
+                        + " scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0");
         assertAuditLog(
-                mAuditLogBuilder.build(),
-                true,
-                new String[] {"p2"},
-                "sdk_sandbox_audit",
-                "t2",
-                "c2");
+                mAuditLogBuilder.build(), true, new String[] {"p2"}, TEST_DOMAIN, "t2", "c2");
     }
 
     @Test
     public void testSelinuxAuditLogsBuilder_withCategories() {
         mAuditLogBuilder.reset(
-                "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0:c123"
                         + " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
         assertAuditLog(
                 mAuditLogBuilder.build(),
                 true,
                 new String[] {"p"},
-                "sdk_sandbox_audit",
+                TEST_DOMAIN,
                 new int[] {123},
                 "t",
                 new int[] {456, 666},
@@ -118,13 +164,15 @@
     @Test
     public void testSelinuxAuditLogsBuilder_withPath() {
         mAuditLogBuilder.reset(
-                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 path=\"/very/long/path\""
                         + " tcontext=u:object_r:t:s0 tclass=c");
         assertAuditLog(
                 mAuditLogBuilder.build(),
                 true,
                 new String[] {"p"},
-                "sdk_sandbox_audit",
+                TEST_DOMAIN,
                 null,
                 "t",
                 null,
@@ -136,13 +184,15 @@
     @Test
     public void testSelinuxAuditLogsBuilder_withPermissive() {
         mAuditLogBuilder.reset(
-                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 permissive=0"
                         + " tcontext=u:object_r:t:s0 tclass=c");
         assertAuditLog(
                 mAuditLogBuilder.build(),
                 true,
                 new String[] {"p"},
-                "sdk_sandbox_audit",
+                TEST_DOMAIN,
                 null,
                 "t",
                 null,
@@ -151,13 +201,15 @@
                 false);
 
         mAuditLogBuilder.reset(
-                "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 tcontext=u:object_r:t:s0 tclass=c"
                         + " permissive=1");
         assertAuditLog(
                 mAuditLogBuilder.build(),
                 true,
                 new String[] {"p"},
-                "sdk_sandbox_audit",
+                TEST_DOMAIN,
                 null,
                 "t",
                 null,
@@ -166,6 +218,40 @@
                 true);
     }
 
+    @Test
+    public void testSelinuxAuditLogsBuilder_wrongConfig() {
+        String notARegexDomain = "not]a[regex";
+        runWithShellPermissionIdentity(
+                () ->
+                        DeviceConfig.setLocalOverride(
+                                DeviceConfig.NAMESPACE_ADSERVICES,
+                                SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+                                notARegexDomain));
+        SelinuxAuditLogBuilder noOpBuilder = new SelinuxAuditLogBuilder();
+
+        noOpBuilder.reset(
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 tcontext=u:object_r:t:s0 tclass=c");
+        assertThat(noOpBuilder.build()).isNull();
+        noOpBuilder.reset(
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0:c123 tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+        assertThat(noOpBuilder.build()).isNull();
+        noOpBuilder.reset(
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 path=\"/very/long/path\""
+                        + " tcontext=u:object_r:t:s0 tclass=c");
+        assertThat(noOpBuilder.build()).isNull();
+        noOpBuilder.reset(
+                "granted { p } scontext=u:r:"
+                        + TEST_DOMAIN
+                        + ":s0 permissive=0 tcontext=u:object_r:t:s0 tclass=c");
+        assertThat(noOpBuilder.build()).isNull();
+    }
+
     private void assertAuditLog(
             SelinuxAuditLog auditLog,
             boolean granted,
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
index 4a70ad3..b6ccf5e 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.selinux;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
@@ -27,6 +28,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 
+import android.provider.DeviceConfig;
 import android.util.EventLog;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,6 +52,7 @@
 
     // Fake tag to use for testing
     private static final int ANSWER_TAG = 42;
+    private static final String TEST_DOMAIN = "test_domain";
 
     private final MockClock mClock = new MockClock();
 
@@ -64,6 +67,14 @@
 
     @Before
     public void setUp() {
+        runWithShellPermissionIdentity(
+                () ->
+                        DeviceConfig.setLocalOverride(
+                                DeviceConfig.NAMESPACE_ADSERVICES,
+                                SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+                                TEST_DOMAIN));
+
+        mSelinuxAutidLogsCollector.setStopRequested(false);
         // move the clock forward for the limiters.
         mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
         // Ignore what was written in the event logs by previous tests.
@@ -74,13 +85,14 @@
 
     @After
     public void tearDown() {
+        runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
         mMockitoSession.finishMocking();
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs() {
-        writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+    public void testWriteAuditLogs() {
+        writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
 
@@ -91,7 +103,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 true,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -104,7 +116,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm1"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype1",
                                 null,
@@ -114,9 +126,9 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_multiplePerms() {
-        writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+    public void testWriteAuditLogs_multiplePerms() {
+        writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
 
@@ -127,7 +139,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm1", "perm2"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -140,7 +152,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm3", "perm4"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -150,11 +162,11 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_withPaths() {
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+    public void testWriteAuditLogs_withPaths() {
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "not_a_path");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
 
@@ -165,7 +177,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -178,7 +190,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -191,7 +203,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -204,7 +216,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -214,23 +226,14 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_withCategories() {
-        writeTestLog(
-                "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+    public void testWriteAuditLogs_withCategories() {
+        writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass");
         writeTestLog(
                 "denied",
                 "perm",
-                "sdk_sandbox_audit",
-                new int[] {123, 456},
-                "ttype",
-                null,
-                "tclass");
-        writeTestLog(
-                "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
-        writeTestLog(
-                "denied",
-                "perm",
-                "sdk_sandbox_audit",
+                TEST_DOMAIN,
                 new int[] {123, 456},
                 "ttype",
                 new int[] {666, 777},
@@ -245,7 +248,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 new int[] {123},
                                 "ttype",
                                 null,
@@ -258,7 +261,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 new int[] {123, 456},
                                 "ttype",
                                 null,
@@ -271,7 +274,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 new int[] {666},
@@ -284,7 +287,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 new int[] {123, 456},
                                 "ttype",
                                 new int[] {666, 777},
@@ -294,11 +297,11 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+    public void testWriteAuditLogs_withPathAndCategories() {
         writeTestLog(
                 "denied",
                 "perm",
-                "sdk_sandbox_audit",
+                TEST_DOMAIN,
                 new int[] {123},
                 "ttype",
                 new int[] {666},
@@ -314,7 +317,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 new int[] {123},
                                 "ttype",
                                 new int[] {666},
@@ -324,10 +327,10 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_permissive() {
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+    public void testWriteAuditLogs_permissive() {
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true);
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false);
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
 
@@ -338,7 +341,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -352,7 +355,7 @@
                                 FrameworkStatsLog.SELINUX_AUDIT_LOG,
                                 false,
                                 new String[] {"perm"},
-                                "sdk_sandbox_audit",
+                                TEST_DOMAIN,
                                 null,
                                 "ttype",
                                 null,
@@ -362,7 +365,7 @@
     }
 
     @Test
-    public void testNotWriteAuditLogs_notSdkSandbox() {
+    public void testNotWriteAuditLogs_notTestDomain() {
         writeTestLog("denied", "perm", "stype", "ttype", "tclass");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -385,15 +388,15 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_upToQuota() {
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+    public void testWriteAuditLogs_upToQuota() {
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         // These are not pushed.
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
 
@@ -415,14 +418,14 @@
     }
 
     @Test
-    public void testWriteSdkSandboxAuditLogs_resetQuota() {
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+    public void testWriteAuditLogs_resetQuota() {
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
         assertThat(done).isTrue();
@@ -441,11 +444,11 @@
                                 anyBoolean()),
                 times(5));
 
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         // move the clock forward to reset the quota limiter.
         mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
         done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -468,16 +471,16 @@
 
     @Test
     public void testNotWriteAuditLogs_stopRequested() {
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         // These are not pushed.
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
 
-        mSelinuxAutidLogsCollector.mStopRequested.set(true);
+        mSelinuxAutidLogsCollector.setStopRequested(true);
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
         assertThat(done).isFalse();
         verify(
@@ -495,7 +498,7 @@
                                 anyBoolean()),
                 never());
 
-        mSelinuxAutidLogsCollector.mStopRequested.set(false);
+        mSelinuxAutidLogsCollector.setStopRequested(false);
         done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
         assertThat(done).isTrue();
         verify(
@@ -516,8 +519,8 @@
 
     @Test
     public void testAuditLogs_resumeJobDoesNotExceedLimit() {
-        writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
-        mSelinuxAutidLogsCollector.mStopRequested.set(true);
+        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+        mSelinuxAutidLogsCollector.setStopRequested(true);
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
 
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
new file mode 100644
index 0000000..2aea8a0
--- /dev/null
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsJobTest {
+
+    private final JobService mJobService = mock(JobService.class);
+    private final SelinuxAuditLogsCollector mAuditLogsCollector =
+            mock(SelinuxAuditLogsCollector.class);
+    private final JobParameters mParams = createJobParameters(666);
+    private final SelinuxAuditLogsJob mAuditLogsJob = new SelinuxAuditLogsJob(mAuditLogsCollector);
+
+    @Before
+    public void setUp() {
+        mAuditLogsCollector.mStopRequested = new AtomicBoolean();
+    }
+
+    @Test
+    public void testFinishSuccessfully() {
+        when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+
+        mAuditLogsJob.start(mJobService, mParams);
+
+        verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+        assertThat(mAuditLogsJob.isRunning()).isFalse();
+    }
+
+    @Test
+    public void testInterrupt() {
+        when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+
+        mAuditLogsJob.start(mJobService, mParams);
+
+        verify(mJobService, never()).jobFinished(any(), anyBoolean());
+        assertThat(mAuditLogsJob.isRunning()).isFalse();
+    }
+
+    @Test
+    public void testInterruptAndResume() {
+        when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+        mAuditLogsJob.start(mJobService, mParams);
+        verify(mJobService, never()).jobFinished(any(), anyBoolean());
+
+        when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+        mAuditLogsJob.start(mJobService, mParams);
+        verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+        assertThat(mAuditLogsJob.isRunning()).isFalse();
+    }
+
+    @Test
+    public void testRequestStop() throws InterruptedException {
+        Semaphore isRunning = new Semaphore(0);
+        Semaphore stopRequested = new Semaphore(0);
+        AtomicReference<Throwable> uncaughtException = new AtomicReference<>();
+
+        // Set up a logs collector that runs in a worker thread until a stop is requested.
+        when(mAuditLogsCollector.collect(anyInt()))
+                .thenAnswer(
+                        invocation -> {
+                            assertThat(mAuditLogsCollector.mStopRequested.get()).isFalse();
+                            isRunning.release();
+                            stopRequested.acquire();
+                            assertThat(mAuditLogsCollector.mStopRequested.get()).isTrue();
+                            return true;
+                        });
+        Thread jobThread =
+                new Thread(
+                        () -> {
+                            mAuditLogsJob.start(mJobService, mParams);
+                        });
+        jobThread.setUncaughtExceptionHandler(
+                (thread, exception) -> uncaughtException.set(exception));
+        assertThat(mAuditLogsJob.isRunning()).isFalse();
+        jobThread.start();
+
+        // Wait until the worker thread is running.
+        isRunning.acquire();
+        assertThat(mAuditLogsJob.isRunning()).isTrue();
+
+        // Request for the worker thread to stop, and wait to verify.
+        mAuditLogsJob.requestStop();
+        stopRequested.release();
+        jobThread.join();
+        assertThat(uncaughtException.get()).isNull();
+        assertThat(mAuditLogsJob.isRunning()).isFalse();
+    }
+
+    private static JobParameters createJobParameters(int jobId) {
+        JobParameters jobParameters = mock(JobParameters.class);
+        when(jobParameters.getJobId()).thenReturn(jobId);
+        return jobParameters;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 5a17851..6cc650f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -26,6 +26,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -68,6 +69,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.LocaleList;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
@@ -110,6 +112,7 @@
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -222,6 +225,8 @@
                 mMockMagnificationConnectionManager);
         when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
                 mMockFullScreenMagnificationController);
+        when(mMockMagnificationController.isFullScreenMagnificationControllerInitialized())
+                .thenReturn(true);
         when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
         when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
                 mMockA11yController);
@@ -568,6 +573,16 @@
         verify(mMockMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
     }
 
+    @Test
+    @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+    public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
+            throws RemoteException {
+        mA11yms.setMagnificationConnection(null);
+
+        verify(mMockFullScreenMagnificationController, atLeastOnce()).reset(
+                /* displayId= */ anyInt(), /* animate= */ anyBoolean());
+    }
+
     @SmallTest
     @Test
     public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
@@ -989,6 +1004,9 @@
     @Test
     public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
             throws Exception {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
         setupShortcutTargetServices();
         String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1008,6 +1026,9 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING)
     public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         Settings.Secure.putInt(
                 mTestableContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
@@ -1035,6 +1056,9 @@
     @Test
     public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
             throws Exception {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
         enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
 
@@ -1052,6 +1076,9 @@
 
     @Test
     public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
 
         mA11yms.enableShortcutsForTargets(
@@ -1095,6 +1122,9 @@
     @Test
     public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
             throws Exception {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
         setupShortcutTargetServices();
 
@@ -1115,6 +1145,9 @@
     @Test
     public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
             throws Exception {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
 
         mA11yms.enableShortcutsForTargets(
@@ -1154,6 +1187,9 @@
     @Test
     public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
             throws Exception {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
         AccessibilityUtils.setAccessibilityServiceState(
                 mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1174,6 +1210,9 @@
 
     @Test
     public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
 
         mA11yms.enableShortcutsForTargets(
@@ -1193,6 +1232,9 @@
 
     @Test
     public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
 
         mA11yms.enableShortcutsForTargets(
@@ -1211,6 +1253,9 @@
 
     @Test
     public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
 
         mA11yms.enableShortcutsForTargets(
@@ -1230,6 +1275,9 @@
 
     @Test
     public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
 
         mA11yms.enableShortcutsForTargets(
@@ -1249,6 +1297,9 @@
 
     @Test
     public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
         setupShortcutTargetServices();
 
@@ -1268,6 +1319,9 @@
 
     @Test
     public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
 
         mA11yms.enableShortcutsForTargets(
@@ -1286,6 +1340,9 @@
 
     @Test
     public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         mockManageAccessibilityGranted(mTestableContext);
         setupShortcutTargetServices();
 
@@ -1311,6 +1368,9 @@
 
     @Test
     public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
+        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+        Assume.assumeTrue("The test is setup to run as a user 0",
+                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableQuickSettings_shortcutSet();
 
         mA11yms.enableShortcutsForTargets(
@@ -1695,4 +1755,8 @@
             return mBroadcastReceivers;
         }
     }
+
+    private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
+        return service.getCurrentUserIdLocked() == context.getUserId();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f3cd0d6..7b71f85 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
 import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -139,6 +140,8 @@
 
     private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
 
+    private boolean mMockMagnificationConnectionState;
+
     FullScreenMagnificationController mFullScreenMagnificationController;
 
     public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -175,6 +178,8 @@
 
         mScaleProvider = new MagnificationScaleProvider(mMockContext);
 
+        // Assume the connection is established by default
+        mMockMagnificationConnectionState = true;
         mFullScreenMagnificationController =
                 new FullScreenMagnificationController(
                         mMockControllerCtx,
@@ -184,7 +189,8 @@
                         () -> mMockThumbnail,
                         ConcurrentUtils.DIRECT_EXECUTOR,
                         () -> mMockScroller,
-                        () -> mMockTimeAnimator);
+                        () -> mMockTimeAnimator,
+                        () -> mMockMagnificationConnectionState);
     }
 
     @After
@@ -196,7 +202,6 @@
                 CURRENT_USER_ID);
     }
 
-
     @Test
     public void testRegister_WindowManagerAndContextRegisterListeners() {
         register(DISPLAY_0);
@@ -291,6 +296,21 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+    public void testSetScale_noConnection_doNothing() {
+        register(TEST_DISPLAY);
+
+        // Assume that the connection does not exist.
+        mMockMagnificationConnectionState = false;
+
+        final float scale = 2.0f;
+        final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        assertFalse(mFullScreenMagnificationController
+                .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+        assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
+    }
+
+    @Test
     public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
         for (int i = 0; i < DISPLAY_COUNT; i++) {
             setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 7fbd521..f482ddc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -197,6 +197,8 @@
 
     private final Scroller mMockScroller = spy(new Scroller(mContext));
 
+    private boolean mMockMagnificationConnectionState;
+
     private OffsettableClock mClock;
     private FullScreenMagnificationGestureHandler mMgh;
     private TestHandler mHandler;
@@ -229,6 +231,7 @@
         Settings.Secure.putFloatForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
                 UserHandle.USER_SYSTEM);
+        mMockMagnificationConnectionState = true;
         mFullScreenMagnificationController =
                 new FullScreenMagnificationController(
                         mockController,
@@ -238,7 +241,8 @@
                         () -> null,
                         ConcurrentUtils.DIRECT_EXECUTOR,
                         () -> mMockScroller,
-                        TimeAnimator::new) {
+                        TimeAnimator::new,
+                        () -> mMockMagnificationConnectionState) {
                     @Override
                     public boolean magnificationRegionContains(int displayId, float x, float y) {
                         return true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 58567ca..2528177 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -220,7 +220,8 @@
                                 () -> null,
                                 ConcurrentUtils.DIRECT_EXECUTOR,
                                 () -> mMockScroller,
-                                () -> mTimeAnimator));
+                                () -> mTimeAnimator,
+                                () -> true));
         mScreenMagnificationController.register(TEST_DISPLAY);
 
         mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 7c0dbf4..c6f3eb3 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -34,6 +34,7 @@
 import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
 import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
 import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.SCHEDULED_STOP_BACKGROUND_USER_MSG;
 import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
 import static com.android.server.am.UserController.USER_CURRENT_MSG;
 import static com.android.server.am.UserController.USER_START_MSG;
@@ -323,7 +324,8 @@
     @Test
     public void testStartUserUIDisabled() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         verify(mInjector, never()).showUserSwitchingDialog(
@@ -393,7 +395,8 @@
     @Test
     public void testFailedStartUserInForeground() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         mUserController.startUserInForeground(NONEXIST_USER_ID);
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
@@ -470,7 +473,8 @@
     @Test
     public void testContinueUserSwitch() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -483,7 +487,7 @@
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, times(0)).dismissKeyguard(any());
         verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
-        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
         verifySystemUserVisibilityChangesNeverNotified();
     }
 
@@ -491,7 +495,8 @@
     public void testContinueUserSwitchDismissKeyguard() {
         when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -504,14 +509,15 @@
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, times(1)).dismissKeyguard(any());
         verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
-        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
         verifySystemUserVisibilityChangesNeverNotified();
     }
 
     @Test
     public void testContinueUserSwitchUIDisabled() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
@@ -524,11 +530,11 @@
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector, never()).dismissUserSwitchingDialog(any());
-        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+        continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
     }
 
     private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId,
-            boolean backgroundUserStopping) {
+            boolean backgroundUserStopping, boolean expectScheduleBackgroundUserStopping) {
         Set<Integer> expectedCodes = new LinkedHashSet<>();
         expectedCodes.add(COMPLETE_USER_SWITCH_MSG);
         expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -536,6 +542,9 @@
             expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
             expectedCodes.add(0); // this is for directly posting in stopping.
         }
+        if (expectScheduleBackgroundUserStopping) {
+            expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
+        }
         Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
         assertEquals("Unexpected message sent", expectedCodes, actualCodes);
         Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -571,6 +580,112 @@
                 ).collect(Collectors.toList()), Collections.emptySet());
     }
 
+    /** Test scheduling stopping of background users after a user-switch. */
+    @Test
+    public void testScheduleStopOfBackgroundUser_switch() {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+        setUpUser(TEST_USER_ID1, NO_USERINFO_FLAGS);
+
+        // Switch to TEST_USER_ID from user 0
+        int numberOfUserSwitches = 0;
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+                ++numberOfUserSwitches,
+                /* expectOldUserStopping= */false,
+                /* expectScheduleBackgroundUserStopping= */ false);
+        assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+                mUserController.getRunningUsersLU());
+
+        // Allow the post-switch processing to complete (there should be no scheduled stopping).
+        assertAndProcessScheduledStopBackgroundUser(false, null);
+        assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+                mUserController.getRunningUsersLU());
+
+        // Switch to TEST_USER_ID1 from TEST_USER_ID
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
+                ++numberOfUserSwitches,
+                /* expectOldUserStopping= */false,
+                /* expectScheduleBackgroundUserStopping= */ true);
+        assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1),
+                mUserController.getRunningUsersLU());
+
+        // Switch back to TEST_USER_ID from TEST_USER_ID1
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID, TEST_USER_ID1,
+                ++numberOfUserSwitches,
+                /* expectOldUserStopping= */false,
+                /* expectScheduleBackgroundUserStopping= */ true);
+        assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID),
+                mUserController.getRunningUsersLU());
+
+        // Allow the post-switch processing to complete.
+        assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID);
+        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+        assertAndProcessScheduledStopBackgroundUser(false, null);
+        assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+                mUserController.getRunningUsersLU());
+    }
+
+    /** Test scheduling stopping of background users that were started in the background. */
+    @Test
+    public void testScheduleStopOfBackgroundUser_startInBackground() throws Exception {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+        // Start two full background users (which should both get scheduled for stopping)
+        // and one profile (which should not).
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+
+        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+
+        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+
+        assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2);
+        assertAndProcessScheduledStopBackgroundUser(false, null);
+
+        // Now that we've processed the stops, let's make sure that a subsequent one will work too.
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2, TEST_USER_ID3),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID3);
+        assertAndProcessScheduledStopBackgroundUser(false, null);
+        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+                new HashSet<>(mUserController.getRunningUsersLU()));
+    }
+
+    /**
+     * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
+     * @param userId the user we are checking to see whether it is scheduled.
+     *               Can be null, when expectScheduled is false, to indicate no user should be
+     *               scheduled.
+     */
+    private void assertAndProcessScheduledStopBackgroundUser(
+            boolean expectScheduled, @Nullable Integer userId) {
+        TestHandler handler = mInjector.mHandler;
+        if (expectScheduled) {
+            assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+            handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId);
+            mUserController.processScheduledStopOfBackgroundUser(userId);
+        } else {
+            assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+        }
+    }
+
     @Test
     public void testExplicitSystemUserStartInBackground() {
         setUpUser(UserHandle.USER_SYSTEM, 0);
@@ -587,13 +702,14 @@
     public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
             throws InterruptedException, RemoteException {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
         int numberOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         // running: user 0, USER_ID
         assertTrue(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -601,7 +717,7 @@
 
         numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         // running: user 0, USER_ID, USER_ID1
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
@@ -609,7 +725,7 @@
 
         numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
         // skip middle step and call this directly.
         mUserController.finishUserSwitch(ussUser2);
@@ -631,13 +747,14 @@
     public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
             throws Exception {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
         int numberOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         // running: user 0, USER_ID
         assertTrue(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -645,7 +762,7 @@
         numberOfUserSwitches++;
 
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
-                numberOfUserSwitches, true);
+                numberOfUserSwitches, true, false);
         // running: user 0, USER_ID1
         // stopped + unlocked: USER_ID
         numberOfUserSwitches++;
@@ -663,7 +780,7 @@
                 .lockCeStorage(anyInt());
 
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
-                numberOfUserSwitches, true);
+                numberOfUserSwitches, true, false);
         // running: user 0, USER_ID2
         // stopped + unlocked: USER_ID1
         // stopped + locked: USER_ID
@@ -686,7 +803,8 @@
     public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped()
             throws Exception {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         final int PARENT_ID = 200;
         final int PROFILE1_ID = 201;
@@ -707,7 +825,7 @@
 
         int numberOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
         waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
         assertTrue(mUserController.canStartMoreUsers());
@@ -722,7 +840,7 @@
 
         numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID));
         waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
         assertTrue(mUserController.canStartMoreUsers());
@@ -747,7 +865,7 @@
 
         numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
         waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
         // We've now done a user switch and should notice that we've exceeded the maximum number of
@@ -766,7 +884,8 @@
     @Test
     public void testRunningUsersListOrder_parentAfterProfile() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         final int PARENT_ID = 200;
         final int PROFILE1_ID = 201;
@@ -787,7 +906,7 @@
 
         int numberOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         assertEquals(Arrays.asList(
                 new Integer[] {SYSTEM_USER_ID, PARENT_ID}),
                 mUserController.getRunningUsersLU());
@@ -799,7 +918,7 @@
 
         numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
-                numberOfUserSwitches, false);
+                numberOfUserSwitches, false, false);
         assertEquals(Arrays.asList(
                 new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}),
                 mUserController.getRunningUsersLU());
@@ -827,7 +946,8 @@
     @Test
     public void testRunningUsersListOrder_currentAtEnd() {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         final int CURRENT_ID = 200;
         final int PROFILE_ID = 201;
@@ -842,7 +962,7 @@
                 new Integer[] {SYSTEM_USER_ID}),
                 mUserController.getRunningUsersLU());
 
-        addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false);
+        addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false, false);
         assertEquals(Arrays.asList(
                 new Integer[] {SYSTEM_USER_ID, CURRENT_ID}),
                 mUserController.getRunningUsersLU());
@@ -864,7 +984,8 @@
     @Test
     public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         setUpAndStartUserInBackground(TEST_USER_ID);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
@@ -922,7 +1043,8 @@
     @Test
     public void testUserLockingForDelayedLockingMode() throws Exception {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         // allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
         setUpAndStartUserInBackground(TEST_USER_ID);
@@ -973,7 +1095,8 @@
     @Test
     public void testStopProfile_doesNotStopItsParent() throws Exception {
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         final Range<Integer> RUNNING_RANGE =
                 Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED);
@@ -1053,7 +1176,8 @@
     @Test
     public void testStopPrivateProfile() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
                 android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1071,7 +1195,8 @@
     @Test
     public void testStopPrivateProfileWithDelayedLocking() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
                 android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1083,7 +1208,8 @@
     @Test
     public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.disableFlags(
@@ -1113,7 +1239,8 @@
     public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
             throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
-                /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
                 android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1130,7 +1257,8 @@
     @Test
     public void testStopManagedProfileWithDelayedLocking() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
                 android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1285,7 +1413,8 @@
     public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception {
         // enable user switch ui, because keyguard is only shown then
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
-                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+                /* backgroundUserScheduledStopTimeSecs= */ -1);
 
         // mock the device to be secure in order to expect the keyguard to be shown
         when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
@@ -1365,7 +1494,8 @@
     }
 
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
-            int expectedNumberOfCalls, boolean expectOldUserStopping) {
+            int expectedNumberOfCalls, boolean expectOldUserStopping,
+            boolean expectScheduleBackgroundUserStopping) {
         // Start user -- this will update state of mUserController
         mUserController.startUser(newUserId, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -1378,8 +1508,12 @@
         mInjector.mHandler.clearAllRecordedMessages();
         // Verify that continueUserSwitch worked as expected
         continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
+        assertEquals(mInjector.mHandler
+                        .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId),
+                expectScheduleBackgroundUserStopping);
         verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
-        continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
+        continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping,
+                expectScheduleBackgroundUserStopping);
     }
 
     private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3f5217c..8ca8623 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -39,6 +39,7 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManagerInternal;
 import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -59,6 +60,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
@@ -177,6 +180,24 @@
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testOnRequestVibrationParamsComplete_withNullVibrationParams_throwsException() {
+        mVibratorControlService.registerVibratorController(mFakeVibratorController);
+        int timeoutInMillis = 10;
+        CompletableFuture<Void> unusedFuture =
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+                        timeoutInMillis);
+        IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+        List<VibrationParam> vibrationParamList = Arrays.asList(
+                VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+                null,
+                VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+        mVibratorControlService.onRequestVibrationParamsComplete(token,
+                vibrationParamList.toArray(new VibrationParam[0]));
+    }
+
     @Test
     public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
@@ -214,6 +235,19 @@
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetVibrationParams_withNullVibrationParams_throwsException() {
+        mVibratorControlService.registerVibratorController(mFakeVibratorController);
+        List<VibrationParam> vibrationParamList = Arrays.asList(
+                VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+                null,
+                VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+        mVibratorControlService.setVibrationParams(
+                vibrationParamList.toArray(new VibrationParam[0]),
+                mFakeVibratorController);
+    }
+
     @Test
     public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() {
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
index a606388..c17d11e 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
@@ -42,7 +42,10 @@
         return vibrationParamList.toArray(new VibrationParam[0]);
     }
 
-    private static VibrationParam generateVibrationParam(int type, float scale) {
+    /**
+     * Generates a {@link VibrationParam} with the specified type and scale.
+     */
+    public static VibrationParam generateVibrationParam(int type, float scale) {
         ScaleParam scaleParam = new ScaleParam();
         scaleParam.typesMask = type;
         scaleParam.scale = scale;
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 2e80bc7..03302ce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -130,7 +130,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -1943,8 +1942,7 @@
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
-
-	final int scale = dh / dw;
+        final int scale = dh / dw;
 
         // App bounds should be dh / scale x dw / scale
         assertEquals(dw, rotatedDisplayBounds.width());
@@ -4179,13 +4177,8 @@
     }
 
     @Test
-    @Ignore // TODO(b/330888878): fix test in main
-    public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
-        if (Flags.insetsDecoupledConfiguration()) {
-            // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
-            //  bounds no longer contains display cutout.
-            return;
-        }
+    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+    public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() {
         // Set up portrait close to square display
         setUpDisplaySizeWithApp(2200, 2280);
         final DisplayContent display = mActivity.mDisplayContent;
@@ -4198,16 +4191,58 @@
                         .setInsetsSize(Insets.of(0, 0, 0, 150))
         };
         display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
-        assertTrue(navbar.providesDisplayDecorInsets()
-                && display.getDisplayPolicy().updateDecorInsetsInfo());
+        assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo());
         display.sendNewConfiguration();
 
-        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
 
-        // Activity is fullscreen even though orientation is not respected with insets, because
-        // the display still matches or is less than the activity aspect ratio
-        assertEquals(display.getBounds(), mActivity.getBounds());
-        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        final Rect bounds = activity.getBounds();
+        // Activity should be letterboxed and should have portrait app bounds
+        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertTrue(bounds.height() > bounds.width());
+    }
+
+    @Test
+    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+    public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
+        setUpDisplaySizeWithApp(2200, 2280);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        final DisplayContent dc = mActivity.mDisplayContent;
+        // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+        final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+                "navbar");
+        final Binder owner = new Binder();
+        navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+                        .setInsetsSize(Insets.of(0, 0, 0, 150))
+        };
+        dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+        assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
+        dc.sendNewConfiguration();
+
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+        prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+                SCREEN_ORIENTATION_LANDSCAPE);
+        // To force config to update again but with the same landscape orientation.
+        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+
+        assertTrue(activity.shouldCreateCompatDisplayInsets());
+        assertNotNull(activity.getCompatDisplayInsets());
+        // Activity is not letterboxed for fixed orientation because orientation is respected
+        // with insets, and should not be in size compat mode
+        assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(activity.inSizeCompatMode());
     }
 
     @Test
@@ -4229,6 +4264,7 @@
         // can be aligned inside parentAppBounds
         assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200));
     }
+
     @Test
     public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
         if (Flags.insetsDecoupledConfiguration()) {
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 1ca808f..225e85e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -833,8 +833,11 @@
         final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
         final ActivityRecord.CompatDisplayInsets compatInsets =
                 new ActivityRecord.CompatDisplayInsets(
-                        display, activity, /* fixedOrientationBounds= */ null);
-        task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
+                        display, activity, /* letterboxedContainerBounds */ null,
+                        /* useOverrideInsets */ false);
+        final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint();
+        overrideHint.mTmpCompatInsets = compatInsets;
+        task.computeConfigResourceOverrides(inOutConfig, parentConfig, overrideHint);
 
         assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
         final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ba7ba532..8fe45cb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4614,6 +4614,31 @@
     }
 
     /**
+     * Set owner for this subscription.
+     *
+     * @param subscriptionId the subId of the subscription.
+     * @param groupOwner The group owner to assign to the subscription
+     *
+     * @throws SecurityException if caller is not authorized.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setGroupOwner(int subscriptionId, @NonNull String groupOwner) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                iSub.setGroupOwner(subscriptionId, groupOwner);
+            } else {
+                throw new IllegalStateException("[setGroupOwner]: "
+                        + "subscription service unavailable");
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Set userHandle for a subscription.
      *
      * Used to set an association between a subscription and a user on the device so that voice
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 6678f40..1bfec29 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -309,6 +309,18 @@
      */
     int setUsageSetting(int usageSetting, int subId, String callingPackage);
 
+    /**
+      * Set owner for this subscription.
+      *
+      * @param subId the unique SubscriptionInfo index in database
+      * @param groupOwner The group owner to assign to the subscription
+      *
+      * @throws SecurityException if caller is not authorized.
+      *
+      * @hide
+      */
+     void setGroupOwner(int subId, String groupOwner);
+
      /**
       * Set userHandle for this subscription.
       *
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 1fdf97a..093923f 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
+import android.util.LongArrayQueue;
 import android.util.Xml;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index b23a87e..fa352cf 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,6 +18,7 @@
     <!-- Base application theme. -->
     <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
         <!-- Customize your theme here. -->
+        <item name="android:windowLayoutInDisplayCutoutMode">default</item>
     </style>
 
 </resources>
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6189fb0..edad678 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -107,7 +107,6 @@
     @Mock protected Context mContext;
     @Mock protected Network mNetwork;
     @Mock protected FeatureFlags mFeatureFlags;
-    @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
     @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     @Mock protected ConnectivityManager mConnectivityManager;
     @Mock protected TelephonyManager mTelephonyManager;
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 45ab986..2ba5705 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -993,6 +993,16 @@
      * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
      * or {@link #setupInterfaceForSoftApMode(String)}.
      *
+     * <p>
+     * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the
+     * returned scan results should include separate scan result for each BSSID within the
+     * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs.
+     * Original Multi-BSSID Element will be included in the Information Elements attached to
+     * each of the scan results.
+     * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an
+     * optional requirement for devices running with older WiFi generations.
+     * </p>
+     *
      * @param ifaceName Name of the interface.
      * @param scanType The type of scan result to be returned, can be
      * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.