Merge "Add presubmit for com.android.internal.net" into main am: 3f4e2a789e am: 37a297872b

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2875554

Change-Id: I3db4e8d7e42e5fcbe3dc3cd2ffdd7eae0830f346
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bee5011..975ec54 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -127,6 +127,7 @@
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IBinder;
+import android.os.IBinderCallback;
 import android.os.ICancellationSignal;
 import android.os.LocaleList;
 import android.os.Looper;
@@ -358,6 +359,15 @@
     /** Maps from activity token to the pending override configuration. */
     @GuardedBy("mPendingOverrideConfigs")
     private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>();
+
+    /**
+     * A queue of pending ApplicationInfo updates. In case when we get a concurrent update
+     * this queue allows us to only apply the latest object, and it can be applied on demand
+     * instead of waiting for the handler thread to reach the scheduled callback.
+     */
+    @GuardedBy("mResourcesManager")
+    private final ArrayMap<String, ApplicationInfo> mPendingAppInfoUpdates = new ArrayMap<>();
+
     /** The activities to be truly destroyed (not include relaunch). */
     final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
             Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
@@ -1259,9 +1269,19 @@
         }
 
         public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
+            synchronized (mResourcesManager) {
+                var oldAi = mPendingAppInfoUpdates.put(ai.packageName, ai);
+                if (oldAi != null && oldAi.createTimestamp > ai.createTimestamp) {
+                    Slog.w(TAG, "Skipping application info changed for obsolete AI with TS "
+                            + ai.createTimestamp + " < already pending TS "
+                            + oldAi.createTimestamp);
+                    mPendingAppInfoUpdates.put(ai.packageName, oldAi);
+                    return;
+                }
+            }
             mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai);
-            mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
-            sendMessage(H.APPLICATION_INFO_CHANGED, ai);
+            mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai.packageName);
+            sendMessage(H.APPLICATION_INFO_CHANGED, ai.packageName);
         }
 
         public void updateTimeZone() {
@@ -2436,7 +2456,7 @@
                     break;
                 }
                 case APPLICATION_INFO_CHANGED:
-                    handleApplicationInfoChanged((ApplicationInfo) msg.obj);
+                    applyPendingApplicationInfoChanges((String) msg.obj);
                     break;
                 case RUN_ISOLATED_ENTRY_POINT:
                     handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
@@ -3918,7 +3938,8 @@
             mProfiler.startProfiling();
         }
 
-        // Make sure we are running with the most recent config.
+        // Make sure we are running with the most recent config and resource paths.
+        applyPendingApplicationInfoChanges(r.activityInfo.packageName);
         mConfigurationController.handleConfigurationChanged(null, null);
         updateDeviceIdForNonUIContexts(deviceId);
 
@@ -6244,6 +6265,17 @@
         r.mLastReportedWindowingMode = newWindowingMode;
     }
 
+    private void applyPendingApplicationInfoChanges(String packageName) {
+        final ApplicationInfo ai;
+        synchronized (mResourcesManager) {
+            ai = mPendingAppInfoUpdates.remove(packageName);
+        }
+        if (ai == null) {
+            return;
+        }
+        handleApplicationInfoChanged(ai);
+    }
+
     /**
      * Updates the application info.
      *
@@ -6269,6 +6301,16 @@
             apk = ref != null ? ref.get() : null;
             ref = mResourcePackages.get(ai.packageName);
             resApk = ref != null ? ref.get() : null;
+            for (ActivityClientRecord ar : mActivities.values()) {
+                if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) {
+                    ar.activityInfo.applicationInfo = ai;
+                    if (apk != null || resApk != null) {
+                        ar.packageInfo = apk != null ? apk : resApk;
+                    } else {
+                        apk = ar.packageInfo;
+                    }
+                }
+            }
         }
 
         if (apk != null) {
@@ -7051,6 +7093,18 @@
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
+
+        // Set binder transaction callback after finishing bindApplication
+        Binder.setTransactionCallback(new IBinderCallback() {
+            @Override
+            public void onTransactionError(int pid, int code, int flags, int err) {
+                try {
+                    mgr.frozenBinderTransactionDetected(pid, code, flags, err);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+            }
+        });
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d859f3f..24cb9ea 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -460,6 +460,33 @@
      */
     public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28;
 
+    /**
+     * The process was killed because the binder proxy limit for system server was exceeded.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29;
+
+    /**
+     * The process was killed by the [kernel] Out-of-memory (OOM) killer; this
+     * would be set only when the reason is {@link #REASON_LOW_MEMORY}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_OOM_KILL = 30;
+
+    /**
+     * The process was killed because its async kernel binder buffer is running out
+     * while being frozen.
+     * this would be set only when the reason is {@link #REASON_FREEZER}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_FREEZER_BINDER_ASYNC_FULL = 31;
+
     // If there is any OEM code which involves additional app kill reasons, it should
     // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
 
@@ -635,6 +662,9 @@
         SUBREASON_KILL_BACKGROUND,
         SUBREASON_PACKAGE_UPDATE,
         SUBREASON_UNDELIVERED_BROADCAST,
+        SUBREASON_EXCESSIVE_BINDER_OBJECTS,
+        SUBREASON_OOM_KILL,
+        SUBREASON_FREEZER_BINDER_ASYNC_FULL,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SubReason {}
@@ -1360,6 +1390,12 @@
                 return "PACKAGE UPDATE";
             case SUBREASON_UNDELIVERED_BROADCAST:
                 return "UNDELIVERED BROADCAST";
+            case SUBREASON_EXCESSIVE_BINDER_OBJECTS:
+                return "EXCESSIVE BINDER OBJECTS";
+            case SUBREASON_OOM_KILL:
+                return "OOM KILL";
+            case SUBREASON_FREEZER_BINDER_ASYNC_FULL:
+                return "FREEZER BINDER ASYNC FULL";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 46260ea..37616e7 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -924,4 +924,14 @@
     void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
     int[] getUidFrozenState(in int[] uids);
+
+    /**
+     * Notify AMS about binder transactions to frozen apps.
+     *
+     * @param debugPid The binder transaction sender
+     * @param code The binder transaction code
+     * @param flags The binder transaction flags
+     * @param err The binder transaction error
+     */
+    oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err);
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 0414f79..ccbccbf 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -342,7 +342,9 @@
      */
     public void updateApplicationInfo(@NonNull ApplicationInfo aInfo,
             @Nullable List<String> oldPaths) {
-        setApplicationInfo(aInfo);
+        if (!setApplicationInfo(aInfo)) {
+            return;
+        }
 
         final List<String> newPaths = new ArrayList<>();
         makePaths(mActivityThread, aInfo, newPaths);
@@ -387,7 +389,13 @@
         mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
     }
 
-    private void setApplicationInfo(ApplicationInfo aInfo) {
+    private boolean setApplicationInfo(ApplicationInfo aInfo) {
+        if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) {
+            Slog.w(TAG, "New application info for package " + aInfo.packageName
+                    + " is out of date with TS " + aInfo.createTimestamp + " < the current TS "
+                    + mApplicationInfo.createTimestamp);
+            return false;
+        }
         final int myUid = Process.myUid();
         aInfo = adjustNativeLibraryPaths(aInfo);
         mApplicationInfo = aInfo;
@@ -410,6 +418,7 @@
         if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
             mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
         }
+        return true;
     }
 
     void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid,
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 52949d6..2349ef8 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -180,6 +180,11 @@
 
     @Override
     public void injectInputEventToInputFilter(InputEvent event) throws RemoteException {
+        synchronized (mLock) {
+            throwIfCalledByNotTrustedUidLocked();
+            throwIfShutdownLocked();
+            throwIfNotConnectedLocked();
+        }
         mAccessibilityManager.injectInputEventToInputFilter(event);
     }
 
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index d352be1..581115b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3203,7 +3203,7 @@
      * stream configurations are the same as for applications targeting SDK version older than
      * 31.</p>
      * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a>
      * for additional mandatory stream configurations on a per-capability basis.</p>
      * <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p>
      * <ul>
@@ -3322,11 +3322,12 @@
      * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL }
      * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }.
      * This is an app-readable conversion of the mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>.
+     * based on specific device level and capabilities.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * As per documentation, the stream combinations with given PREVIEW, RECORD and
@@ -3355,11 +3356,12 @@
     /**
      * <p>An array of mandatory concurrent stream combinations.
      * This is an app-readable conversion of the concurrent mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a>
+     * for each device which has its Id present in the set returned by
      * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }.
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
@@ -3464,7 +3466,7 @@
      * <p>If a camera device supports multi-resolution output streams for a particular format, for
      * each of its mandatory stream combinations, the camera device will support using a
      * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
      * for additional details.</p>
      * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }.
      * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with
@@ -3473,7 +3475,7 @@
      * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution
      * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported
      * multi-resolution output stream formats. Refer to
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }}
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a>
      * for details about the additional mandatory stream combinations in this case.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
@@ -3586,11 +3588,12 @@
      * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set
      * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
      * This is an app-readable conversion of the maximum resolution mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a>
+     * for each device which has the
      * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
      * capability.
      * Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3613,11 +3616,12 @@
      * 10-bit output capability
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
      * This is an app-readable conversion of the 10 bit output mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a>
+     * for each device which has the
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT }
      * capability.
      * Clients can use the array as a quick reference to find an appropriate camera stream
@@ -3638,11 +3642,12 @@
      * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
      * This is an app-readable conversion of the preview stabilization mandatory stream
      * combination
-     * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION}
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a>
+     * for each device which supports {@code PREVIEW_STABILIZATION}
      * Clients can use the array as a quick reference to find an appropriate camera stream
      * combination.
      * The mandatory stream combination array will be {@code null} in case the device does not
@@ -3715,8 +3720,8 @@
      * <p>The guaranteed stream combinations related to stream use case for a camera device with
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability is documented in the camera device
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream
-     * combinations.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>.
+     * The application is strongly recommended to use one of the guaranteed stream combinations.
      * If the application creates a session with a stream combination not in the guaranteed
      * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
      * the camera device may ignore some stream use cases due to hardware constraints
@@ -3752,11 +3757,13 @@
     /**
      * <p>An array of mandatory stream combinations with stream use cases.
      * This is an app-readable conversion of the mandatory stream combination
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a>
+     * with each stream's use case being set.</p>
      * <p>The array of
      * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
      * generated according to the documented
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a>
+     * for a camera device with
      * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
      * capability.
      * The mandatory stream combination array will be {@code null} in case the device doesn't
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index dfc27ca..a0cf972 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1213,8 +1213,7 @@
      * <ul>
      * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
      * <li>All mandatory stream combinations for this specific capability as per
-     *   documentation
-     *   {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li>
+     *   <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
      * <li>In case the device is not able to capture some combination of supported
      *   standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
      *   then those constraints must be listed in
@@ -1253,8 +1252,8 @@
      * </ul>
      * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
      * lists all of the supported stream use cases.</p>
-     * <p>Refer to
-     * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations }
+     * <p>Refer to the
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>
      * for the mandatory stream combinations involving stream use cases, which can also be
      * queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p>
      * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
@@ -1753,9 +1752,9 @@
     /**
      * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or
      * better.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic
      * support for color image capture. The only exception is that the device may
      * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth
@@ -1781,9 +1780,9 @@
 
     /**
      * <p>This camera device is capable of supporting advanced imaging applications.</p>
-     * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>A <code>FULL</code> device will support below capabilities:</p>
      * <ul>
      * <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
@@ -1811,9 +1810,9 @@
 
     /**
      * <p>This camera device is running in backward compatibility mode.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the
-     * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations }
-     * documentation are supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a>
+     * in the documentation are supported.</p>
      * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual
      * post-processing, arbitrary cropping regions, and has relaxed performance constraints.
      * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a
@@ -1836,9 +1835,9 @@
      * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to
      * FULL-level capabilities.</p>
      * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and
-     * <code>LIMITED</code> tables in the
-     * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations }
-     * documentation are guaranteed to be supported.</p>
+     * <code>LIMITED</code>
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a>
+     * in the documentation are guaranteed to be supported.</p>
      * <p>The following additional capabilities are guaranteed to be supported:</p>
      * <ul>
      * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 25c84e5..78ae8d5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1477,7 +1477,7 @@
      * <p>To start a CaptureSession with a target FPS range different from the
      * capture request template's default value, the application
      * is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the target fps range before creating the capture session. The aeTargetFpsRange is
      * typically a session parameter. Specifying it at session creation time helps avoid
      * session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2154,7 +2154,7 @@
      * OFF if the recording output is not stabilized, or if there are no output
      * Surface types that can be stabilized.</p>
      * <p>The application is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the desired video stabilization mode before creating the capture session.
      * Video stabilization mode is a session parameter on many devices. Specifying
      * it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 986dd889..18632e5 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -896,7 +896,7 @@
      * <p>To start a CaptureSession with a target FPS range different from the
      * capture request template's default value, the application
      * is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the target fps range before creating the capture session. The aeTargetFpsRange is
      * typically a session parameter. Specifying it at session creation time helps avoid
      * session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2379,7 +2379,7 @@
      * OFF if the recording output is not stabilized, or if there are no output
      * Surface types that can be stabilized.</p>
      * <p>The application is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the desired video stabilization mode before creating the capture session.
      * Video stabilization mode is a session parameter on many devices. Specifying
      * it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 218d4bb..3db1cb0 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -665,6 +665,32 @@
      */
     public static final native void blockUntilThreadAvailable();
 
+
+    /**
+     * TODO (b/308179628): Move this to libbinder for non-Java usages.
+     */
+    private static IBinderCallback sBinderCallback = null;
+
+    /**
+     * Set callback function for unexpected binder transaction errors.
+     *
+     * @hide
+     */
+    public static final void setTransactionCallback(IBinderCallback callback) {
+        sBinderCallback = callback;
+    }
+
+    /**
+     * Execute the callback function if it's already set.
+     *
+     * @hide
+     */
+    public static final void transactionCallback(int pid, int code, int flags, int err) {
+        if (sBinderCallback != null) {
+            sBinderCallback.onTransactionError(pid, code, flags, err);
+        }
+    }
+
     /**
      * Default constructor just initializes the object.
      *
diff --git a/core/java/android/os/IBinderCallback.java b/core/java/android/os/IBinderCallback.java
new file mode 100644
index 0000000..e4be5b0
--- /dev/null
+++ b/core/java/android/os/IBinderCallback.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Callback interface for binder transaction errors
+ *
+ * @hide
+ */
+public interface IBinderCallback {
+    /**
+     * Callback function for unexpected binder transaction errors.
+     *
+     * @param debugPid The binder transaction sender
+     * @param code The binder transaction code
+     * @param flags The binder transaction flags
+     * @param err The binder transaction error
+     */
+    void onTransactionError(int debugPid, int code, int flags, int err);
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 58376a7..0801dd8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,16 +62,14 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Deque;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
 
 /**
  * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -89,6 +87,8 @@
             DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
             DocumentsContract.QUERY_ARG_MIME_TYPES);
 
+    private static final int MAX_RESULTS_NUMBER = 23;
+
     private static String joinNewline(String... args) {
         return TextUtils.join("\n", args);
     }
@@ -375,62 +375,53 @@
     }
 
     /**
-     * This method is similar to
-     * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
-     * all children documents including hidden directories/files.
-     *
-     * <p>
-     * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
-     * reasons. This method may show privacy sensitive data, so its usage should only be in
-     * restricted modes.
-     *
-     * @param parentDocumentId the directory to return children for.
-     * @param projection list of {@link Document} columns to put into the
-     *            cursor. If {@code null} all supported columns should be
-     *            included.
-     * @param sortOrder how to order the rows, formatted as an SQL
-     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
-     *            Passing {@code null} will use the default sort order, which
-     *            may be unordered. This ordering is a hint that can be used to
-     *            prioritize how data is fetched from the network, but UI may
-     *            always enforce a specific ordering
-     * @throws FileNotFoundException when parent document doesn't exist or query fails
+     * WARNING: this method should really be {@code final}, but for the backward compatibility it's
+     * not; new classes that extend {@link FileSystemProvider} should override
+     * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.
      */
-    protected Cursor queryChildDocumentsShowAll(
-            String parentDocumentId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
-    }
-
     @Override
-    public Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder)
+    public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
-        // Access to some directories is hidden for privacy reasons.
-        return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);
     }
 
-    private Cursor queryChildDocuments(
-            String parentDocumentId, String[] projection, String sortOrder,
-            @NonNull Predicate<File> filter) throws FileNotFoundException {
-        final File parent = getFileForDocId(parentDocumentId);
-        final MatrixCursor result = new DirectoryCursor(
-                resolveProjection(projection), parentDocumentId, parent);
+    /**
+     * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it
+     * could return <b>all</b> content of the directory, <b>including restricted (hidden)
+     * directories and files</b>.
+     * <p>
+     * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and
+     * {@code Android/obb/} on the external storage) are hidden for privacy reasons.
+     * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care.
+     */
+    @Override
+    public final Cursor queryChildDocumentsForManage(String documentId, String[] projection,
+            String sortOrder) throws FileNotFoundException {
+        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);
+    }
 
-        if (!filter.test(parent)) {
-            Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId);
+    protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder,
+            boolean includeHidden) throws FileNotFoundException {
+        final File parent = getFileForDocId(documentId);
+        final MatrixCursor result = new DirectoryCursor(
+                resolveProjection(projection), documentId, parent);
+
+        if (!parent.isDirectory()) {
+            Log.w(TAG, '"' + documentId + "\" is not a directory");
             return result;
         }
 
-        if (parent.isDirectory()) {
-            for (File file : FileUtils.listFilesOrEmpty(parent)) {
-                if (filter.test(file)) {
-                    includeFile(result, null, file);
-                }
-            }
-        } else {
-            Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
+        if (!includeHidden && shouldHideDocument(documentId)) {
+            Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");
+            return result;
         }
+
+        for (File file : FileUtils.listFilesOrEmpty(parent)) {
+            if (!includeHidden && shouldHideDocument(file)) continue;
+
+            includeFile(result, null, file);
+        }
+
         return result;
     }
 
@@ -452,23 +443,29 @@
      *
      * @see ContentResolver#EXTRA_HONORED_ARGS
      */
-    protected final Cursor querySearchDocuments(
-            File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
-            throws FileNotFoundException {
+    protected final Cursor querySearchDocuments(File folder, String[] projection,
+            Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
-        final List<File> pending = new ArrayList<>();
-        pending.add(folder);
-        while (!pending.isEmpty() && result.getCount() < 24) {
-            final File file = pending.remove(0);
-            if (shouldHide(file)) continue;
+
+        // We'll be a running a BFS here.
+        final Queue<File> pending = new ArrayDeque<>();
+        pending.offer(folder);
+
+        while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) {
+            final File file = pending.poll();
+
+            // Skip hidden documents (both files and directories)
+            if (shouldHideDocument(file)) continue;
 
             if (file.isDirectory()) {
                 for (File child : FileUtils.listFilesOrEmpty(file)) {
-                    pending.add(child);
+                    pending.offer(child);
                 }
             }
-            if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
-                    queryArgs)) {
+
+            if (exclusion.contains(file.getAbsolutePath())) continue;
+
+            if (matchSearchQueryArguments(file, queryArgs)) {
                 includeFile(result, null, file);
             }
         }
@@ -612,26 +609,23 @@
 
         final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
         if (flagIndex != -1) {
+            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
             int flags = 0;
             if (file.canWrite()) {
-                if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+                flags |= Document.FLAG_SUPPORTS_DELETE;
+                flags |= Document.FLAG_SUPPORTS_RENAME;
+                flags |= Document.FLAG_SUPPORTS_MOVE;
+                if (isDir) {
                     flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
-
-                    if (shouldBlockFromTree(docId)) {
-                        flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
-                    }
-
                 } else {
                     flags |= Document.FLAG_SUPPORTS_WRITE;
-                    flags |= Document.FLAG_SUPPORTS_DELETE;
-                    flags |= Document.FLAG_SUPPORTS_RENAME;
-                    flags |= Document.FLAG_SUPPORTS_MOVE;
                 }
             }
 
+            if (isDir && shouldBlockDirectoryFromTree(docId)) {
+                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
+            }
+
             if (mimeType.startsWith("image/")) {
                 flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
             }
@@ -664,22 +658,36 @@
         return row;
     }
 
-    private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
-            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+    /**
+     * Some providers may want to restrict access to certain directories and files,
+     * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
+     * privacy reasons.
+     * Such providers should override this method.
+     */
+    protected boolean shouldHideDocument(@NonNull String documentId)
+            throws FileNotFoundException {
+        return false;
+    }
 
     /**
-     * In a scoped storage world, access to "Android/data" style directories are
-     * hidden for privacy reasons.
+     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
+     * a {@link String} {@code documentId}.
+     *
+     * @see #shouldHideDocument(String)
      */
-    protected boolean shouldHide(@NonNull File file) {
-        return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+    protected final boolean shouldHideDocument(@NonNull File document)
+            throws FileNotFoundException {
+        return shouldHideDocument(getDocIdForFile(document));
     }
 
-    private boolean shouldShow(@NonNull File file) {
-        return !shouldHide(file);
-    }
-
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
+    /**
+     * @return if the directory that should be blocked from being selected when the user launches
+     * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
+     */
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
         return false;
     }
 
diff --git a/core/java/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java
new file mode 100644
index 0000000..9cc4a35
--- /dev/null
+++ b/core/java/com/android/internal/os/BinderfsStatsReader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import com.android.internal.util.ProcFileReader;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Reads and parses {@code binder_logs/stats} file in the {@code binderfs} filesystem.
+ * Reuse procFileReader as the contents are generated by Linux kernel in the same way.
+ *
+ * A typical example of binderfs stats log
+ *
+ * binder stats:
+ * BC_TRANSACTION: 378004
+ * BC_REPLY: 268352
+ * BC_FREE_BUFFER: 665854
+ * ...
+ * proc 12645
+ * context binder
+ * threads: 12
+ * requested threads: 0+5/15
+ * ready threads 0
+ * free async space 520192
+ * ...
+ */
+public class BinderfsStatsReader {
+    private final String mPath;
+
+    public BinderfsStatsReader() {
+        mPath = "/dev/binderfs/binder_logs/stats";
+    }
+
+    public BinderfsStatsReader(String path) {
+        mPath = path;
+    }
+
+    /**
+     * Read binderfs stats and call the consumer(pid, free) function for each valid process
+     *
+     * @param predicate  Test if the pid is valid.
+     * @param biConsumer Callback function for each valid pid and its free async space
+     * @param consumer   The error function to deal with exceptions
+     */
+    public void handleFreeAsyncSpace(Predicate<Integer> predicate,
+            BiConsumer<Integer, Integer> biConsumer, Consumer<Exception> consumer) {
+        try (ProcFileReader mReader = new ProcFileReader(new FileInputStream(mPath))) {
+            while (mReader.hasMoreData()) {
+                // find the next process
+                if (!mReader.nextString().equals("proc")) {
+                    mReader.finishLine();
+                    continue;
+                }
+
+                // read pid
+                int pid = mReader.nextInt();
+                mReader.finishLine();
+
+                // check if we have interest in this process
+                if (!predicate.test(pid)) {
+                    continue;
+                }
+
+                // read free async space
+                mReader.finishLine(); // context binder
+                mReader.finishLine(); // threads:
+                mReader.finishLine(); // requested threads:
+                mReader.finishLine(); // ready threads
+                if (!mReader.nextString().equals("free")) {
+                    mReader.finishLine();
+                    continue;
+                }
+                if (!mReader.nextString().equals("async")) {
+                    mReader.finishLine();
+                    continue;
+                }
+                if (!mReader.nextString().equals("space")) {
+                    mReader.finishLine();
+                    continue;
+                }
+                int free = mReader.nextInt();
+                mReader.finishLine();
+                biConsumer.accept(pid, free);
+            }
+        } catch (IOException | NumberFormatException e) {
+            consumer.accept(e);
+        }
+    }
+}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index bfd80a9e..d2d5186 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -73,6 +73,7 @@
     jclass mClass;
     jmethodID mExecTransact;
     jmethodID mGetInterfaceDescriptor;
+    jmethodID mTransactionCallback;
 
     // Object state.
     jfieldID mObject;
@@ -1173,6 +1174,8 @@
     gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
     gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor",
         "()Ljava/lang/String;");
+    gBinderOffsets.mTransactionCallback =
+            GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
     gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
 
     return RegisterMethodsOrDie(
@@ -1387,7 +1390,12 @@
 
     if (err == NO_ERROR) {
         return JNI_TRUE;
-    } else if (err == UNKNOWN_TRANSACTION) {
+    }
+
+    env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(),
+                              code, flags, err);
+
+    if (err == UNKNOWN_TRANSACTION) {
         return JNI_FALSE;
     }
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 56066b2..b12e147 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1121,10 +1121,8 @@
       }
     }
     // Fallback done
-
-    fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(),
-        ce_data_inode, parent_path.data()));
-    return nullptr;
+    ALOGW("Unable to find %s:%lld in %s", package_name.data(), ce_data_inode, parent_path.data());
+    return "";
   }
 }
 
@@ -1145,11 +1143,19 @@
                         true /*call_fail_fn*/);
 
   std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
+  if (ce_data_path.empty()) {
+    ALOGE("Ignoring missing CE app data dir for %s\n", package_name.data());
+    return;
+  }
   if (!createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn,
                              false /*call_fail_fn*/)) {
     // CE might unlocks and the name is decrypted
     // get the name and mount again
     ce_data_path=getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
+    if (ce_data_path.empty()) {
+      ALOGE("Ignoring missing CE app data dir for %s\n", package_name.data());
+      return;
+    }
     mountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
   }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bc74f39..b8553cd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8284,6 +8284,10 @@
             android:exported="true">
         </provider>
 
+        <meta-data
+            android:name="com.android.server.patch.25239169"
+            android:value="true" />
+
     </application>
 
 </manifest>
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index de8ff3f..328a1721 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -68,6 +68,7 @@
         deleteViaContentProvider(NAMESPACE, KEY);
         deleteViaContentProvider(NAMESPACE, KEY2);
         deleteViaContentProvider(NAMESPACE, KEY3);
+        DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 86f26e5..df212eb 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -17,7 +17,9 @@
 package android.widget;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.platform.test.annotations.Presubmit;
 import android.util.PollingCheck;
 
@@ -32,6 +34,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 @Presubmit
@@ -49,23 +54,43 @@
     }
 
     @Test
-    public void testScrollAfterFlingTop() {
-        mHorizontalScrollView.scrollTo(100, 0);
-        mHorizontalScrollView.fling(-10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+    public void testScrollAfterFlingLeft() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowLeft = edgeEffect;
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame
+        PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);
         assertEquals(0, mHorizontalScrollView.getScrollX());
     }
 
     @Test
-    public void testScrollAfterFlingBottom() {
+    public void testScrollAfterFlingRight() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowRight = edgeEffect;
         int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
         int maxScroll = childWidth - mHorizontalScrollView.getWidth();
-        mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
-        mHorizontalScrollView.fling(10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame
         PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
         assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
     }
+
+    static class WatchedEdgeEffect extends EdgeEffect {
+        public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
+
+        WatchedEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            super.onAbsorb(velocity);
+            onAbsorbLatch.countDown();
+        }
+    }
 }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
new file mode 100644
index 0000000..5498083
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.util.IntArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Files;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BinderfsStatsReaderTest {
+    private static final String BINDER_LOGS_STATS_HEADER = """
+            binder stats:
+            BC_TRANSACTION: 695756
+            BC_REPLY: 547779
+            BC_FREE_BUFFER: 1283223
+            BR_FAILED_REPLY: 4
+            BR_FROZEN_REPLY: 3
+            BR_ONEWAY_SPAM_SUSPECT: 1
+            proc: active 313 total 377
+            thread: active 3077 total 5227
+            """;
+    private static final String BINDER_LOGS_STATS_PROC1 = """
+            proc 14505
+            context binder
+              threads: 4
+              requested threads: 0+2/15
+              ready threads 0
+              free async space 520192
+              nodes: 9
+              refs: 29 s 29 w 29
+              buffers: 0
+            """;
+    private static final String BINDER_LOGS_STATS_PROC2 = """
+            proc 14461
+            context binder
+              threads: 8
+              requested threads: 0+2/15
+              ready threads 0
+              free async space 62
+              nodes: 30
+              refs: 51 s 51 w 51
+              buffers: 0
+            """;
+    private static final String BINDER_LOGS_STATS_PROC3 = """
+            proc 542
+            context binder
+              threads: 2
+              requested threads: 0+0/15
+              ready threads 0
+              free async space 519896
+              nodes: 1
+              refs: 2 s 3 w 2
+              buffers: 1
+            """;
+    private static final String BINDER_LOGS_STATS_PROC4 = """
+            proc 540
+            context binder
+              threads: 1
+              requested threads: 0+0/0
+              ready threads 1
+              free async space 44
+              nodes: 4
+              refs: 1 s 1 w 1
+              buffers: 0
+            """;
+    private File mStatsDirectory;
+    private int mFreezerBinderAsyncThreshold;
+    private IntArray mValidPids; // The pool of valid pids
+    private IntArray mStatsPids; // The pids read from binderfs stats that are also valid
+    private IntArray mStatsFree; // The free async space of the above pids
+    private boolean mHasError;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+        mStatsDirectory = context.getDir("binder_logs", Context.MODE_PRIVATE);
+        mFreezerBinderAsyncThreshold = 1024;
+        mValidPids = IntArray.fromArray(new int[]{14505, 14461, 542, 540}, 4);
+        mStatsPids = new IntArray();
+        mStatsFree = new IntArray();
+        mHasError = false;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteContents(mStatsDirectory);
+    }
+
+    @Test
+    public void testNoneProc() throws Exception {
+        runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER);
+        assertFalse(mHasError);
+        assertEquals(0, mStatsPids.size());
+        assertEquals(0, mStatsFree.size());
+    }
+
+    @Test
+    public void testOneProc() throws Exception {
+        runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1);
+        assertFalse(mHasError);
+        assertEquals(0, mStatsPids.size());
+        assertEquals(0, mStatsFree.size());
+    }
+
+    @Test
+    public void testTwoProc() throws Exception {
+        runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+                + BINDER_LOGS_STATS_PROC2);
+        assertFalse(mHasError);
+        assertArrayEquals(mStatsPids.toArray(), new int[]{14461});
+        assertArrayEquals(mStatsFree.toArray(), new int[]{62});
+    }
+
+    @Test
+    public void testThreeProc() throws Exception {
+        runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+                + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3);
+        assertFalse(mHasError);
+        assertArrayEquals(mStatsPids.toArray(), new int[]{14461});
+        assertArrayEquals(mStatsFree.toArray(), new int[]{62});
+    }
+
+    @Test
+    public void testFourProc() throws Exception {
+        runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+                + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4);
+        assertFalse(mHasError);
+        assertArrayEquals(mStatsPids.toArray(), new int[]{14461, 540});
+        assertArrayEquals(mStatsFree.toArray(), new int[]{62, 44});
+    }
+
+    @Test
+    public void testInvalidProc() throws Exception {
+        mValidPids = new IntArray();
+        runHandleBlockingFileLocks(BINDER_LOGS_STATS_HEADER + BINDER_LOGS_STATS_PROC1
+                + BINDER_LOGS_STATS_PROC2 + BINDER_LOGS_STATS_PROC3 + BINDER_LOGS_STATS_PROC4);
+        assertFalse(mHasError);
+        assertEquals(0, mStatsPids.size());
+        assertEquals(0, mStatsFree.size());
+    }
+
+    private void runHandleBlockingFileLocks(String fileContents) throws Exception {
+        File tempFile = File.createTempFile("stats", null, mStatsDirectory);
+        Files.write(tempFile.toPath(), fileContents.getBytes());
+        new BinderfsStatsReader(tempFile.toString()).handleFreeAsyncSpace(
+                // Check if the current process is a valid one
+                pid -> mValidPids.indexOf(pid) != -1,
+
+                // Check if the current process is running out of async binder space
+                (pid, free) -> {
+                    if (free < mFreezerBinderAsyncThreshold) {
+                        mStatsPids.add(pid);
+                        mStatsFree.add(free);
+                    }
+                },
+
+                // Log the error if binderfs stats can't be accesses or correctly parsed
+                exception -> mHasError = true);
+        Files.delete(tempFile.toPath());
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c95c9f0..1c79239 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2244,6 +2244,25 @@
         return SPLIT_POSITION_UNDEFINED;
     }
 
+    /**
+     * Returns the {@link StageType} where {@param token} is being used
+     * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise
+     */
+    @StageType
+    public int getSplitItemStage(@Nullable WindowContainerToken token) {
+        if (token == null) {
+            return STAGE_TYPE_UNDEFINED;
+        }
+
+        if (mMainStage.containsToken(token)) {
+            return STAGE_TYPE_MAIN;
+        } else if (mSideStage.containsToken(token)) {
+            return STAGE_TYPE_SIDE;
+        }
+
+        return STAGE_TYPE_UNDEFINED;
+    }
+
     @Override
     public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
         final StageTaskListener topLeftStage =
@@ -2491,7 +2510,16 @@
                 mRecentTasks.ifPresent(
                         recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
             }
-            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
+            @StageType int topStage = STAGE_TYPE_UNDEFINED;
+            if (isSplitScreenVisible()) {
+                // Get the stage where a child exists to keep that stage onTop
+                if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) {
+                    topStage = STAGE_TYPE_MAIN;
+                } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) {
+                    topStage = STAGE_TYPE_SIDE;
+                }
+            }
+            prepareExitSplitScreen(topStage, outWCT);
         }
     }
 
@@ -2908,7 +2936,11 @@
         return SPLIT_POSITION_UNDEFINED;
     }
 
-    /** Synchronize split-screen state with transition and make appropriate preparations. */
+    /**
+     * Synchronize split-screen state with transition and make appropriate preparations.
+     * @param toStage The stage that will not be dismissed. If set to
+     *        {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed
+     */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
             @NonNull SurfaceControl.Transaction finishT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4897c4e..f0bb665 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -50,6 +50,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellInit;
@@ -511,8 +512,26 @@
             // make a new startTransaction because pip's startEnterAnimation "consumes" it so
             // we need a separate one to send over to launcher.
             SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+            @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+            if (mSplitHandler.isSplitScreenVisible()) {
+                // The non-going home case, we could be pip-ing one of the split stages and keep
+                // showing the other
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    TransitionInfo.Change change = info.getChanges().get(i);
+                    if (change == pipChange) {
+                        // Ignore the change/task that's going into Pip
+                        continue;
+                    }
+                    @SplitScreen.StageType int splitItemStage =
+                            mSplitHandler.getSplitItemStage(change.getLastParent());
+                    if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+                        topStageToKeep = splitItemStage;
+                        break;
+                    }
+                }
+            }
             // Let split update internal state for dismiss.
-            mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED,
+            mSplitHandler.prepareDismissAnimation(topStageToKeep,
                     EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
                     finishTransaction);
 
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index af3c295..5a27435 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -68,7 +68,7 @@
     /**
      * The unique address of the device. Some devices don't have addresses, only an empty string.
      */
-    private final @NonNull String mAddress;
+    private @NonNull String mAddress;
     /**
      * The non-unique name of the device. Some devices don't have names, only an empty string.
      * Should not be used as a unique identifier for a device.
@@ -188,6 +188,21 @@
 
     /**
      * @hide
+     * Copy Constructor.
+     * @param ada the copied AudioDeviceAttributes
+     */
+    public AudioDeviceAttributes(AudioDeviceAttributes ada) {
+        mRole = ada.getRole();
+        mType = ada.getType();
+        mAddress = ada.getAddress();
+        mName = ada.getName();
+        mNativeType = ada.getInternalType();
+        mAudioProfiles = ada.getAudioProfiles();
+        mAudioDescriptors = ada.getAudioDescriptors();
+    }
+
+    /**
+     * @hide
      * Returns the role of a device
      * @return the role
      */
@@ -218,6 +233,15 @@
 
     /**
      * @hide
+     * Sets the device address. Only used by audio service.
+     */
+    public void setAddress(@NonNull String address) {
+        Objects.requireNonNull(address);
+        mAddress = address;
+    }
+
+    /**
+     * @hide
      * Returns the name of the audio device, or an empty string for devices without one
      * @return the device name
      */
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4c313b2..3409c29 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.externalstorage;
 
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.StorageStatsManager;
@@ -64,7 +66,19 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.regex.Pattern;
 
+/**
+ * Presents content of the shared (a.k.a. "external") storage.
+ * <p>
+ * Starting with Android 11 (R), restricts access to the certain sections of the shared storage:
+ * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in
+ * the DocumentsUI by default.
+ * See <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ * Storage updates in Android 11</a>.
+ * <p>
+ * Documents ID format: {@code root:path/to/file}.
+ */
 public class ExternalStorageProvider extends FileSystemProvider {
     private static final String TAG = "ExternalStorage";
 
@@ -75,7 +89,12 @@
     private static final Uri BASE_URI =
             new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
 
-    // docId format: root:path/to/file
+    /**
+     * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
+     * {@code /Android/sandbox/} along with all their subdirectories and content.
+     */
+    private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
+            Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -278,76 +297,91 @@
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
 
+    /**
+     * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the
+     * integrated shared ("external") storage along with all their content and subdirectories as
+     * hidden.
+     */
     @Override
-    public Cursor queryChildDocumentsForManage(
-            String parentDocId, String[] projection, String sortOrder)
-            throws FileNotFoundException {
-        return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+    protected boolean shouldHideDocument(@NonNull String documentId) {
+        // Don't need to hide anything on USB drives.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        final String path = getPathFromDocId(documentId);
+        return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
     }
 
     /**
      * Check that the directory is the root of storage or blocked file from tree.
+     * <p>
+     * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
+     * the UI, but the user <b>WILL NOT</b> be able to select them.
      *
-     * @param docId the docId of the directory to be checked
+     * @param documentId the docId of the directory to be checked
      * @return true, should be blocked from tree. Otherwise, false.
+     *
+     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
      */
     @Override
-    protected boolean shouldBlockFromTree(@NonNull String docId) {
-        try {
-            final File dir = getFileForDocId(docId, false /* visible */);
-
-            // the file is null or it is not a directory
-            if (dir == null || !dir.isDirectory()) {
-                return false;
-            }
-
-            // Allow all directories on USB, including the root.
-            try {
-                RootInfo rootInfo = getRootFromDocId(docId);
-                if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
-                    return false;
-                }
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Failed to determine rootInfo for docId");
-            }
-
-            final String path = getPathFromDocId(docId);
-
-            // Block the root of the storage
-            if (path.isEmpty()) {
-                return true;
-            }
-
-            // Block Download folder from tree
-            if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android
-            if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT),
-                    path.toLowerCase(Locale.ROOT))) {
-                return true;
-            }
-
-            // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs
-            if (shouldHide(dir)) {
-                return true;
-            }
-
+    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+            throws FileNotFoundException {
+        final File dir = getFileForDocId(documentId, false);
+        // The file is null or it is not a directory
+        if (dir == null || !dir.isDirectory()) {
             return false;
-        } catch (IOException e) {
-            throw new IllegalArgumentException(
-                    "Failed to determine if " + docId + " should block from tree " + ": " + e);
         }
+
+        // Allow all directories on USB, including the root.
+        if (isOnRemovableUsbStorage(documentId)) {
+            return false;
+        }
+
+        // Get canonical(!) path. Note that this path will have neither leading nor training "/".
+        // This the root's path will be just an empty string.
+        final String path = getPathFromDocId(documentId);
+
+        // Block the root of the storage
+        if (path.isEmpty()) {
+            return true;
+        }
+
+        // Block /Download/ and /Android/ folders from the tree.
+        if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) ||
+                equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) {
+            return true;
+        }
+
+        // This shouldn't really make a difference, but just in case - let's block hidden
+        // directories as well.
+        if (shouldHideDocument(documentId)) {
+            return true;
+        }
+
+        return false;
     }
 
+    private boolean isOnRemovableUsbStorage(@NonNull String documentId) {
+        final RootInfo rootInfo;
+        try {
+            rootInfo = getRootFromDocId(documentId);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');
+            return false;
+        }
+
+        return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;
+    }
+
+    @NonNull
     @Override
-    protected String getDocIdForFile(File file) throws FileNotFoundException {
+    protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {
         return getDocIdForFileMaybeCreate(file, false);
     }
 
-    private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
+    @NonNull
+    private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)
             throws FileNotFoundException {
         String path = file.getAbsolutePath();
 
@@ -417,31 +451,33 @@
     private File getFileForDocId(String docId, boolean visible, boolean mustExist)
             throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return buildFile(root, docId, visible, mustExist);
+        return buildFile(root, docId, mustExist);
     }
 
-    private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
-            throws FileNotFoundException {
+    private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {
         RootInfo root = getRootFromDocId(docId);
-        return Pair.create(root, buildFile(root, docId, visible, true));
+        return Pair.create(root, buildFile(root, docId, /* mustExist */ true));
     }
 
     @VisibleForTesting
-    static String getPathFromDocId(String docId) throws IOException {
+    static String getPathFromDocId(String docId) {
         final int splitIndex = docId.indexOf(':', 1);
         final String docIdPath = docId.substring(splitIndex + 1);
-        // Get CanonicalPath and remove the first "/"
-        final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1);
 
-        if (canonicalPath.isEmpty()) {
-            return canonicalPath;
+        // Canonicalize path and strip the leading "/"
+        final String path;
+        try {
+            path = new File(docIdPath).getCanonicalPath().substring(1);
+        } catch (IOException e) {
+            Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"');
+            return "";
         }
 
-        // remove trailing "/"
-        if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') {
-            return canonicalPath.substring(0, canonicalPath.length() - 1);
+        // Remove the trailing "/" as well.
+        if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') {
+            return path.substring(0, path.length() - 1);
         } else {
-            return canonicalPath;
+            return path;
         }
     }
 
@@ -460,7 +496,7 @@
         return root;
     }
 
-    private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
+    private File buildFile(RootInfo root, String docId, boolean mustExist)
             throws FileNotFoundException {
         final int splitIndex = docId.indexOf(':', 1);
         final String path = docId.substring(splitIndex + 1);
@@ -544,7 +580,7 @@
     @Override
     public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
             throws FileNotFoundException {
-        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);
         final RootInfo root = resolvedDocId.first;
         File child = resolvedDocId.second;
 
@@ -648,6 +684,13 @@
         }
     }
 
+    /**
+     * Print the state into the given stream.
+     * Gets invoked when you run:
+     * <pre>
+     * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider
+     * </pre>
+     */
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
@@ -731,4 +774,8 @@
         }
         return bundle;
     }
+
+    private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) {
+        return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT));
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
deleted file mode 100644
index bc5824a..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.qrcode;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-
-import com.google.zxing.BarcodeFormat;
-import com.google.zxing.EncodeHintType;
-import com.google.zxing.MultiFormatWriter;
-import com.google.zxing.WriterException;
-import com.google.zxing.common.BitMatrix;
-
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-
-public final class QrCodeGenerator {
-    /**
-     * Generates a barcode image with {@code contents}.
-     *
-     * @param contents The contents to encode in the barcode
-     * @param size     The preferred image size in pixels
-     * @return Barcode bitmap
-     */
-    public static Bitmap encodeQrCode(String contents, int size)
-            throws WriterException, IllegalArgumentException {
-        final Map<EncodeHintType, Object> hints = new HashMap<>();
-        if (!isIso88591(contents)) {
-            hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
-        }
-
-        final BitMatrix qrBits = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,
-                size, size, hints);
-        final Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565);
-        for (int x = 0; x < size; x++) {
-            for (int y = 0; y < size; y++) {
-                bitmap.setPixel(x, y, qrBits.get(x, y) ? Color.BLACK : Color.WHITE);
-            }
-        }
-        return bitmap;
-    }
-
-    private static boolean isIso88591(String contents) {
-        CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
-        return encoder.canEncode(contents);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.kt b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.kt
new file mode 100644
index 0000000..7b67ec6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.qrcode
+
+import android.annotation.ColorInt
+import android.graphics.Bitmap
+import android.graphics.Color
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.MultiFormatWriter
+import com.google.zxing.WriterException
+import java.nio.charset.StandardCharsets
+import java.util.EnumMap
+
+object QrCodeGenerator {
+    /**
+     * Generates a barcode image with [contents].
+     *
+     * @param contents The contents to encode in the barcode
+     * @param size     The preferred image size in pixels
+     * @param invert   Whether to invert the black/white pixels (e.g. for dark mode)
+     * @return Barcode bitmap
+     */
+    @JvmStatic
+    @Throws(WriterException::class, java.lang.IllegalArgumentException::class)
+    fun encodeQrCode(contents: String, size: Int, invert: Boolean): Bitmap =
+        encodeQrCode(contents, size, DEFAULT_MARGIN, invert)
+
+    private const val DEFAULT_MARGIN = -1
+
+    /**
+     * Generates a barcode image with [contents].
+     *
+     * @param contents The contents to encode in the barcode
+     * @param size     The preferred image size in pixels
+     * @param margin   The margin around the actual barcode
+     * @param invert   Whether to invert the black/white pixels (e.g. for dark mode)
+     * @return Barcode bitmap
+     */
+    @JvmOverloads
+    @JvmStatic
+    @Throws(WriterException::class, IllegalArgumentException::class)
+    fun encodeQrCode(
+        contents: String,
+        size: Int,
+        margin: Int = DEFAULT_MARGIN,
+        invert: Boolean = false,
+    ): Bitmap {
+        val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
+        if (!isIso88591(contents)) {
+            hints[EncodeHintType.CHARACTER_SET] = StandardCharsets.UTF_8.name()
+        }
+        if (margin != DEFAULT_MARGIN) {
+            hints[EncodeHintType.MARGIN] = margin
+        }
+        val qrBits = MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, size, size, hints)
+        @ColorInt val setColor = if (invert) Color.WHITE else Color.BLACK
+        @ColorInt val unsetColor = if (invert) Color.BLACK else Color.WHITE
+        @ColorInt val pixels = IntArray(size * size)
+        for (x in 0 until size) {
+            for (y in 0 until size) {
+                pixels[x * size + y] = if (qrBits[x, y]) setColor else unsetColor
+            }
+        }
+        return Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565).apply {
+            setPixels(pixels, 0, size, 0, 0, size, size)
+        }
+    }
+
+    private fun isIso88591(contents: String): Boolean =
+        StandardCharsets.ISO_8859_1.newEncoder().canEncode(contents)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3472a85..54a25a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -553,7 +553,9 @@
             Log.w(TAG, "ignoring onTouch with null overlay or animation view controller");
             return false;
         }
-        if (mOverlay.getAnimationViewController().shouldPauseAuth()) {
+        // Wait to receive up or cancel before pausing auth
+        if (mActivePointerId == MotionEvent.INVALID_POINTER_ID
+                && mOverlay.getAnimationViewController().shouldPauseAuth()) {
             Log.w(TAG, "ignoring onTouch with shouldPauseAuth = true");
             return false;
         }
@@ -562,12 +564,6 @@
                     + mOverlay.getRequestId());
             return false;
         }
-
-        if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f
-                && !mAlternateBouncerInteractor.isVisibleState())
-                || mPrimaryBouncerInteractor.isInTransit()) {
-            return false;
-        }
         if (event.getAction() == MotionEvent.ACTION_DOWN
                 || event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
             // Reset on ACTION_DOWN, start of new gesture
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 4cade77..323ed98 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -261,6 +261,13 @@
     }
     //TODO: brightnessfloat change usages to float.
     private int clampToUserSetting(int brightness) {
+        int screenBrightnessModeSetting = mSystemSettings.getIntForUser(
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+        if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
+            return brightness;
+        }
+
         int userSetting = mSystemSettings.getIntForUser(
                 Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE,
                 UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f2d9144..57be2af 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -388,6 +388,10 @@
     // TODO(b/280426085): Tracking Bug
     @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
 
+    // TODO(b/311222557): Tracking bug
+    val ROAMING_INDICATOR_VIA_DISPLAY_INFO =
+        releasedFlag("roaming_indicator_via_display_info")
+
     // TODO(b/292533677): Tracking Bug
     val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
         unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index c4749e0..c77f3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -231,7 +231,6 @@
             animation.removeEndListener(this)
 
             if (!canceled) {
-
                 // The delay between finishing this animation and starting the runnable
                 val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
 
@@ -461,7 +460,6 @@
     }
 
     private fun handleMoveEvent(event: MotionEvent) {
-
         val x = event.x
         val y = event.y
 
@@ -927,17 +925,7 @@
             GestureState.ACTIVE -> {
                 previousXTranslationOnActiveOffset = previousXTranslation
                 updateRestingArrowDimens()
-                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                    vibratorHelper.performHapticFeedback(
-                        mView,
-                        HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
-                    )
-                } else {
-                    vibratorHelper.cancel()
-                    mainHandler.postDelayed(10L) {
-                        vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
-                    }
-                }
+                performActivatedHapticFeedback()
                 val popVelocity =
                     if (previousState == GestureState.INACTIVE) {
                         POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
@@ -958,25 +946,24 @@
 
                 mView.popOffEdge(POP_ON_INACTIVE_VELOCITY)
 
-                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-                    vibratorHelper.performHapticFeedback(
-                        mView,
-                        HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
-                    )
-                } else {
-                    vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
-                }
+                performDeactivatedHapticFeedback()
                 updateRestingArrowDimens()
             }
             GestureState.FLUNG -> {
+                // Typically a vibration is only played while transitioning to ACTIVE. However there
+                // are instances where a fling to trigger back occurs while not in that state.
+                // (e.g. A fling is detected before crossing the trigger threshold.)
+                if (previousState != GestureState.ACTIVE) {
+                    performActivatedHapticFeedback()
+                }
                 mainHandler.postDelayed(POP_ON_FLING_DELAY) {
                     mView.popScale(POP_ON_FLING_VELOCITY)
                 }
-                updateRestingArrowDimens()
                 mainHandler.postDelayed(
                     onEndSetCommittedStateListener.runnable,
                     MIN_DURATION_FLING_ANIMATION
                 )
+                updateRestingArrowDimens()
             }
             GestureState.COMMITTED -> {
                 // In most cases, animating between states is handled via `updateRestingArrowDimens`
@@ -1011,6 +998,31 @@
         }
     }
 
+    private fun performDeactivatedHapticFeedback() {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            vibratorHelper.performHapticFeedback(
+                    mView,
+                    HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+            )
+        } else {
+            vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+        }
+    }
+
+    private fun performActivatedHapticFeedback() {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            vibratorHelper.performHapticFeedback(
+                    mView,
+                    HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+            )
+        } else {
+            vibratorHelper.cancel()
+            mainHandler.postDelayed(10L) {
+                vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+            }
+        }
+    }
+
     private fun convertVelocityToAnimationFactor(
         valueOnFastVelocity: Float,
         valueOnSlowVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 2469a98..3750c44 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -281,6 +281,11 @@
     }
 
     @Override
+    public void onNullBinding(ComponentName name) {
+        executeSetBindService(false);
+    }
+
+    @Override
     public void onServiceDisconnected(ComponentName name) {
         if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
         handleDeath();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index ae70384..c207324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -25,6 +25,7 @@
 import android.util.TimeUtils;
 
 import com.android.app.animation.Interpolators;
+import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.Dumpable;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -95,6 +96,7 @@
     private final KeyguardStateController mKeyguardStateController;
     private final StatusBarStateController mStatusBarStateController;
     private final CommandQueue mCommandQueue;
+    private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
     private boolean mTransitionDeferring;
     private long mTransitionDeferringStartTime;
@@ -134,6 +136,8 @@
         mDozeAmount = mStatusBarStateController.getDozeAmount();
         mContext = context;
         mDisplayId = mContext.getDisplayId();
+        mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
+                mHandler, mContext, null);
     }
 
     /** Call to cleanup the LightBarTransitionsController when done with it. */
@@ -279,7 +283,8 @@
      */
     public boolean supportsIconTintForNavMode(int navigationMode) {
         // In gesture mode, we already do region sampling to update tint based on content beneath.
-        return !QuickStepContract.isGesturalMode(navigationMode);
+        return !QuickStepContract.isGesturalMode(navigationMode)
+                || mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 02473f2..3684d90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -109,8 +109,9 @@
             {
                 int1 = subId
                 str1 = displayInfo.toString()
+                bool1 = displayInfo.isRoaming
             },
-            { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" },
+            { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1 isRoaming=$bool1" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index cd68621..f54f7a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -41,6 +41,8 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
@@ -97,6 +99,7 @@
     bgDispatcher: CoroutineDispatcher,
     logger: MobileInputLogger,
     override val tableLogBuffer: TableLogBuffer,
+    flags: FeatureFlags,
     scope: CoroutineScope,
 ) : MobileConnectionRepository {
     init {
@@ -192,9 +195,15 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isRoaming =
-        callbackEvents
-            .mapNotNull { it.onServiceStateChanged }
-            .map { it.serviceState.roaming }
+        if (flags.isEnabled(ROAMING_INDICATOR_VIA_DISPLAY_INFO)) {
+                callbackEvents
+                    .mapNotNull { it.onDisplayInfoChanged }
+                    .map { it.telephonyDisplayInfo.isRoaming }
+            } else {
+                callbackEvents
+                    .mapNotNull { it.onServiceStateChanged }
+                    .map { it.serviceState.roaming }
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val operatorAlphaShort =
@@ -380,6 +389,7 @@
         private val logger: MobileInputLogger,
         private val carrierConfigRepository: CarrierConfigRepository,
         private val mobileMappingsProxy: MobileMappingsProxy,
+        private val flags: FeatureFlags,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
@@ -403,6 +413,7 @@
                 bgDispatcher,
                 logger,
                 mobileLogger,
+                flags,
                 scope,
             )
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index e7fc870..93a92fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1396,7 +1396,7 @@
         // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
         initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
 
-        // Configure UdfpsView to not accept the ACTION_DOWN event
+        // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
@@ -1427,6 +1427,66 @@
     }
 
     @Test
+    public void onTouch_withNewTouchDetection_ignoreAuthPauseIfFingerDown() throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultDown =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+                        0 /* pointerId */, touchData);
+        final TouchProcessorResult processorResultUp =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.UP,
+                        -1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received and touch is within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultDown);
+        MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+        mBiometricExecutor.runAllReady();
+
+        // THEN the down touch is received
+        verify(mInputManager).pilferPointers(any());
+        verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+                anyBoolean());
+
+        // GIVEN that auth is paused
+        when(mUdfpsAnimationViewController.shouldPauseAuth()).thenReturn(true);
+
+        // WHEN ACTION_UP is received and touch is within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUp);
+        event.setAction(ACTION_UP);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
+        mBiometricExecutor.runAllReady();
+        event.recycle();
+
+        // THEN the UP is still received
+        verify(mInputManager).pilferPointers(any());
+        verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(),
+                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+                anyBoolean());
+    }
+
+    @Test
     public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
@@ -1552,53 +1612,6 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer()
-            throws RemoteException {
-        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
-                0L);
-        final TouchProcessorResult processorResultMove =
-                new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
-                        1 /* pointerId */, touchData);
-
-        // Enable new touch detection.
-        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
-
-        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
-        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
-
-        // Configure UdfpsView to accept the ACTION_MOVE event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
-        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-
-        // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        mFgExecutor.runAllReady();
-
-        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
-
-        // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS
-        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
-        when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f);
-
-        // WHEN ACTION_MOVE is received and touch is within sensor
-        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
-                processorResultMove);
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
-        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
-        mBiometricExecutor.runAllReady();
-        moveEvent.recycle();
-
-        // THEN the touch is NOT processed
-        verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
-                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
-                anyBoolean());
-    }
-
-
-    @Test
     public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible()
             throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 3af444a..27fd3b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -165,12 +165,28 @@
         int maxBrightness = 3;
         when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
                 eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness);
+        when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                eq(UserHandle.USER_CURRENT)))
+                .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         assertEquals(maxBrightness, mServiceFake.screenBrightness);
     }
 
     @Test
+    public void testAod_usesLightSensorNotClampingToAutoBrightnessValue() {
+        int maxBrightness = 3;
+        when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness);
+        when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
+                eq(UserHandle.USER_CURRENT)))
+                .thenReturn(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
+    }
+
+    @Test
     public void doze_doesNotUseLightSensor() {
         // GIVEN the device is DOZE and the display state changes to ON
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 67587e3..37df93e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -373,6 +373,30 @@
         verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any());
     }
 
+    @Test
+    public void testNullBindingCallsUnbind() {
+        Context mockContext = mock(Context.class);
+        // Binding has to succeed
+        when(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mockContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mExecutor);
+
+        manager.executeSetBindService(true);
+        mExecutor.runAllReady();
+
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mockContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any());
+
+        captor.getValue().onNullBinding(mTileServiceComponentName);
+        mExecutor.runAllReady();
+        verify(mockContext).unbindService(captor.getValue());
+    }
+
     private void mockChangeEnabled(long changeId, boolean enabled) {
         doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                 any(UserHandle.class)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index ede02d1..ee48734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -23,6 +23,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -66,6 +68,8 @@
 class FullMobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: FullMobileConnectionRepository
 
+    private val flags = FakeFeatureFlags().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     private val systemClock = FakeSystemClock()
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -685,6 +689,7 @@
                 testDispatcher,
                 logger = mock(),
                 tableLogBuffer,
+                flags,
                 testScope.backgroundScope,
             )
         whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3af960b..a4698f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -27,7 +27,9 @@
 import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
 import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
@@ -58,6 +60,8 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -103,6 +107,8 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    private val flags = FakeFeatureFlags().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
@@ -147,6 +153,7 @@
                 testDispatcher,
                 logger,
                 tableLogger,
+                flags,
                 testScope.backgroundScope,
             )
     }
@@ -599,8 +606,78 @@
         }
 
     @Test
-    fun roaming_gsm_queriesServiceState() =
+    fun roaming_gsm_queriesDisplayInfo_viaDisplayInfo() =
         testScope.runTest {
+            // GIVEN flag is true
+            flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+
+            // Re-create the repository, because the flag is read at init
+            underTest =
+                MobileConnectionRepositoryImpl(
+                    SUB_1_ID,
+                    context,
+                    subscriptionModel,
+                    DEFAULT_NAME_MODEL,
+                    SEP,
+                    telephonyManager,
+                    systemUiCarrierConfig,
+                    fakeBroadcastDispatcher,
+                    mobileMappings,
+                    testDispatcher,
+                    logger,
+                    tableLogger,
+                    flags,
+                    testScope.backgroundScope,
+                )
+
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            val cb = getTelephonyCallbackForType<DisplayInfoListener>()
+
+            // CDMA roaming is off, GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            cb.onDisplayInfoChanged(
+                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false)
+            )
+
+            assertThat(latest).isFalse()
+
+            // CDMA roaming is off, GSM roaming is on
+            cb.onDisplayInfoChanged(
+                TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true)
+            )
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun roaming_gsm_queriesDisplayInfo_viaServiceState() =
+        testScope.runTest {
+            // GIVEN flag is false
+            flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false)
+
+            // Re-create the repository, because the flag is read at init
+            underTest =
+                MobileConnectionRepositoryImpl(
+                    SUB_1_ID,
+                    context,
+                    subscriptionModel,
+                    DEFAULT_NAME_MODEL,
+                    SEP,
+                    telephonyManager,
+                    systemUiCarrierConfig,
+                    fakeBroadcastDispatcher,
+                    mobileMappings,
+                    testDispatcher,
+                    logger,
+                    tableLogger,
+                    flags,
+                    testScope.backgroundScope,
+                )
+
             var latest: Boolean? = null
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 852ed20..0939f28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -31,6 +31,8 @@
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -96,6 +98,9 @@
     private lateinit var underTest: MobileConnectionRepositoryImpl
     private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
+    private val flags =
+        FakeFeatureFlags().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
@@ -136,6 +141,7 @@
                 testDispatcher,
                 logger,
                 tableLogger,
+                flags,
                 testScope.backgroundScope,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 6f9764a..77577f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -41,6 +41,8 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -91,6 +93,9 @@
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionsRepositoryImpl
 
+    private val flags =
+        FakeFeatureFlags().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
     private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
@@ -182,6 +187,7 @@
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
                 scope = testScope.backgroundScope,
+                flags = flags,
                 carrierConfigRepository = carrierConfigRepository,
             )
         carrierMergedFactory =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index cf815c2..ec04da7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -50,10 +50,7 @@
     }
 
     fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) =
-        mock<TelephonyDisplayInfo>().also {
-            whenever(it.networkType).thenReturn(networkType)
-            whenever(it.overrideNetworkType).thenReturn(overrideNetworkType)
-        }
+        TelephonyDisplayInfo(networkType, overrideNetworkType)
 
     inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
         val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fdb28ba..5312279 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5232,6 +5232,8 @@
 
     @Override
     public void injectInputEventToInputFilter(InputEvent event) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS,
+                "injectInputEventToInputFilter");
         synchronized (mLock) {
             final long endMillis =
                     SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 34c2548..bfa0428 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -705,8 +705,7 @@
         public PendingIntent requestNotificationAccess(ComponentName component, int userId)
                 throws RemoteException {
             String callingPackage = component.getPackageName();
-            checkCanCallNotificationApi(callingPackage);
-            // TODO: check userId.
+            checkCanCallNotificationApi(callingPackage, userId);
             if (component.flattenToString().length() > MAX_CN_LENGTH) {
                 throw new IllegalArgumentException("Component name is too long.");
             }
@@ -732,7 +731,7 @@
         @Deprecated
         @Override
         public boolean hasNotificationAccess(ComponentName component) throws RemoteException {
-            checkCanCallNotificationApi(component.getPackageName());
+            checkCanCallNotificationApi(component.getPackageName(), getCallingUserId());
             NotificationManager nm = getContext().getSystemService(NotificationManager.class);
             return nm.isNotificationListenerAccessGranted(component);
         }
@@ -946,8 +945,7 @@
             createNewAssociation(userId, packageName, macAddressObj, null, null, false);
         }
 
-        private void checkCanCallNotificationApi(String callingPackage) {
-            final int userId = getCallingUserId();
+        private void checkCanCallNotificationApi(String callingPackage, int userId) {
             enforceCallerIsSystemOr(userId, callingPackage);
 
             if (getCallingUid() == SYSTEM_UID) return;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9555ddc..3bbb62c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5410,7 +5410,20 @@
                 intent = new Intent(Intent.ACTION_MAIN);
             }
             try {
-                target.send(code, intent, resolvedType, allowlistToken, null,
+                if (allowlistToken != null) {
+                    final int callingUid = Binder.getCallingUid();
+                    final String packageName;
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        packageName = AppGlobals.getPackageManager().getNameForUid(callingUid);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target."
+                            + " Calling package: " + packageName + "; intent: " + intent
+                            + "; options: " + options);
+                }
+                target.send(code, intent, resolvedType, null, null,
                         requiredPermission, options);
             } catch (RemoteException e) {
             }
@@ -20121,7 +20134,7 @@
         final long token = Binder.clearCallingIdentity();
 
         try {
-            return mOomAdjuster.mCachedAppOptimizer.isFreezerSupported();
+            return CachedAppOptimizer.isFreezerSupported();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -20249,4 +20262,21 @@
             return index >= 0 && !mMediaProjectionTokenMap.valueAt(index).isEmpty();
         }
     }
+
+    /**
+     * Deal with binder transactions to frozen apps.
+     *
+     * @param debugPid The binder transaction sender
+     * @param code The binder transaction code
+     * @param flags The binder transaction flags
+     * @param err The binder transaction error
+     */
+    @Override
+    public void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err) {
+        final ProcessRecord app;
+        synchronized (mPidsSelfLocked) {
+            app = mPidsSelfLocked.get(debugPid);
+        }
+        mOomAdjuster.mCachedAppOptimizer.binderError(debugPid, app, code, flags, err);
+    }
 }
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
index 9b5f18c..710278d 100644
--- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Handler;
@@ -54,6 +56,7 @@
         setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app));
         setTitle("Waiting For Debugger");
         WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         attrs.setTitle("Waiting For Debugger: " + app.info.processName);
         getWindow().setAttributes(attrs);
     }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index db93323..70ed916 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -52,6 +52,8 @@
 import android.app.ActivityManagerInternal.OomAdjReason;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
+import android.app.ApplicationExitInfo.Reason;
+import android.app.ApplicationExitInfo.SubReason;
 import android.app.IApplicationThread;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -67,6 +69,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.IntArray;
 import android.util.Pair;
@@ -75,6 +78,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderfsStatsReader;
 import com.android.internal.os.ProcLocksReader;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ServiceThread;
@@ -131,6 +135,12 @@
             "freeze_binder_offset";
     @VisibleForTesting static final String KEY_FREEZER_BINDER_THRESHOLD =
             "freeze_binder_threshold";
+    @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_ENABLED =
+            "freeze_binder_callback_enabled";
+    @VisibleForTesting static final String KEY_FREEZER_BINDER_CALLBACK_THROTTLE =
+            "freeze_binder_callback_throttle";
+    @VisibleForTesting static final String KEY_FREEZER_BINDER_ASYNC_THRESHOLD =
+            "freeze_binder_async_threshold";
 
     static final int UNFREEZE_REASON_NONE =
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_NONE;
@@ -269,6 +279,9 @@
     @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
     @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
     @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_THRESHOLD = 1_000;
+    @VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED = true;
+    @VisibleForTesting static final long DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE = 10_000L;
+    @VisibleForTesting static final int DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD = 1_024;
 
     @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
                 Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@@ -311,6 +324,7 @@
     static final int COMPACT_NATIVE_MSG = 5;
     static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
     static final int DEADLOCK_WATCHDOG_MSG = 7;
+    static final int BINDER_ERROR_MSG = 8;
 
     // When free swap falls below this percentage threshold any full (file + anon)
     // compactions will be downgraded to file only compactions to reduce pressure
@@ -407,7 +421,10 @@
                             } else if (KEY_FREEZER_BINDER_ENABLED.equals(name)
                                     || KEY_FREEZER_BINDER_DIVISOR.equals(name)
                                     || KEY_FREEZER_BINDER_THRESHOLD.equals(name)
-                                    || KEY_FREEZER_BINDER_OFFSET.equals(name)) {
+                                    || KEY_FREEZER_BINDER_OFFSET.equals(name)
+                                    || KEY_FREEZER_BINDER_CALLBACK_ENABLED.equals(name)
+                                    || KEY_FREEZER_BINDER_CALLBACK_THROTTLE.equals(name)
+                                    || KEY_FREEZER_BINDER_ASYNC_THRESHOLD.equals(name)) {
                                 updateFreezerBinderState();
                             }
                         }
@@ -479,7 +496,15 @@
     @VisibleForTesting volatile int mFreezerBinderOffset = DEFAULT_FREEZER_BINDER_OFFSET;
     @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mFreezerBinderThreshold = DEFAULT_FREEZER_BINDER_THRESHOLD;
-
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile boolean mFreezerBinderCallbackEnabled =
+            DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mFreezerBinderCallbackThrottle =
+            DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile int mFreezerBinderAsyncThreshold =
+            DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD;
 
     // Handler on which compaction runs.
     @VisibleForTesting
@@ -487,6 +512,7 @@
     private Handler mFreezeHandler;
     @GuardedBy("mProcLock")
     private boolean mFreezerOverride = false;
+    private long mFreezerBinderCallbackLast = -1;
 
     @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
     @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;
@@ -789,6 +815,12 @@
             pw.println("  " + KEY_FREEZER_BINDER_THRESHOLD + "=" + mFreezerBinderThreshold);
             pw.println("  " + KEY_FREEZER_BINDER_DIVISOR + "=" + mFreezerBinderDivisor);
             pw.println("  " + KEY_FREEZER_BINDER_OFFSET + "=" + mFreezerBinderOffset);
+            pw.println("  " + KEY_FREEZER_BINDER_CALLBACK_ENABLED + "="
+                    + mFreezerBinderCallbackEnabled);
+            pw.println("  " + KEY_FREEZER_BINDER_CALLBACK_THROTTLE + "="
+                    + mFreezerBinderCallbackThrottle);
+            pw.println("  " + KEY_FREEZER_BINDER_ASYNC_THRESHOLD + "="
+                    + mFreezerBinderAsyncThreshold);
             synchronized (mProcLock) {
                 int size = mFrozenProcesses.size();
                 pw.println("  Apps frozen: " + size);
@@ -1308,10 +1340,22 @@
         mFreezerBinderThreshold = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                 KEY_FREEZER_BINDER_THRESHOLD, DEFAULT_FREEZER_BINDER_THRESHOLD);
+        mFreezerBinderCallbackEnabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+                KEY_FREEZER_BINDER_CALLBACK_ENABLED, DEFAULT_FREEZER_BINDER_CALLBACK_ENABLED);
+        mFreezerBinderCallbackThrottle = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+                KEY_FREEZER_BINDER_CALLBACK_THROTTLE, DEFAULT_FREEZER_BINDER_CALLBACK_THROTTLE);
+        mFreezerBinderAsyncThreshold = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+                KEY_FREEZER_BINDER_ASYNC_THRESHOLD, DEFAULT_FREEZER_BINDER_ASYNC_THRESHOLD);
         Slog.d(TAG_AM, "Freezer binder state set to enabled=" + mFreezerBinderEnabled
                 + ", divisor=" + mFreezerBinderDivisor
                 + ", offset=" + mFreezerBinderOffset
-                + ", threshold=" + mFreezerBinderThreshold);
+                + ", threshold=" + mFreezerBinderThreshold
+                + ", callback enabled=" + mFreezerBinderCallbackEnabled
+                + ", callback throttle=" + mFreezerBinderCallbackThrottle
+                + ", async threshold=" + mFreezerBinderAsyncThreshold);
     }
 
     private boolean parseProcStateThrottle(String procStateThrottleString) {
@@ -2174,6 +2218,21 @@
                         Slog.w(TAG_AM, "Unable to check file locks");
                     }
                 } break;
+                case BINDER_ERROR_MSG: {
+                    IntArray pids = new IntArray();
+                    // Copy the frozen pids to a local array to release mProcLock ASAP
+                    synchronized (mProcLock) {
+                        int size = mFrozenProcesses.size();
+                        for (int i = 0; i < size; i++) {
+                            pids.add(mFrozenProcesses.keyAt(i));
+                        }
+                    }
+
+                    // Check binder errors to frozen processes with a local freezer lock
+                    synchronized (mFreezerLock) {
+                        binderErrorLocked(pids);
+                    }
+                } break;
                 default:
                     return;
             }
@@ -2479,4 +2538,115 @@
                 return UNFREEZE_REASON_NONE;
         }
     }
+
+    /**
+     * Kill a frozen process with a specified reason
+     */
+    public void killProcess(int pid, String reason, @Reason int reasonCode,
+            @SubReason int subReason) {
+        mAm.mHandler.post(() -> {
+            synchronized (mAm) {
+                synchronized (mProcLock) {
+                    ProcessRecord proc = mFrozenProcesses.get(pid);
+                    // The process might have been killed or unfrozen by others
+                    if (proc != null && proc.getThread() != null && !proc.isKilledByAm()) {
+                        proc.killLocked(reason, reasonCode, subReason, true);
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and
+     * kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss
+     * async binder transactions due to kernel binder buffer running out.
+     *
+     * @param debugPid The binder transaction sender
+     * @param app The ProcessRecord of the sender
+     * @param code The binder transaction code
+     * @param flags The binder transaction flags
+     * @param err The binder transaction error
+     */
+    public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) {
+        Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName)
+                + " sent binder code " + code + " with flags " + flags
+                + " to frozen apps and got error " + err);
+
+        // Do nothing if the binder error callback is not enabled.
+        // That means the frozen apps in a wrong state will be killed when they are unfrozen later.
+        if (!mUseFreezer || !mFreezerBinderCallbackEnabled) {
+            return;
+        }
+
+        final long now = SystemClock.uptimeMillis();
+        if (now < mFreezerBinderCallbackLast + mFreezerBinderCallbackThrottle) {
+            Slog.d(TAG_AM, "Too many transaction errors, throttling freezer binder callback.");
+            return;
+        }
+        mFreezerBinderCallbackLast = now;
+
+        // Check all frozen processes in Freezer handler
+        mFreezeHandler.sendEmptyMessage(BINDER_ERROR_MSG);
+    }
+
+    private void binderErrorLocked(IntArray pids) {
+        // PIDs that run out of async binder buffer when being frozen
+        ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();
+
+        for (int i = 0; i < pids.size(); i++) {
+            int current = pids.get(i);
+            try {
+                int freezeInfo = getBinderFreezeInfo(current);
+
+                if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
+                    killProcess(current, "Sync transaction while frozen",
+                            ApplicationExitInfo.REASON_FREEZER,
+                            ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION);
+
+                    // No need to check async transactions in this case
+                    continue;
+                }
+
+                if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
+                    if (pidsAsync != null) {
+                        pidsAsync.add(current);
+                    }
+                    if (DEBUG_FREEZER) {
+                        Slog.w(TAG_AM, "pid " + current
+                                + " received async transactions while frozen");
+                    }
+                }
+            } catch (Exception e) {
+                // The process has died. No need to kill it again.
+                Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
+            }
+        }
+
+        // TODO: when kernel binder driver supports, poll the binder status directly.
+        // Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
+        // only true source for now. The following code checks all frozen PIDs. If any of them
+        // is running out of async binder buffer, kill it. Otherwise it will be killed at a
+        // later time when AMS unfreezes it, which causes race issues.
+        if (pidsAsync == null || pidsAsync.size() == 0) {
+            return;
+        }
+        new BinderfsStatsReader().handleFreeAsyncSpace(
+                // Check if the frozen process has pending async calls
+                pidsAsync::contains,
+
+                // Kill the current process if it's running out of async binder space
+                (current, free) -> {
+                    if (free < mFreezerBinderAsyncThreshold) {
+                        Slog.w(TAG_AM, "pid " + current
+                                + " has " + free + " free async space, killing");
+                        killProcess(current, "Async binder space running out while frozen",
+                                ApplicationExitInfo.REASON_FREEZER,
+                                ApplicationExitInfo.SUBREASON_FREEZER_BINDER_ASYNC_FULL);
+                    }
+                },
+
+                // Log the error if binderfs stats can't be accesses or correctly parsed
+                exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
+    }
 }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 96cca0d..2ca7b6e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1928,24 +1928,26 @@
         EventLog.writeEvent(EventLogTags.UC_SWITCH_USER, targetUserId);
         int currentUserId = getCurrentUserId();
         UserInfo targetUserInfo = getUserInfo(targetUserId);
-        if (targetUserId == currentUserId) {
-            Slogf.i(TAG, "user #" + targetUserId + " is already the current user");
-            return true;
-        }
-        if (targetUserInfo == null) {
-            Slogf.w(TAG, "No user info for user #" + targetUserId);
-            return false;
-        }
-        if (!targetUserInfo.supportsSwitchTo()) {
-            Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
-            return false;
-        }
-        if (FactoryResetter.isFactoryResetting()) {
-            Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress");
-            return false;
-        }
         boolean userSwitchUiEnabled;
         synchronized (mLock) {
+            if (targetUserId == currentUserId && mTargetUserId == UserHandle.USER_NULL) {
+                Slogf.i(TAG, "user #" + targetUserId + " is already the current user");
+                return true;
+            }
+            if (targetUserInfo == null) {
+                Slogf.w(TAG, "No user info for user #" + targetUserId);
+                return false;
+            }
+            if (!targetUserInfo.supportsSwitchTo()) {
+                Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
+                return false;
+            }
+            if (FactoryResetter.isFactoryResetting()) {
+                Slogf.w(TAG, "Cannot switch to User #" + targetUserId
+                        + ": factory reset in progress");
+                return false;
+            }
+
             if (!mInitialized) {
                 Slogf.e(TAG, "Cannot switch to User #" + targetUserId
                         + ": UserController not ready yet");
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 292fc14..51cb950 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -165,7 +165,8 @@
 
     @Override
     public String toString() {
-        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
+        return "type: " + mDeviceType
+                + " internal type: 0x" + Integer.toHexString(mInternalDeviceType)
                 + " addr: " + mDeviceAddress + " bt audio type: "
                 + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
                 + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b9535d6..96d2078 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1250,8 +1250,8 @@
     }
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
-            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1260,8 +1260,8 @@
     }
 
     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
-            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1279,8 +1279,8 @@
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
-            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
-        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1288,6 +1288,11 @@
         mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
     }
 
+    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+            List<AudioDeviceAttributes> devices) {
+        return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices);
+    }
+
     /*package*/ void registerCommunicationDeviceDispatcher(
             @NonNull ICommunicationDeviceDispatcher dispatcher) {
         mCommDevDispatchers.register(dispatcher);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e08fdd6..a1d2e14 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -121,6 +121,26 @@
     }
 
     /**
+     * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink
+     * Bluetooth device and no corresponding entry already exists.
+     * @param ada the device to add if needed
+     */
+    void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) {
+        if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) {
+            return;
+        }
+        synchronized (mDeviceInventoryLock) {
+            if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) {
+                return;
+            }
+            AdiDeviceState ads = new AdiDeviceState(
+                    ada.getType(), ada.getInternalType(), ada.getAddress());
+            mDeviceInventory.put(ads.getDeviceId(), ads);
+        }
+        mDeviceBroker.persistAudioDeviceSettings();
+    }
+
+    /**
      * Adds a new AdiDeviceState or updates the audio device cateogory of the matching
      * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
      * @param deviceState the device to update
@@ -992,8 +1012,8 @@
 
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
-            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
-        mPrefDevDispatchers.register(dispatcher);
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mPrefDevDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1002,8 +1022,8 @@
     }
 
     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
-            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
-        mNonDefDevDispatchers.register(dispatcher);
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mNonDefDevDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1084,8 +1104,8 @@
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
-            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
-        mDevRoleCapturePresetDispatchers.register(dispatcher);
+            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+        mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1414,6 +1434,8 @@
                     updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
                     if (!connect) {
                         purgeDevicesRoles_l();
+                    } else {
+                        addAudioDeviceInInventoryIfNeeded(attributes);
                     }
                 }
                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1702,6 +1724,7 @@
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
 
         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+        addAudioDeviceInInventoryIfNeeded(ada);
     }
 
     static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2006,9 +2029,9 @@
         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
-
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_AVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
@@ -2018,6 +2041,7 @@
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
+        addAudioDeviceInInventoryIfNeeded(ada);
         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                 .set(MediaMetrics.Property.DEVICE,
@@ -2128,6 +2152,7 @@
                             sensorUuid));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
+            addAudioDeviceInInventoryIfNeeded(ada);
         }
 
         if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2462,6 +2487,9 @@
         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
+                if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
@@ -2475,6 +2503,9 @@
         final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
+                if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
@@ -2488,6 +2519,9 @@
         final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; ++i) {
             try {
+                if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
                         capturePreset, role, devices);
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b555a52..c317473 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2851,8 +2851,11 @@
         if (devices == null) {
             return AudioSystem.ERROR;
         }
+
+        devices = retrieveBluetoothAddresses(devices);
+
         final String logString = String.format(
-                "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
+                "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy,
                 devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2908,7 +2911,7 @@
                     status, strategy));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -2923,6 +2926,9 @@
                                                 @NonNull AudioDeviceAttributes device) {
         super.setDeviceAsNonDefaultForStrategy_enforcePermission();
         Objects.requireNonNull(device);
+
+        device = retrieveBluetoothAddress(device);
+
         final String logString = String.format(
                 "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
@@ -2949,6 +2955,9 @@
                                                    AudioDeviceAttributes device) {
         super.removeDeviceAsNonDefaultForStrategy_enforcePermission();
         Objects.requireNonNull(device);
+
+        device = retrieveBluetoothAddress(device);
+
         final String logString = String.format(
                 "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString());
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2983,7 +2992,7 @@
                     status, strategy));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -2996,7 +3005,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -3020,7 +3030,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
@@ -3036,7 +3047,7 @@
     }
 
     /**
-     * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
      */
     public int setPreferredDevicesForCapturePreset(
             int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -3055,6 +3066,8 @@
             return AudioSystem.ERROR;
         }
 
+        devices = retrieveBluetoothAddresses(devices);
+
         final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
                 capturePreset, devices);
         if (status != AudioSystem.SUCCESS) {
@@ -3101,7 +3114,7 @@
                     status, capturePreset));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -3115,7 +3128,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /**
@@ -3135,7 +3149,9 @@
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
         enforceQueryStateOrModifyRoutingPermission();
-        return getDevicesForAttributesInt(attributes, false /* forVolume */);
+
+        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+                getDevicesForAttributesInt(attributes, false /* forVolume */)));
     }
 
     /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes)
@@ -3145,7 +3161,8 @@
      */
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
             @NonNull AudioAttributes attributes) {
-        return getDevicesForAttributesInt(attributes, false /* forVolume */);
+        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+                getDevicesForAttributesInt(attributes, false /* forVolume */)));
     }
 
     /**
@@ -7346,6 +7363,8 @@
         Objects.requireNonNull(device);
         AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
 
+        device = retrieveBluetoothAddress(device);
+
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
                 + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
                 + device.getAddress() + " behavior:"
@@ -7429,6 +7448,8 @@
         // verify parameters
         Objects.requireNonNull(device);
 
+        device = retrieveBluetoothAddress(device);
+
         return getDeviceVolumeBehaviorInt(device);
     }
 
@@ -7503,9 +7524,12 @@
     /**
      * see AudioManager.setWiredDeviceConnectionState()
      */
-    public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+    public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes,
             @ConnectionState int state, String caller) {
         super.setWiredDeviceConnectionState_enforcePermission();
+        Objects.requireNonNull(attributes);
+
+        attributes = retrieveBluetoothAddress(attributes);
 
         if (state != CONNECTION_STATE_CONNECTED
                 && state != CONNECTION_STATE_DISCONNECTED) {
@@ -7546,6 +7570,9 @@
             boolean connected) {
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        device = retrieveBluetoothAddress(device);
+
         mDeviceBroker.setTestDeviceConnectionState(device,
                 connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
         // simulate a routing update from native
@@ -10304,6 +10331,100 @@
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
+    private boolean isBluetoothPrividged() {
+        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH_CONNECT)
+                || Binder.getCallingUid() == Process.SYSTEM_UID;
+    }
+
+    List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) {
+        if (isBluetoothPrividged()) {
+            return devices;
+        }
+
+        List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>();
+        for (AudioDeviceAttributes ada : devices) {
+            if (ada == null) {
+                continue;
+            }
+            checkedDevices.add(retrieveBluetoothAddressUncheked(ada));
+        }
+        return checkedDevices;
+    }
+
+    AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) {
+        if (isBluetoothPrividged()) {
+            return ada;
+        }
+        return retrieveBluetoothAddressUncheked(ada);
+    }
+
+    AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) {
+        Objects.requireNonNull(ada);
+        if (AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+            String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress());
+            for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
+                if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
+                        && (ada.getInternalType() == ads.getInternalDeviceType())
+                        && anonymizedAddress.equals(anonymizeBluetoothAddress(
+                                ads.getDeviceAddress())))) {
+                    continue;
+                }
+                ada.setAddress(ads.getDeviceAddress());
+                break;
+            }
+        }
+        return ada;
+    }
+
+    /**
+     * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
+     * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
+     * @param address Mac address to be anonymized
+     * @return anonymized mac address
+     */
+    static String anonymizeBluetoothAddress(String address) {
+        if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) {
+            return null;
+        }
+        return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
+    }
+
+    private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList(
+                List<AudioDeviceAttributes> devices) {
+        if (isBluetoothPrividged()) {
+            return devices;
+        }
+        return anonymizeAudioDeviceAttributesListUnchecked(devices);
+    }
+
+    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+            List<AudioDeviceAttributes> devices) {
+        List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>();
+        for (AudioDeviceAttributes ada : devices) {
+            anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada));
+        }
+        return anonymizedDevices;
+    }
+
+    private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked(
+            AudioDeviceAttributes ada) {
+        if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+            return ada;
+        }
+        AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
+        res.setAddress(anonymizeBluetoothAddress(ada.getAddress()));
+        return res;
+    }
+
+    private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) {
+        if (isBluetoothPrividged()) {
+            return ada;
+        }
+
+        return anonymizeAudioDeviceAttributesUnchecked(ada);
+    }
+
     //==========================================================================================
 
     // camera sound is forced if any of the resources corresponding to one active SIM
@@ -10351,13 +10472,16 @@
         Objects.requireNonNull(usages);
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
         if (timeOutMs <= 0 || usages.length == 0) {
             throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
         }
         Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs
                 + " usages:" + Arrays.toString(usages));
 
-        if (mDeviceBroker.isDeviceConnected(device)) {
+        if (mDeviceBroker.isDeviceConnected(ada)) {
             // not throwing an exception as there could be a race between a connection (server-side,
             // notification of connection in flight) and a mute operation (client-side)
             Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
@@ -10369,12 +10493,19 @@
                         + mMutingExpectedDevice);
                 throw new IllegalStateException("muteAwaitConnection already in progress");
             }
-            mMutingExpectedDevice = device;
+            mMutingExpectedDevice = ada;
             mMutedUsagesAwaitingConnection = usages;
-            mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+            mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs);
         }
-        dispatchMuteAwaitConnection(cb -> { try {
-            cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes dev = ada;
+                if (!isPrivileged) {
+                    dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+                }
+                cb.dispatchOnMutedUntilConnection(dev, usages);
+            } catch (RemoteException e) { }
+        });
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -10383,7 +10514,7 @@
         super.getMutingExpectedDevice_enforcePermission();
 
         synchronized (mMuteAwaitConnectionLock) {
-            return mMutingExpectedDevice;
+            return anonymizeAudioDeviceAttributes(mMutingExpectedDevice);
         }
     }
 
@@ -10392,6 +10523,9 @@
     public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
         Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
         final int[] mutedUsages;
         synchronized (mMuteAwaitConnectionLock) {
@@ -10401,7 +10535,7 @@
                 Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
                 return;
             }
-            if (!device.equalTypeAddress(mMutingExpectedDevice)) {
+            if (!ada.equalTypeAddress(mMutingExpectedDevice)) {
                 Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
                         + "] but expected device is" + mMutingExpectedDevice);
                 throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
@@ -10411,8 +10545,14 @@
             mMutedUsagesAwaitingConnection = null;
             mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device);
         }
-        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
-                    AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes dev = ada;
+                if (!isPrivileged) {
+                    dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+                }
+                cb.dispatchOnUnmutedEvent(
+                        AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages);
             } catch (RemoteException e) { } });
     }
 
@@ -10426,7 +10566,7 @@
         super.registerMuteAwaitConnectionDispatcher_enforcePermission();
 
         if (register) {
-            mMuteAwaitConnectionDispatchers.register(cb);
+            mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged());
         } else {
             mMuteAwaitConnectionDispatchers.unregister(cb);
         }
@@ -10450,8 +10590,14 @@
             mPlaybackMonitor.cancelMuteAwaitConnection(
                     "checkMuteAwaitConnection device " + device + " connected, unmuting");
         }
-        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
-                AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes ada = device;
+                if (!isPrivileged) {
+                    ada = anonymizeAudioDeviceAttributesUnchecked(device);
+                }
+                cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION,
+                        ada, mutedUsages);
             } catch (RemoteException e) { } });
     }
 
@@ -10471,7 +10617,8 @@
             mMutingExpectedDevice = null;
             mMutedUsagesAwaitingConnection = null;
         }
-        dispatchMuteAwaitConnection(cb -> { try {
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
                 cb.dispatchOnUnmutedEvent(
                         AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
                         timedOutDevice, mutedUsages);
@@ -10479,13 +10626,14 @@
     }
 
     private void dispatchMuteAwaitConnection(
-            java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+            java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) {
         final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
         // lazy initialization as errors unlikely
         ArrayList<IMuteAwaitConnectionCallback> errorList = null;
         for (int i = 0; i < nbDispatchers; i++) {
             try {
-                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i),
+                        (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i));
             } catch (Exception e) {
                 if (errorList == null) {
                     errorList = new ArrayList<>(1);
@@ -13048,6 +13196,9 @@
             @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) {
         Objects.requireNonNull(device, "device must not be null");
         enforceModifyAudioRoutingPermission();
+
+        device = retrieveBluetoothAddress(device);
+
         final String getterKey = "additional_output_device_delay="
                 + device.getInternalType() + "," + device.getAddress(); // "getter" key as an id.
         final String setterKey = getterKey + "," + delayMillis;     // append the delay for setter
@@ -13068,6 +13219,9 @@
     @IntRange(from = 0)
     public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device, "device must not be null");
+
+        device = retrieveBluetoothAddress(device);
+
         final String key = "additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
@@ -13095,6 +13249,9 @@
     @IntRange(from = 0)
     public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device, "device must not be null");
+
+        device = retrieveBluetoothAddress(device);
+
         final String key = "max_additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 115421d..fe4b042 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -467,13 +467,14 @@
                 // If we have a GNSS provider override, add the hardware provider as a standalone
                 // option for use by apps with the correct permission. Note the GNSS HAL can only
                 // support a single client, so mGnssManagerService.getGnssLocationProvider() can
-                // only be installed with a single provider.
+                // only be installed with a single provider. Locations from this provider won't
+                // be reported through the passive provider.
                 LocationProviderManager gnssHardwareManager =
                         new LocationProviderManager(
                                 mContext,
                                 mInjector,
                                 GPS_HARDWARE_PROVIDER,
-                                mPassiveManager,
+                                /*passiveManager=*/ null,
                                 Collections.singletonList(Manifest.permission.LOCATION_HARDWARE));
                 addLocationProviderManager(
                         gnssHardwareManager, mGnssManagerService.getGnssLocationProvider());
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 0176989..e8f78f3 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -118,7 +118,10 @@
 
     protected boolean canSnooze(int numberToSnooze) {
         synchronized (mLock) {
-            if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) {
+            if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT
+                    || (mPersistedSnoozedNotifications.size()
+                    + mPersistedSnoozedNotificationsWithContext.size() + numberToSnooze)
+                    > CONCURRENT_SNOOZE_LIMIT) {
                 return false;
             }
         }
@@ -343,6 +346,9 @@
 
             if (groupSummaryKey != null) {
                 NotificationRecord record = mSnoozedNotifications.remove(groupSummaryKey);
+                String trimmedKey = getTrimmedString(groupSummaryKey);
+                mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
+                mPersistedSnoozedNotifications.remove(trimmedKey);
 
                 if (record != null && !record.isCanceled) {
                     Runnable runnable = () -> {
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index e53c436..ca149c5 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -219,6 +219,7 @@
         case Process.AUDIOSERVER_UID:  // fastcapture, fastmixer
         case Process.CAMERASERVER_UID: // camera high frame rate recording
         case Process.BLUETOOTH_UID:    // Bluetooth audio playback
+        case Process.PHONE_UID:        // phone call
             return true;
         default:
             return false;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 062d797..6a6880a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3706,7 +3706,8 @@
                     if (type == XmlPullParser.START_TAG) {
                         final String name = parser.getName();
                         if (name.equals(TAG_USER)) {
-                            UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+                            UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID),
+                                    mUserVersion);
 
                             if (userData != null) {
                                 synchronized (mUsersLock) {
@@ -4387,7 +4388,7 @@
     }
 
     @GuardedBy({"mPackagesLock"})
-    private UserData readUserLP(int id) {
+    private UserData readUserLP(int id, int userVersion) {
         try (ResilientAtomicFile file = getUserFile(id)) {
             FileInputStream fis = null;
             try {
@@ -4396,19 +4397,19 @@
                     Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
                     return null;
                 }
-                return readUserLP(id, fis);
+                return readUserLP(id, fis, userVersion);
             } catch (Exception e) {
                 // Remove corrupted file and retry.
                 Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
                 file.failRead(fis, e);
-                return readUserLP(id);
+                return readUserLP(id, userVersion);
             }
         }
     }
 
     @GuardedBy({"mPackagesLock"})
     @VisibleForTesting
-    UserData readUserLP(int id, InputStream is) throws IOException,
+    UserData readUserLP(int id, InputStream is, int userVersion) throws IOException,
             XmlPullParserException {
         int flags = 0;
         String userType = null;
@@ -4501,7 +4502,17 @@
                 } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
                     legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) {
-                    localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+                    if (userVersion < 10) {
+                        // Prior to version 10, the local user restrictions were stored as sub tags
+                        // grouped by the user id of the source user. The source is no longer stored
+                        // on versions 10+ as this is now stored in the DevicePolicyEngine.
+                        RestrictionsSet oldLocalRestrictions =
+                                RestrictionsSet.readRestrictions(
+                                    parser, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
+                        localRestrictions = oldLocalRestrictions.mergeAll();
+                    } else {
+                        localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+                    }
                 } else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
                     globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
                 } else if (TAG_ACCOUNT.equals(tag)) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 04f1fac..3caba73 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -700,7 +700,7 @@
     private boolean mCurrentLaunchCanTurnScreenOn = true;
 
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
-    private boolean mLastSurfaceShowing;
+    boolean mLastSurfaceShowing;
 
     /**
      * The activity is opaque and fills the entire space of this task.
@@ -5348,19 +5348,14 @@
                 // Finish should only ever commit visibility=false, so we can check full containment
                 // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
-                if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) {
-                    Slog.e(TAG, "setVisibility=" + visible
-                            + " while transition is not collecting or finishing "
-                            + this + " caller=" + Debug.getCallers(8));
-                    // Force showing the parents because they may be hidden by previous transition.
+                if (!inFinishingTransition) {
                     if (visible) {
-                        final Transaction t = getSyncTransaction();
-                        for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent;
-                                p = p.getParent()) {
-                            if (p.mSurfaceControl != null) {
-                                t.show(p.mSurfaceControl);
-                            }
+                        if (!mDisplayContent.isSleeping() || canShowWhenLocked()) {
+                            mTransitionController.onVisibleWithoutCollectingTransition(this,
+                                    Debug.getCallers(1, 1));
                         }
+                    } else if (!mDisplayContent.isSleeping()) {
+                        Slog.w(TAG, "Set invisible without transition " + this);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index be7d9b6..15a3f93 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -87,9 +87,11 @@
                 activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid()
                         || activityBelowInTask.isUid(mActivityRecord.getUid()));
         if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) {
+            // Set to non-touchable, so the touch events can pass through.
             mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE,
                     InputConfig.NOT_TOUCHABLE);
         } else {
+            // Set to touchable, so it can block by intercepting the touch events.
             mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
         }
         mInputWindowHandleWrapper.setDisplayId(mActivityRecord.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e7c6ff4..7ef90fe 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1374,7 +1374,6 @@
             dc.handleCompleteDeferredRemoval();
         }
         validateKeyguardOcclusion();
-        validateVisibility();
 
         mState = STATE_FINISHED;
         // Rotation change may be deferred while there is a display change transition, so check
@@ -2734,29 +2733,6 @@
         }
     }
 
-    private void validateVisibility() {
-        for (int i = mTargets.size() - 1; i >= 0; --i) {
-            if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
-                return;
-            }
-        }
-        // All modes are CLOSE. The surfaces may be hidden by the animation unexpectedly.
-        // If the window container should be visible, then recover it.
-        mController.mStateValidators.add(() -> {
-            for (int i = mTargets.size() - 1; i >= 0; --i) {
-                final ChangeInfo change = mTargets.get(i);
-                if (!change.mContainer.isVisibleRequested()
-                        || change.mContainer.mSurfaceControl == null) {
-                    continue;
-                }
-                Slog.e(TAG, "Force show for visible " + change.mContainer
-                        + " which may be hidden by transition unexpectedly");
-                change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl);
-                change.mContainer.scheduleAnimation();
-            }
-        });
-    }
-
     /**
      * Returns {@code true} if the transition and the corresponding transaction should be applied
      * on display thread. Currently, this only checks for display rotation change because the order
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a43a6f6..3ad4232 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -951,6 +951,44 @@
         mValidateDisplayVis.clear();
     }
 
+    void onVisibleWithoutCollectingTransition(WindowContainer<?> wc, String caller) {
+        final boolean isPlaying = !mPlayingTransitions.isEmpty();
+        Slog.e(TAG, "Set visible without transition " + wc + " playing=" + isPlaying
+                + " caller=" + caller);
+        if (!isPlaying) {
+            enforceSurfaceVisible(wc);
+            return;
+        }
+        // Update surface visibility after the playing transitions are finished, so the last
+        // visibility won't be replaced by the finish transaction of transition.
+        mStateValidators.add(() -> {
+            if (wc.isVisibleRequested()) {
+                enforceSurfaceVisible(wc);
+            }
+        });
+    }
+
+    private void enforceSurfaceVisible(WindowContainer<?> wc) {
+        if (wc.mSurfaceControl == null) return;
+        wc.getSyncTransaction().show(wc.mSurfaceControl);
+        final ActivityRecord ar = wc.asActivityRecord();
+        if (ar != null) {
+            ar.mLastSurfaceShowing = true;
+        }
+        // Force showing the parents because they may be hidden by previous transition.
+        for (WindowContainer<?> p = wc.getParent(); p != null && p != wc.mDisplayContent;
+                p = p.getParent()) {
+            if (p.mSurfaceControl != null) {
+                p.getSyncTransaction().show(p.mSurfaceControl);
+                final Task task = p.asTask();
+                if (task != null) {
+                    task.mLastSurfaceShowing = true;
+                }
+            }
+        }
+        wc.scheduleAnimation();
+    }
+
     /**
      * Called when the transition has a complete set of participants for its operation. In other
      * words, it is when the transition is "ready" but is still waiting for participants to draw.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e6ab78d..758a971 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -65,6 +65,7 @@
 import android.os.FactoryTest;
 import android.os.FileUtils;
 import android.os.IBinder;
+import android.os.IBinderCallback;
 import android.os.IIncidentManager;
 import android.os.Looper;
 import android.os.Message;
@@ -970,6 +971,14 @@
             }
         }
 
+        // Set binder transaction callback after starting system services
+        Binder.setTransactionCallback(new IBinderCallback() {
+            @Override
+            public void onTransactionError(int pid, int code, int flags, int err) {
+                mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err);
+            }
+        });
+
         // Loop forever.
         Looper.loop();
         throw new RuntimeException("Main thread loop unexpectedly exited");
diff --git a/services/tests/servicestests/res/xml/user_100_v9.xml b/services/tests/servicestests/res/xml/user_100_v9.xml
new file mode 100644
index 0000000..03c08ed
--- /dev/null
+++ b/services/tests/servicestests/res/xml/user_100_v9.xml
@@ -0,0 +1,20 @@
+<user id="100"
+    serialNumber="0"
+    flags="3091"
+    type="android.os.usertype.full.SYSTEM"
+    created="0"
+    lastLoggedIn="0"
+    lastLoggedInFingerprint="0"
+    profileBadge="0">
+  <restrictions no_oem_unlock="true" />
+  <device_policy_local_restrictions>
+    <restrictions_user user_id="0">
+      <restrictions no_camera="true" />
+    </restrictions_user>
+    <restrictions_user user_id="100">
+      <restrictions no_camera="true" />
+      <restrictions no_install_unknown_sources="true" />
+    </restrictions_user>
+  </device_policy_local_restrictions>
+  <ignorePrepareStorageErrors>false</ignorePrepareStorageErrors>
+</user>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 9f75cf8..429c58e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -43,27 +43,33 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.res.Resources;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.text.TextUtils;
+import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.frameworks.servicestests.R;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerService.UserData;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -76,6 +82,7 @@
 @MediumTest
 public class UserManagerServiceUserInfoTest {
     private UserManagerService mUserManagerService;
+    private Resources mResources;
 
     @Before
     public void setup() {
@@ -95,6 +102,8 @@
         assertEquals("Multiple users so this test can't run.", 1, users.size());
         assertEquals("Only user present isn't the system user.",
                 UserHandle.USER_SYSTEM, users.get(0).id);
+
+        mResources = InstrumentationRegistry.getTargetContext().getResources();
     }
 
     @Test
@@ -108,7 +117,7 @@
         byte[] bytes = baos.toByteArray();
 
         UserData read = mUserManagerService.readUserLP(
-                data.info.id, new ByteArrayInputStream(bytes));
+                data.info.id, new ByteArrayInputStream(bytes), 0);
 
         assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
     }
@@ -135,7 +144,11 @@
         // Clear the restrictions to see if they are properly read in from the user file.
         setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
 
-        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes));
+        final int userVersion = 10;
+        //read the secondary and SYSTEM user file to fetch local/global device policy restrictions.
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes),
+                userVersion);
+
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
         assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction));
     }
@@ -286,6 +299,45 @@
         assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
     }
 
+    /** Tests readUserLP upgrading from version 9 to 10+. */
+    @Test
+    public void testUserRestrictionsUpgradeFromV9() throws Exception {
+        final String[] localRestrictions = new String[] {
+            UserManager.DISALLOW_CAMERA,
+            UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+        };
+
+        final int userId = 100;
+        UserData data = new UserData();
+        data.info = createUser(userId, FLAG_FULL, "A type");
+
+        mUserManagerService.putUserInfo(data.info);
+
+        for (String restriction : localRestrictions) {
+            assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+            assertFalse(mUserManagerService.hasUserRestriction(restriction, userId));
+        }
+
+        // Convert the xml resource to the system storage xml format.
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DataOutputStream os = new DataOutputStream(baos);
+        XmlPullParser in = mResources.getXml(R.xml.user_100_v9);
+        XmlSerializer out = Xml.newBinarySerializer();
+        out.setOutput(os, StandardCharsets.UTF_8.name());
+        Xml.copy(in, out);
+        byte[] userBytes = baos.toByteArray();
+        baos.reset();
+
+        final int userVersion = 9;
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(userBytes),
+                userVersion);
+
+        for (String restriction : localRestrictions) {
+            assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+            assertTrue(mUserManagerService.hasUserRestriction(restriction, userId));
+        }
+    }
+
     /** Creates a UserInfo with the given flags and userType. */
     private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) {
         return new UserInfo(userId, "A Name", "A path", flags, userType);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 51b9c17..22c7f9c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -18,6 +18,8 @@
 import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
 import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -72,6 +74,14 @@
 public class SnoozeHelperTest extends UiServiceTestCase {
     private static final String TEST_CHANNEL_ID = "test_channel_id";
 
+    private static final String XML_TAG_NAME = "snoozed-notifications";
+    private static final String XML_SNOOZED_NOTIFICATION = "notification";
+    private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
+    private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
+    private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
+    private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id";
+    private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version";
+
     @Mock SnoozeHelper.Callback mCallback;
     @Mock AlarmManager mAm;
     @Mock ManagedServices.UserProfiles mUserProfiles;
@@ -315,6 +325,53 @@
     }
 
     @Test
+    public void testSnoozeLimit_maximumPersisted() throws XmlPullParserException, IOException {
+        final long snoozeTimeout = 1234;
+        final String snoozeContext = "ctx";
+        // Serialize & deserialize notifications so that only persisted lists are used
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        serializer.startTag(null, XML_TAG_NAME);
+        // Serialize maximum number of timed + context snoozed notifications, half of each
+        for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++) {
+            final boolean timedNotification = i % 2 == 0;
+            if (timedNotification) {
+                serializer.startTag(null, XML_SNOOZED_NOTIFICATION);
+            } else {
+                serializer.startTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
+            }
+            serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, 1);
+            serializer.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, "key" + i);
+            if (timedNotification) {
+                serializer.attributeLong(null, XML_SNOOZED_NOTIFICATION_TIME, snoozeTimeout);
+                serializer.endTag(null, XML_SNOOZED_NOTIFICATION);
+            } else {
+                serializer.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, snoozeContext);
+                serializer.endTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
+            }
+        }
+        serializer.endTag(null, XML_TAG_NAME);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+        mSnoozeHelper.readXml(parser, 1);
+        // Verify that we can't snooze any more notifications
+        //  and that the limit is caused by persisted notifications
+        assertThat(mSnoozeHelper.canSnooze(1)).isFalse();
+        assertThat(mSnoozeHelper.isSnoozed(UserHandle.USER_SYSTEM, "pkg", "key0")).isFalse();
+        assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM,
+                "pkg", "key0")).isEqualTo(snoozeTimeout);
+        assertThat(
+            mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
+                "key1")).isEqualTo(snoozeContext);
+    }
+
+    @Test
     public void testCancelByApp() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
@@ -587,6 +644,7 @@
 
     @Test
     public void repostGroupSummary_repostsSummary() throws Exception {
+        final int snoozeDuration = 1000;
         IntArray profileIds = new IntArray();
         profileIds.add(UserHandle.USER_SYSTEM);
         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
@@ -594,10 +652,14 @@
                 "pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
         NotificationRecord r2 = getNotificationRecord(
                 "pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
-        mSnoozeHelper.snooze(r, 1000);
-        mSnoozeHelper.snooze(r2, 1000);
+        final long snoozeTime = System.currentTimeMillis() + snoozeDuration;
+        mSnoozeHelper.snooze(r, snoozeDuration);
+        mSnoozeHelper.snooze(r2, snoozeDuration);
         assertEquals(2, mSnoozeHelper.getSnoozed().size());
         assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+        // Verify that summary notification was added to the persisted list
+        assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
+                r.getKey())).isAtLeast(snoozeTime);
 
         mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
 
@@ -606,6 +668,39 @@
 
         assertEquals(1, mSnoozeHelper.getSnoozed().size());
         assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+        // Verify that summary notification was removed from the persisted list
+        assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
+                r.getKey())).isEqualTo(0);
+    }
+
+    @Test
+    public void snoozeWithContext_repostGroupSummary_removesPersisted() throws Exception {
+        final String snoozeContext = "zzzzz";
+        IntArray profileIds = new IntArray();
+        profileIds.add(UserHandle.USER_SYSTEM);
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+        NotificationRecord r = getNotificationRecord(
+                "pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
+        NotificationRecord r2 = getNotificationRecord(
+                "pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
+        mSnoozeHelper.snooze(r, snoozeContext);
+        mSnoozeHelper.snooze(r2, snoozeContext);
+        assertEquals(2, mSnoozeHelper.getSnoozed().size());
+        assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+        // Verify that summary notification was added to the persisted list
+        assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
+            "pkg", r.getKey())).isEqualTo(snoozeContext);
+
+        mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
+
+        verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
+        verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false);
+
+        assertEquals(1, mSnoozeHelper.getSnoozed().size());
+        assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+        // Verify that summary notification was removed from the persisted list
+        assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
+                "pkg", r.getKey())).isNull();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3eed0b7..185f5ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1179,10 +1179,12 @@
     @Test
     public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
         registerTestTransitionPlayer();
+        spyOn(mRootWindowContainer.mTransitionController);
+        final ActivityRecord bottomActivity = createActivityWithTask();
+        bottomActivity.setVisibility(false);
+        bottomActivity.setState(STOPPED, "test");
+        bottomActivity.mLastSurfaceShowing = false;
         final ActivityRecord activity = createActivityWithTask();
-        // Put an activity on top of test activity to make it invisible and prevent us from
-        // accidentally resuming the topmost one again.
-        new ActivityBuilder(mAtm).build();
         activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
@@ -1190,6 +1192,14 @@
 
         verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
         assertFalse(activity.inTransition());
+
+        // finishIfPossible -> completeFinishing -> addToFinishingAndWaitForIdle
+        // -> resumeFocusedTasksTopActivities
+        assertTrue(bottomActivity.isState(RESUMED));
+        assertTrue(bottomActivity.isVisible());
+        verify(mRootWindowContainer.mTransitionController).onVisibleWithoutCollectingTransition(
+                eq(bottomActivity), any());
+        assertTrue(bottomActivity.mLastSurfaceShowing);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7d9160a..2328f79 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3141,6 +3141,15 @@
     public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = "roaming_operator_string_array";
 
     /**
+     * Config to show the roaming indicator (i.e. the "R" icon) from the status bar when roaming.
+     * The roaming indicator will be shown if this is {@code true} and will not be shown if this is
+     * {@code false}.
+     *
+     * @hide
+     */
+    public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool";
+
+    /**
      * URL from which the proto containing the public key of the Carrier used for
      * IMSI encryption will be downloaded.
      * @hide
@@ -3306,11 +3315,11 @@
      * If {@code false} the SPN display checks if the current MCC/MNC is different from the
      * SIM card's MCC/MNC.
      *
-     * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
-     * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
-     * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
-     * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
-     * @see KEY_FORCE_HOME_NETWORK_BOOL
+     * @see #KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+     * @see #KEY_ROAMING_OPERATOR_STRING_ARRAY
+     * @see #KEY_FORCE_HOME_NETWORK_BOOL
      *
      * @hide
      */
@@ -3334,12 +3343,42 @@
     /**
      * Determines whether we should show a notification when the phone established a data
      * connection in roaming network, to warn users about possible roaming charges.
+     *
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY
      * @hide
      */
     public static final String KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL =
             "show_data_connected_roaming_notification";
 
     /**
+     * Determines what MCCs are exceptions for the value of
+     * {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}.
+     * An empty list indicates that there are no exceptions.
+     *
+     * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY
+     * @hide
+     */
+    public static final String
+            KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY =
+            "data_connected_roaming_notification_excluded_mccs_string_array";
+
+    /**
+     * Determines what MCC+MNCs are exceptions for the MCCs specified in
+     * {@link #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY}, meaning the
+     * value for the MCC+MNC is {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}.
+     * An empty list indicates that there are no MNC-specific exceptions.
+     *
+     * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL
+     * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY
+     * @hide
+     */
+    public static final String
+            KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY =
+            "data_connected_roaming_notification_included_mcc_mncs_string_array";
+
+    /**
      * A list of 4 LTE RSRP thresholds above which a signal level is considered POOR,
      * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
      *
@@ -10171,6 +10210,7 @@
                 false);
         sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true);
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false);
         sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true);
@@ -10207,6 +10247,11 @@
         sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
         sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
+        sDefaults.putStringArray(KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY,
+                new String[0]);
+        sDefaults.putStringArray(
+                KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY,
+                new String[0]);
         sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
                 // Boundaries: [-140 dBm, -44 dBm]
                 new int[] {