Merge "Fix issue where location provider overlay may not bind" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index aec464d..e96d07f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -55,6 +55,7 @@
 import android.util.SparseArrayMap;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
+import android.util.SparseSetArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
@@ -158,19 +159,6 @@
     @GuardedBy("mLock")
     private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray();
 
-    private DeviceIdleInternal mDeviceIdleInternal;
-    private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
-                    mHandler.post(FlexibilityController.this::updatePowerAllowlistCache);
-                    break;
-            }
-        }
-    };
     @VisibleForTesting
     @GuardedBy("mLock")
     final FlexibilityTracker mFlexibilityTracker;
@@ -182,6 +170,7 @@
     private final FcHandler mHandler;
     @VisibleForTesting
     final PrefetchController mPrefetchController;
+    private final SpecialAppTracker mSpecialAppTracker;
 
     /**
      * Stores the beginning of prefetch jobs lifecycle per app as a maximum of
@@ -355,16 +344,16 @@
         mPercentsToDropConstraints =
                 FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
         mPrefetchController = prefetchController;
+        mSpecialAppTracker = new SpecialAppTracker();
 
         if (mFlexibilityEnabled) {
-            registerBroadcastReceiver();
+            mSpecialAppTracker.startTracking();
         }
     }
 
     @Override
     public void onSystemServicesReady() {
-        mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
-        mHandler.post(FlexibilityController.this::updatePowerAllowlistCache);
+        mSpecialAppTracker.onSystemServicesReady();
     }
 
     @Override
@@ -453,6 +442,7 @@
         final int userId = UserHandle.getUserId(uid);
         mPrefetchLifeCycleStart.delete(userId, packageName);
         mJobScoreTrackers.delete(uid, packageName);
+        mSpecialAppTracker.onAppRemoved(userId, packageName);
         for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
             final JobStatus js = mJobsToCheck.valueAt(i);
             if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName))
@@ -466,6 +456,7 @@
     @GuardedBy("mLock")
     public void onUserRemovedLocked(int userId) {
         mPrefetchLifeCycleStart.delete(userId);
+        mSpecialAppTracker.onUserRemoved(userId);
         for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) {
             final int uid = mJobScoreTrackers.keyAt(u);
             if (UserHandle.getUserId(uid) == userId) {
@@ -496,9 +487,10 @@
                 // Only exclude DEFAULT+ priority jobs for BFGS+ apps
                 || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
                         && js.getEffectivePriority() >= PRIORITY_DEFAULT)
-                // For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs.
+                // For special/privileged apps, automatically exclude DEFAULT+ priority jobs.
                 || (js.getEffectivePriority() >= PRIORITY_DEFAULT
-                        && mPowerAllowlistedApps.contains(js.getSourcePackageName()))
+                        && mSpecialAppTracker.isSpecialApp(
+                                js.getSourceUserId(), js.getSourcePackageName()))
                 || hasEnoughSatisfiedConstraintsLocked(js)
                 || mService.isCurrentlyRunningLocked(js);
     }
@@ -827,39 +819,6 @@
         mFcConfig.processConstantLocked(properties, key);
     }
 
-    private void registerBroadcastReceiver() {
-        IntentFilter filter = new IntentFilter(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
-    }
-
-    private void unregisterBroadcastReceiver() {
-        mContext.unregisterReceiver(mBroadcastReceiver);
-    }
-
-    private void updatePowerAllowlistCache() {
-        if (mDeviceIdleInternal == null) {
-            return;
-        }
-
-        // Don't call out to DeviceIdleController with the lock held.
-        final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle();
-        final ArraySet<String> changedPkgs = new ArraySet<>();
-        synchronized (mLock) {
-            changedPkgs.addAll(mPowerAllowlistedApps);
-            mPowerAllowlistedApps.clear();
-            for (final String pkgName : allowlistedPkgs) {
-                mPowerAllowlistedApps.add(pkgName);
-                if (changedPkgs.contains(pkgName)) {
-                    changedPkgs.remove(pkgName);
-                } else {
-                    changedPkgs.add(pkgName);
-                }
-            }
-            mPackagesToCheck.addAll(changedPkgs);
-            mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES);
-        }
-    }
-
     @VisibleForTesting
     class FlexibilityTracker {
         final ArrayList<ArraySet<JobStatus>> mTrackedJobs;
@@ -1343,12 +1302,12 @@
                             mFlexibilityEnabled = true;
                             mPrefetchController
                                     .registerPrefetchChangedListener(mPrefetchChangedListener);
-                            registerBroadcastReceiver();
+                            mSpecialAppTracker.startTracking();
                         } else {
                             mFlexibilityEnabled = false;
                             mPrefetchController
                                     .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
-                            unregisterBroadcastReceiver();
+                            mSpecialAppTracker.stopTracking();
                         }
                     }
                     break;
@@ -1653,6 +1612,176 @@
         return mFcConfig;
     }
 
+    private class SpecialAppTracker {
+        /**
+         * Lock for objects inside this class. This should never be held when attempting to acquire
+         * {@link #mLock}. It is fine to acquire this if already holding {@link #mLock}.
+         */
+        private final Object mSatLock = new Object();
+
+        private DeviceIdleInternal mDeviceIdleInternal;
+
+        /** Set of all apps that have been deemed special, keyed by user ID. */
+        private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>();
+        @GuardedBy("mSatLock")
+        private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
+
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                switch (intent.getAction()) {
+                    case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
+                        mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
+                        break;
+                }
+            }
+        };
+
+        public boolean isSpecialApp(final int userId, @NonNull String packageName) {
+            synchronized (mSatLock) {
+                if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) {
+                    return true;
+                }
+                if (mSpecialApps.contains(userId, packageName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean isSpecialAppInternal(final int userId, @NonNull String packageName) {
+            synchronized (mSatLock) {
+                if (mPowerAllowlistedApps.contains(packageName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private void onAppRemoved(final int userId, String packageName) {
+            synchronized (mSatLock) {
+                // Don't touch the USER_ALL set here. If the app is completely removed from the
+                // device, any list that affects USER_ALL should update and this would eventually
+                // be updated with those lists no longer containing the app.
+                mSpecialApps.remove(userId, packageName);
+            }
+        }
+
+        private void onSystemServicesReady() {
+            mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+
+            synchronized (mLock) {
+                if (mFlexibilityEnabled) {
+                    mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
+                }
+            }
+        }
+
+        private void onUserRemoved(final int userId) {
+            synchronized (mSatLock) {
+                mSpecialApps.remove(userId);
+            }
+        }
+
+        private void startTracking() {
+            IntentFilter filter = new IntentFilter(
+                    PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+            mContext.registerReceiver(mBroadcastReceiver, filter);
+
+            updatePowerAllowlistCache();
+        }
+
+        private void stopTracking() {
+            mContext.unregisterReceiver(mBroadcastReceiver);
+
+            synchronized (mSatLock) {
+                mPowerAllowlistedApps.clear();
+                mSpecialApps.clear();
+            }
+        }
+
+        /**
+         * Update the processed special app set for the specified user ID, only looking at the
+         * specified set of apps. This method must <b>NEVER</b> be called while holding
+         * {@link #mSatLock}.
+         */
+        private void updateSpecialAppSetUnlocked(final int userId, @NonNull ArraySet<String> pkgs) {
+            // This method may need to acquire mLock, so ensure that mSatLock isn't held to avoid
+            // lock inversion.
+            if (Thread.holdsLock(mSatLock)) {
+                throw new IllegalStateException("Must never hold local mSatLock");
+            }
+            if (pkgs.size() == 0) {
+                return;
+            }
+            final ArraySet<String> changedPkgs = new ArraySet<>();
+
+            synchronized (mSatLock) {
+                for (int i = pkgs.size() - 1; i >= 0; --i) {
+                    final String pkgName = pkgs.valueAt(i);
+                    if (isSpecialAppInternal(userId, pkgName)) {
+                        if (mSpecialApps.add(userId, pkgName)) {
+                            changedPkgs.add(pkgName);
+                        }
+                    } else if (mSpecialApps.remove(userId, pkgName)) {
+                        changedPkgs.add(pkgName);
+                    }
+                }
+            }
+
+            if (changedPkgs.size() > 0) {
+                synchronized (mLock) {
+                    mPackagesToCheck.addAll(changedPkgs);
+                    mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES);
+                }
+            }
+        }
+
+        private void updatePowerAllowlistCache() {
+            if (mDeviceIdleInternal == null) {
+                return;
+            }
+
+            // Don't call out to DeviceIdleController with the lock held.
+            final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle();
+            final ArraySet<String> changedPkgs = new ArraySet<>();
+            synchronized (mSatLock) {
+                changedPkgs.addAll(mPowerAllowlistedApps);
+                mPowerAllowlistedApps.clear();
+                for (String pkgName : allowlistedPkgs) {
+                    mPowerAllowlistedApps.add(pkgName);
+                    if (!changedPkgs.remove(pkgName)) {
+                        // The package wasn't in the previous set of allowlisted apps. Add it
+                        // since its state has changed.
+                        changedPkgs.add(pkgName);
+                    }
+                }
+            }
+
+            // The full allowlist is currently user-agnostic, so use USER_ALL for these packages.
+            updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+        }
+
+        public void dump(@NonNull IndentingPrintWriter pw) {
+            pw.println("Special apps:");
+            pw.increaseIndent();
+
+            synchronized (mSatLock) {
+                for (int u = 0; u < mSpecialApps.size(); ++u) {
+                    pw.print(mSpecialApps.keyAt(u));
+                    pw.print(": ");
+                    pw.println(mSpecialApps.valuesAt(u));
+                }
+
+                pw.println();
+                pw.print("Power allowlisted packages: ");
+                pw.println(mPowerAllowlistedApps);
+            }
+
+            pw.decreaseIndent();
+        }
+    }
+
     @Override
     @GuardedBy("mLock")
     public void dumpConstants(IndentingPrintWriter pw) {
@@ -1690,8 +1819,7 @@
         pw.decreaseIndent();
 
         pw.println();
-        pw.print("Power allowlisted packages: ");
-        pw.println(mPowerAllowlistedApps);
+        mSpecialAppTracker.dump(pw);
 
         pw.println();
         mFlexibilityTracker.dump(pw, predicate, nowElapsed);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 062bdaa..d9b5d56 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10421,8 +10421,7 @@
 
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
-    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilterToAutoTransact(@NonNull String);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String, boolean);
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean defaultToObserveMode();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 73914a2..4ef0a1b 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -67,6 +67,8 @@
         mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
         mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT,
                 android.content.IntentSender.class);
+        String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        final UserManager userManager = UserManager.get(this);
 
         if (mUserId == UserHandle.USER_NULL) {
             Log.wtf(TAG, "Invalid user id: " + mUserId + ". Stopping.");
@@ -74,13 +76,20 @@
             return;
         }
 
+        if (android.os.Flags.allowPrivateProfile()
+                && !userManager.isManagedProfile(mUserId)) {
+            Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName
+                    + " called for a non-managed-profile " + mUserId);
+            finish();
+            return;
+        }
+
         if (mReason != UNLAUNCHABLE_REASON_QUIET_MODE) {
             Log.wtf(TAG, "Invalid unlaunchable type: " + mReason);
             finish();
             return;
         }
 
-        String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
         boolean showEmergencyCallButton =
                 (targetPackageName != null && targetPackageName.equals(
                         mTelecomManager.getDefaultDialerPackage(UserHandle.of(mUserId))));
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 7b53ca6..0fb7c95 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -204,7 +204,7 @@
     method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
-    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method public boolean removeAidsForService(android.content.ComponentName, String);
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 65d0625..64f7fa4 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -32,7 +32,7 @@
     boolean setDefaultForNextTap(int userHandle, in ComponentName service);
     boolean setDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable);
     boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
-    boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
+    boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter, boolean autoTransact);
     boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
     boolean unsetOffHostForService(int userHandle, in ComponentName service);
     AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index c81b95b..e62e37b 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -681,34 +681,18 @@
 
     /**
      * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
-     * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this or
-     * {@link  ApduServiceInfo#addPollingLoopFilterToAutoTransact(String)} multiple times will
-     * cause the value to be overwritten each time.
+     * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this
+     * multiple times will cause the value to be overwritten each time.
      * @param pollingLoopFilter the polling loop filter to add, must be a  valide hexadecimal string
      */
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
-        mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), false);
+    public void addPollingLoopFilter(@NonNull String pollingLoopFilter,
+            boolean autoTransact) {
+        mAutoTransact.put(pollingLoopFilter, autoTransact);
 
     }
 
     /**
-     * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will cause the
-     * device to exit observe mode, just as if
-     * {@link android.nfc.NfcAdapter#setObserveModeEnabled(boolean)} had been called with true,
-     * allowing transactions to proceed. The matching frame will also be delivered to
-     * {@link HostApduService#processPollingFrames(List)}. Adding a key with this or
-     * {@link  ApduServiceInfo#addPollingLoopFilter(String)} multiple times will
-     * cause the value to be overwritten each time.
-     *
-     * @param pollingLoopFilter the polling loop filter to add, must be a  valide hexadecimal string
-     */
-    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
-        mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
-    }
-
-    /**
      * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
      * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
      * @param pollingLoopFilter this polling loop filter to add.
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index e681a85..47ddd9d 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -42,6 +42,7 @@
 import android.util.Log;
 
 import java.util.HashMap;
+import java.util.HexFormat;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -59,7 +60,6 @@
  */
 public final class CardEmulation {
     private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
-    private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}");
 
     static final String TAG = "CardEmulation";
 
@@ -360,21 +360,28 @@
     }
 
     /**
-     * Register a polling loop filter (PLF) for a HostApduService. The PLF can be sequence of an
-     * even number of hexadecimal numbers (0-9, A-F or a-f). When non-standard polling loop frame
-     * matches this sequence exactly, it may be delivered to
-     * {@link HostApduService#processPollingFrames(List)}  if this service is currently
-     * preferred or there are no other services registered for this filter.
+     * Register a polling loop filter (PLF) for a HostApduService and indicate whether it should
+     * auto-transact or not.  The PLF can be sequence of an
+     * even number of at least 2 hexadecimal numbers (0-9, A-F or a-f), representing a series of
+     * bytes. When non-standard polling loop frame matches this sequence exactly, it may be
+     * delivered to {@link HostApduService#processPollingFrames(List)}. If auto-transact is set to
+     * true, then observe mode will also be disabled.  if this service is currently preferred or
+     * there are no other services registered for this filter.
      * @param service The HostApduService to register the filter for
      * @param pollingLoopFilter The filter to register
+     * @param autoTransact true to have the NFC stack automatically disable observe mode and allow
+     *         transactions to proceed when this filter matches, false otherwise
      * @return true if the filter was registered, false otherwise
+     * @throws IllegalArgumentException if the passed in string doesn't parse to at least one byte
      */
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
     public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
-            @NonNull String pollingLoopFilter) {
+            @NonNull String pollingLoopFilter, boolean autoTransact) {
+        pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter);
+
         try {
             return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
-                    service, pollingLoopFilter);
+                    service, pollingLoopFilter, autoTransact);
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
@@ -384,7 +391,8 @@
             }
             try {
                 return sService.registerPollingLoopFilterForService(
-                        mContext.getUser().getIdentifier(), service, pollingLoopFilter);
+                        mContext.getUser().getIdentifier(), service,
+                        pollingLoopFilter, autoTransact);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to reach CardEmulationService.");
                 return false;
@@ -979,15 +987,14 @@
      * @hide
      */
     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) {
+    public static @NonNull String validatePollingLoopFilter(@NonNull String pollingLoopFilter) {
         // Verify hex characters
-        if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) {
-            Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter
-                    + " is not a valid Polling Loop Filter.");
-            return false;
+        byte[] plfBytes = HexFormat.of().parseHex(pollingLoopFilter);
+        if (plfBytes.length == 0) {
+            throw new IllegalArgumentException(
+                "Polling loop filter must contain at least one byte.");
         }
-
-        return true;
+        return HexFormat.of().withUpperCase().formatHex(plfBytes);
     }
 
     /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 904e184..cf6aab6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -403,7 +403,7 @@
                 resolvedPath = info.getResolvedBaseApkPath();
             }
             if (info == null || !info.isSealed() || resolvedPath == null) {
-                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
+                Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
                 finish();
                 return;
             }
@@ -418,7 +418,7 @@
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
             if (info == null || !info.isPreApprovalRequested()) {
-                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
+                Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
                 finish();
                 return;
             }
@@ -839,7 +839,9 @@
                     // work for the multiple user case, i.e. the caller task user and started
                     // Activity user are not the same. To avoid having multiple PIAs in the task,
                     // finish the current PackageInstallerActivity
-                    finish();
+                    // Because finish() is overridden to set the installation result, we must use
+                    // the original finish() method, or the confirmation dialog fails to appear.
+                    PackageInstallerActivity.super.finish();
                 }
             }, 500);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8b2a0ec..05e07a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -377,7 +377,9 @@
                 if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name
                     else SysuiR.string::dnd_is_on.name
             ).also { data ->
-                clock?.run { events.onZenDataChanged(data) }
+                mainExecutor.execute {
+                    clock?.run { events.onZenDataChanged(data) }
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index b0cc3bd..cd5b124 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -438,9 +438,20 @@
 
                 // Play haptics
                 launch {
-                    viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
-                        if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
-                            vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+                    viewModel.hapticsToPlay.collect { haptics ->
+                        if (haptics.hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+                            if (haptics.flag != null) {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    haptics.hapticFeedbackConstant,
+                                    haptics.flag,
+                                )
+                            } else {
+                                vibratorHelper.performHapticFeedback(
+                                    view,
+                                    haptics.hapticFeedbackConstant,
+                                )
+                            }
                             viewModel.clearHaptics()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 788991d..c933e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -53,6 +53,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 /** ViewModel for BiometricPrompt. */
@@ -144,9 +145,10 @@
     private val _forceLargeSize = MutableStateFlow(false)
     private val _forceMediumSize = MutableStateFlow(false)
 
-    private val _hapticsToPlay = MutableStateFlow(HapticFeedbackConstants.NO_HAPTICS)
+    private val _hapticsToPlay =
+        MutableStateFlow(HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, /* flag= */ null))
 
-    /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
+    /** Event fired to the view indicating a [HapticsToPlay] */
     val hapticsToPlay = _hapticsToPlay.asStateFlow()
 
     /** The current position of the prompt */
@@ -686,16 +688,26 @@
     }
 
     private fun vibrateOnSuccess() {
-        _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
+        _hapticsToPlay.value =
+            HapticsToPlay(
+                HapticFeedbackConstants.CONFIRM,
+                HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+            )
     }
 
     private fun vibrateOnError() {
-        _hapticsToPlay.value = HapticFeedbackConstants.REJECT
+        _hapticsToPlay.value =
+            HapticsToPlay(
+                HapticFeedbackConstants.REJECT,
+                HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+            )
     }
 
-    /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
+    /** Clears the [hapticsToPlay] variable by setting its constant to the NO_HAPTICS default. */
     fun clearHaptics() {
-        _hapticsToPlay.value = HapticFeedbackConstants.NO_HAPTICS
+        _hapticsToPlay.update { previous ->
+            HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, previous.flag)
+        }
     }
 
     companion object {
@@ -724,3 +736,9 @@
     val isStarted: Boolean
         get() = this == Normal || this == Delayed
 }
+
+/**
+ * The state of haptic feedback to play. It is composed by a [HapticFeedbackConstants] and a
+ * [HapticFeedbackConstants] flag.
+ */
+data class HapticsToPlay(val hapticFeedbackConstant: Int, val flag: Int?)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt
index 5a71bd6..7dfd53e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/SceneContainerFlagsExtension.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 
-private const val FLEXI_NOTIFS = false
+private const val FLEXI_NOTIFS = true
 
 /**
  * Returns whether flexiglass is displaying notifications, which is currently an optional piece of
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 8431fbc..9dedf5c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -18,9 +18,6 @@
 
 import android.content.Context
 import android.media.AudioManager
-import com.android.settingslib.media.data.repository.SpatializerRepository
-import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
-import com.android.settingslib.media.domain.interactor.SpatializerInteractor
 import com.android.settingslib.statusbar.notification.domain.interactor.NotificationsSoundPolicyInteractor
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
@@ -66,16 +63,5 @@
             notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
         ): AudioVolumeInteractor =
             AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
-
-        @Provides
-        fun provdieSpatializerRepository(
-            audioManager: AudioManager,
-            @Background backgroundContext: CoroutineContext,
-        ): SpatializerRepository =
-            SpatializerRepositoryImpl(audioManager.spatializer, backgroundContext)
-
-        @Provides
-        fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
-            SpatializerInteractor(repository)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
new file mode 100644
index 0000000..18a9161
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.dagger
+
+import android.media.AudioManager
+import android.media.Spatializer
+import com.android.settingslib.media.data.repository.SpatializerRepository
+import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+
+/** Spatializer module. */
+@Module
+interface SpatializerModule {
+    companion object {
+        @Provides
+        fun provideSpatializer(
+            audioManager: AudioManager,
+        ): Spatializer = audioManager.spatializer
+
+        @Provides
+        fun provdieSpatializerRepository(
+            spatializer: Spatializer,
+            @Background backgroundContext: CoroutineContext,
+        ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext)
+
+        @Provides
+        fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
+            SpatializerInteractor(repository)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c6aee42..64a5644 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -59,7 +59,8 @@
                 AudioModule.class,
                 AncModule.class,
                 CaptioningModule.class,
-                MediaDevicesModule.class
+                MediaDevicesModule.class,
+                SpatializerModule.class,
         },
         subcomponents = {
                 VolumePanelComponent.class
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index ff68fe3..140849b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -241,19 +241,27 @@
 
             viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
 
-            val confirmConstant by collectLastValue(viewModel.hapticsToPlay)
-            assertThat(confirmConstant)
+            val confirmHaptics by collectLastValue(viewModel.hapticsToPlay)
+            assertThat(confirmHaptics?.hapticFeedbackConstant)
                 .isEqualTo(
                     if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
                     else HapticFeedbackConstants.CONFIRM
                 )
+            assertThat(confirmHaptics?.flag)
+                .isEqualTo(
+                    if (expectConfirmation) null
+                    else HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
+                )
 
             if (expectConfirmation) {
                 viewModel.confirmAuthenticated()
             }
 
-            val confirmedConstant by collectLastValue(viewModel.hapticsToPlay)
-            assertThat(confirmedConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+            val confirmedHaptics by collectLastValue(viewModel.hapticsToPlay)
+            assertThat(confirmedHaptics?.hapticFeedbackConstant)
+                .isEqualTo(HapticFeedbackConstants.CONFIRM)
+            assertThat(confirmedHaptics?.flag)
+                .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
         }
 
     @Test
@@ -265,16 +273,21 @@
             viewModel.confirmAuthenticated()
         }
 
-        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
-        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+        val currentHaptics by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentHaptics?.hapticFeedbackConstant)
+            .isEqualTo(HapticFeedbackConstants.CONFIRM)
+        assertThat(currentHaptics?.flag)
+            .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
     }
 
     @Test
     fun playErrorHaptic_SetsRejectConstant() = runGenericTest {
         viewModel.showTemporaryError("test", "messageAfterError", false)
 
-        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
-        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+        val currentHaptics by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentHaptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+        assertThat(currentHaptics?.flag)
+            .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
     }
 
     @Test
@@ -800,8 +813,9 @@
             hapticFeedback = true,
         )
 
-        val constant by collectLastValue(viewModel.hapticsToPlay)
-        assertThat(constant).isEqualTo(HapticFeedbackConstants.REJECT)
+        val haptics by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+        assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
     }
 
     @Test
@@ -813,8 +827,8 @@
             hapticFeedback = false,
         )
 
-        val constant by collectLastValue(viewModel.hapticsToPlay)
-        assertThat(constant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS)
+        val haptics by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS)
     }
 
     private suspend fun TestScope.showTemporaryErrors(