Merge "Enlarge mLast4FrameMetricsInfos from 4 to 6 entries" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index baf142a..c7df662 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9875,6 +9875,7 @@
     field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
     field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
     field public static final int RESULT_OK = -1; // 0xffffffff
+    field @FlaggedApi("android.companion.association_failure_code") public static final int RESULT_SECURITY_ERROR = 4; // 0x4
     field public static final int RESULT_USER_REJECTED = 1; // 0x1
   }
 
@@ -9884,7 +9885,7 @@
     method public void onAssociationPending(@NonNull android.content.IntentSender);
     method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
     method public abstract void onFailure(@Nullable CharSequence);
-    method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int);
+    method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int, @Nullable CharSequence);
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 965e3c4..ba1dc56 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4334,11 +4334,13 @@
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
     @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    @UserHandleAware
     public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) {
         throwIfParentInstance("getContentProtectionPolicy");
         if (mService != null) {
             try {
-                return mService.getContentProtectionPolicy(admin, mContext.getPackageName());
+                return mService.getContentProtectionPolicy(admin, mContext.getPackageName(),
+                        myUserId());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c393a9e..d4e5c99 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -621,7 +621,7 @@
     void calculateHasIncompatibleAccounts();
 
     void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
-    int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
+    int getContentProtectionPolicy(in ComponentName who, String callerPackageName, int userId);
 
     int[] getSubscriptionIds(String callerPackageName);
 
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index a50425e..db3de62 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -40,8 +40,8 @@
                 public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) {
                     String targetPackageName = parcel.readString8();
                     String functionIdentifier = parcel.readString8();
-                    GenericDocument parameters;
-                    parameters = GenericDocument.createFromParcel(parcel);
+                    GenericDocumentWrapper parameters = GenericDocumentWrapper
+                            .CREATOR.createFromParcel(parcel);
                     Bundle extras = parcel.readBundle(Bundle.class.getClassLoader());
                     return new ExecuteAppFunctionRequest(
                             targetPackageName, functionIdentifier, extras, parameters);
@@ -75,17 +75,17 @@
      *
      * <p>The document may have missing parameters. Developers are advised to implement defensive
      * handling measures.
-     *
+     * <p>
      * TODO(b/357551503): Document how function parameters can be obtained for function execution
      */
     @NonNull
-    private final GenericDocument mParameters;
+    private final GenericDocumentWrapper mParameters;
 
     private ExecuteAppFunctionRequest(
             @NonNull String targetPackageName,
             @NonNull String functionIdentifier,
             @NonNull Bundle extras,
-            @NonNull GenericDocument parameters) {
+            @NonNull GenericDocumentWrapper parameters) {
         mTargetPackageName = Objects.requireNonNull(targetPackageName);
         mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
         mExtras = Objects.requireNonNull(extras);
@@ -117,7 +117,7 @@
      */
     @NonNull
     public GenericDocument getParameters() {
-        return mParameters;
+        return mParameters.getValue();
     }
 
     /**
@@ -152,7 +152,8 @@
         @NonNull
         private Bundle mExtras = Bundle.EMPTY;
         @NonNull
-        private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+        private GenericDocument mParameters =
+                new GenericDocument.Builder<>("", "", "").build();
 
         public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
             mTargetPackageName = Objects.requireNonNull(targetPackageName);
@@ -173,7 +174,8 @@
          */
         @NonNull
         public Builder setParameters(@NonNull GenericDocument parameters) {
-            mParameters = Objects.requireNonNull(parameters);
+            Objects.requireNonNull(parameters);
+            mParameters = parameters;
             return this;
         }
 
@@ -183,7 +185,8 @@
         @NonNull
         public ExecuteAppFunctionRequest build() {
             return new ExecuteAppFunctionRequest(
-                    mTargetPackageName, mFunctionIdentifier, mExtras, mParameters);
+                    mTargetPackageName, mFunctionIdentifier, mExtras,
+                    new GenericDocumentWrapper(mParameters));
         }
     }
 }
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 872327d..9fb3375 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -41,13 +41,16 @@
             new Creator<ExecuteAppFunctionResponse>() {
                 @Override
                 public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
-                    GenericDocument result =
-                            Objects.requireNonNull(GenericDocument.createFromParcel(parcel));
+                    GenericDocumentWrapper resultWrapper =
+                            Objects.requireNonNull(
+                                    GenericDocumentWrapper
+                                            .CREATOR.createFromParcel(parcel));
                     Bundle extras = Objects.requireNonNull(
                             parcel.readBundle(Bundle.class.getClassLoader()));
                     int resultCode = parcel.readInt();
                     String errorMessage = parcel.readString8();
-                    return new ExecuteAppFunctionResponse(result, extras, resultCode, errorMessage);
+                    return new ExecuteAppFunctionResponse(
+                            resultWrapper, extras, resultCode, errorMessage);
                 }
 
                 @Override
@@ -127,7 +130,7 @@
      * <p>See {@link #getResultDocument} for more information on extracting the return value.
      */
     @NonNull
-    private final GenericDocument mResultDocument;
+    private final GenericDocumentWrapper mResultDocumentWrapper;
 
     /**
      * Returns the additional metadata data relevant to this function execution response.
@@ -135,17 +138,30 @@
     @NonNull
     private final Bundle mExtras;
 
-    private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+    private ExecuteAppFunctionResponse(@NonNull GenericDocumentWrapper resultDocumentWrapper,
                                        @NonNull Bundle extras,
                                        @ResultCode int resultCode,
                                        @Nullable String errorMessage) {
-        mResultDocument = Objects.requireNonNull(resultDocument);
+        mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
         mExtras = Objects.requireNonNull(extras);
         mResultCode = resultCode;
         mErrorMessage = errorMessage;
     }
 
     /**
+     * Returns result codes from throwable.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    static @ResultCode int getResultCode(@NonNull Throwable t) {
+        if (t instanceof IllegalArgumentException) {
+            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+        }
+        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+    }
+
+    /**
      * Returns a generic document containing the return value of the executed function.
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
@@ -166,7 +182,7 @@
      */
     @NonNull
     public GenericDocument getResultDocument() {
-        return mResultDocument;
+        return mResultDocumentWrapper.getValue();
     }
 
     /**
@@ -210,7 +226,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        mResultDocument.writeToParcel(dest, flags);
+        mResultDocumentWrapper.writeToParcel(dest, flags);
         dest.writeBundle(mExtras);
         dest.writeInt(mResultCode);
         dest.writeString8(mErrorMessage);
@@ -236,24 +252,13 @@
     }
 
     /**
-     * Returns result codes from throwable.
-     *
-     * @hide
-     */
-    static @ResultCode int getResultCode(@NonNull Throwable t) {
-        if (t instanceof IllegalArgumentException) {
-            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
-        }
-        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
-    }
-
-    /**
      * The builder for creating {@link ExecuteAppFunctionResponse} instances.
      */
     @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
     public static final class Builder {
         @NonNull
-        private GenericDocument mResultDocument = new GenericDocument.Builder<>("", "", "").build();
+        private GenericDocument mResultDocument =
+                new GenericDocument.Builder<>("", "", "").build();
         @NonNull
         private Bundle mExtras = Bundle.EMPTY;
         private int mResultCode;
@@ -271,7 +276,8 @@
          * with a result code of {@link #RESULT_OK} and a resultDocument.
          */
         public Builder(@NonNull GenericDocument resultDocument) {
-            mResultDocument = Objects.requireNonNull(resultDocument);
+            Objects.requireNonNull(resultDocument);
+            mResultDocument = resultDocument;
             mResultCode = RESULT_OK;
         }
 
@@ -300,7 +306,8 @@
         @NonNull
         public ExecuteAppFunctionResponse build() {
             return new ExecuteAppFunctionResponse(
-                    mResultDocument, mExtras, mResultCode, mErrorMessage);
+                    new GenericDocumentWrapper(mResultDocument),
+                    mExtras, mResultCode, mErrorMessage);
         }
     }
 }
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
new file mode 100644
index 0000000..8c76c8e
--- /dev/null
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.app.appsearch.GenericDocument;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * The Parcelable object contains a {@link GenericDocument} to allow the parcelization of it
+ * exceeding the binder limit.
+ *
+ * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
+ * directly or Android shared memory if the data is large.
+ *
+ * @hide
+ * @see Parcel#writeBlob(byte[])
+ */
+public final class GenericDocumentWrapper implements Parcelable {
+    public static final Creator<GenericDocumentWrapper> CREATOR =
+            new Creator<>() {
+                @Override
+                public GenericDocumentWrapper createFromParcel(Parcel in) {
+                    byte[] dataBlob = Objects.requireNonNull(in.readBlob());
+                    Parcel unmarshallParcel = Parcel.obtain();
+                    try {
+                        unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
+                        unmarshallParcel.setDataPosition(0);
+                        return new GenericDocumentWrapper(
+                                GenericDocument.createFromParcel(unmarshallParcel));
+                    } finally {
+                        unmarshallParcel.recycle();
+                    }
+                }
+
+                @Override
+                public GenericDocumentWrapper[] newArray(int size) {
+                    return new GenericDocumentWrapper[size];
+                }
+            };
+    @NonNull
+    private final GenericDocument mGenericDocument;
+
+    public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
+        mGenericDocument = Objects.requireNonNull(genericDocument);
+    }
+
+    /**
+     * Returns the wrapped {@link android.app.appsearch.GenericDocument}
+     */
+    @NonNull
+    public GenericDocument getValue() {
+        return mGenericDocument;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            mGenericDocument.writeToParcel(parcel, flags);
+            byte[] bytes = parcel.marshall();
+            dest.writeBlob(bytes);
+        } finally {
+            parcel.recycle();
+        }
+
+    }
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1529842..1cdf3b1 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,7 +21,6 @@
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
 
-import static java.util.Collections.unmodifiableMap;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
@@ -58,7 +57,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
-import android.util.ArrayMap;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -78,7 +76,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
@@ -146,12 +143,19 @@
     /**
      * The result code to propagate back to the user activity, indicates the internal error
      * in CompanionDeviceManager.
-     * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the
-     * {@link AssociationInfo}.
      */
     public static final int RESULT_INTERNAL_ERROR = 3;
 
     /**
+     * The result code to propagate back to the user activity and
+     * {@link Callback#onFailure(int, CharSequence)}, indicates app is not allow to create the
+     * association due to the security issue.
+     * E.g. There are missing necessary permissions when creating association.
+     */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
+    public static final int RESULT_SECURITY_ERROR = 4;
+
+    /**
      * Requesting applications will receive the String in {@link Callback#onFailure} if the
      * association dialog is explicitly declined by the users. E.g. press the Don't allow
      * button.
@@ -374,7 +378,6 @@
          */
         public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
 
-        //TODO(b/331459560): Add deprecated and remove abstract after API cut for W.
         /**
          * Invoked if the association could not be created.
          *
@@ -385,11 +388,15 @@
         /**
          * Invoked if the association could not be created.
          *
-         * @param resultCode indicate the particular reason why the association
-         *                   could not be created.
+         * Please note that both {@link #onFailure(CharSequence error)} and this
+         * API will be called if the association could not be created.
+         *
+         * @param errorCode indicate the particular error code why the association
+         *                  could not be created.
+         * @param error error message.
          */
         @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
-        public void onFailure(@ResultCode int resultCode) {}
+        public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {}
     }
 
     private final ICompanionDeviceManager mService;
@@ -1825,12 +1832,12 @@
         }
 
         @Override
-        public void onFailure(@ResultCode int resultCode) {
+        public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {
             if (Flags.associationFailureCode()) {
-                execute(mCallback::onFailure, resultCode);
+                execute(mCallback::onFailure, errorCode, error);
             }
 
-            execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode));
+            execute(mCallback::onFailure, error);
         }
 
         private <T> void execute(Consumer<T> callback, T arg) {
@@ -1840,6 +1847,12 @@
                 mHandler.post(() -> callback.accept(arg));
             }
         }
+
+        private <T, U> void execute(BiConsumer<T, U> callback, T arg1, U arg2) {
+            if (mExecutor != null) {
+                mExecutor.execute(() -> callback.accept(arg1, arg2));
+            }
+        }
     }
 
     private static class OnAssociationsChangedListenerProxy
@@ -2014,15 +2027,4 @@
             }
         }
     }
-
-    private static final Map<Integer, String> RESULT_CODE_TO_REASON;
-    static {
-        final Map<Integer, String> map = new ArrayMap<>();
-        map.put(RESULT_CANCELED, REASON_CANCELED);
-        map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
-        map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
-        map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR);
-
-        RESULT_CODE_TO_REASON = unmodifiableMap(map);
-    }
 }
diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
index b1be30a..a6f86a5 100644
--- a/core/java/android/companion/IAssociationRequestCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -25,5 +25,5 @@
 
     oneway void onAssociationCreated(in AssociationInfo associationInfo);
 
-    oneway void onFailure(in int resultCode);
+    oneway void onFailure(in int errorCode, in CharSequence error);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index cd3ce87..5779a44 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -2501,33 +2501,19 @@
         return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
     }
 
-    private void addIndentOrComma(StringBuilder sb, String indent) {
-        if (indent != null) {
-            sb.append("\n  ");
-            sb.append(indent);
-        } else {
-            sb.append(", ");
-        }
+    /** @hide */
+    public String toSimpleString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(mId);
+        addReadableFlags(sb);
+        return sb.toString();
     }
 
-    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
-        final StringBuilder sb = new StringBuilder();
-
-        if (indent != null) {
-            sb.append(indent);
-        }
-
-        sb.append("ShortcutInfo {");
-
-        sb.append("id=");
-        sb.append(secure ? "***" : mId);
-
-        sb.append(", flags=0x");
-        sb.append(Integer.toHexString(mFlags));
+    private void addReadableFlags(StringBuilder sb) {
         sb.append(" [");
         if ((mFlags & FLAG_SHADOW) != 0) {
-            // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
-            // we don't have an isXxx for this.
+            // Note the shadow flag isn't actually used anywhere and it's
+            // just for dumpsys, so we don't have an isXxx for this.
             sb.append("Sdw");
         }
         if (!isEnabled()) {
@@ -2576,7 +2562,32 @@
             sb.append("Hid-L");
         }
         sb.append("]");
+    }
 
+    private void addIndentOrComma(StringBuilder sb, String indent) {
+        if (indent != null) {
+            sb.append("\n  ");
+            sb.append(indent);
+        } else {
+            sb.append(", ");
+        }
+    }
+
+    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
+        final StringBuilder sb = new StringBuilder();
+
+        if (indent != null) {
+            sb.append(indent);
+        }
+
+        sb.append("ShortcutInfo {");
+
+        sb.append("id=");
+        sb.append(secure ? "***" : mId);
+
+        sb.append(", flags=0x");
+        sb.append(Integer.toHexString(mFlags));
+        addReadableFlags(sb);
         addIndentOrComma(sb, indent);
 
         sb.append("packageName=");
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 7665fe8..04a810a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -461,6 +461,12 @@
         @SuppressLint("NonUserGetterCalled")
         public boolean registerClient(Context ctx, IBinder token, int extension,
                 String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+            if (!SystemProperties.getBoolean("ro.camerax.extensions.enabled",
+                    /*default*/ false)) {
+                Log.v(TAG, "Disabled camera extension property!");
+                return false;
+            }
+
             boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
 
             if (Flags.concertMode()) {
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 8bd6c85..c38ee08 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -3,6 +3,7 @@
 [email protected]
 [email protected]
 [email protected]
[email protected]
 
 per-file *NetworkSecurityPolicy.java = file:net/OWNERS
 per-file Confirmation*.java = file:/keystore/OWNERS
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1734223..ad457ce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -25,6 +25,7 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.flags.Flags.disableDrawWakeLock;
 
 import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
 import static com.android.window.flags.Flags.noDuplicateSurfaceDestroyedEvents;
@@ -51,6 +52,7 @@
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -208,6 +210,15 @@
     private boolean mIsWearOs;
 
     /**
+     * This change disables the {@code DRAW_WAKE_LOCK}, an internal wakelock acquired per-frame
+     * duration display DOZE. It was added to allow animation during AOD. This wakelock consumes
+     * battery severely if the animation is too heavy, so, it will be removed.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private static final long DISABLE_DRAW_WAKE_LOCK_WALLPAPER = 361433696L;
+
+    /**
      * Wear products currently force a slight scaling transition to wallpapers
      * when the QSS is opened. However, on Wear 6 (SDK 35) and above, 1P watch faces
      * will be expected to either implement their own scaling, or to override this
@@ -362,6 +373,8 @@
         private SurfaceControl mScreenshotSurfaceControl;
         private Point mScreenshotSize = new Point();
 
+        private final boolean mDisableDrawWakeLock;
+
         final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
             {
                 mRequestedFormat = PixelFormat.RGBX_8888;
@@ -406,6 +419,9 @@
             }
 
             private void prepareToDraw() {
+                if (mDisableDrawWakeLock) {
+                    return;
+                }
                 if (mDisplayState == Display.STATE_DOZE) {
                     try {
                         mSession.pokeDrawLock(mWindow);
@@ -546,6 +562,8 @@
         public Engine(Supplier<Long> clockFunction, Handler handler) {
             mClockFunction = clockFunction;
             mHandler = handler;
+            mDisableDrawWakeLock = CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK_WALLPAPER)
+                    && disableDrawWakeLock();
         }
 
         /**
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 1535145..815fd1c 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -556,12 +556,9 @@
                                 + "is a different type from the others. All frames should be the "
                                 + "same type.");
                     }
-                    if (drawableFrame.getIntrinsicWidth() != width ||
-                        drawableFrame.getIntrinsicHeight() != height) {
-                        throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
-                                + "is different. All frames should have the exact same size and "
-                                + "share the same hotspot.");
-                    }
+                    // TODO(b/361232935): Check when bitmap size of the ith frame is different
+                    // drawableFrame.getIntrinsicWidth() != width ||
+                    // drawableFrame.getIntrinsicHeight() != height
                     if (isVectorAnimation) {
                         drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
                                 (VectorDrawable) drawableFrame, pointerScale);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1922327..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,7 +83,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.system.Os;
 import android.text.TextUtils;
@@ -6247,18 +6246,6 @@
 
     private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
             @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
-        try {
-            Trace.beginSection(rv.hasDrawInstructions()
-                    ? "RemoteViews#inflateViewWithDrawInstructions"
-                    : "RemoteViews#inflateView");
-            return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
-            @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
@@ -6397,7 +6384,7 @@
 
         private View mResult;
         private ViewTree mTree;
-        private List<Action> mActions;
+        private Action[] mActions;
         private Exception mError;
 
         private AsyncApplyTask(
@@ -6424,20 +6411,11 @@
 
                 if (mRV.mActions != null) {
                     int count = mRV.mActions.size();
-                    mActions = new ArrayList<>(count);
-                    try {
-                        Trace.beginSection(hasDrawInstructions()
-                                ? "RemoteViews#initActionAsyncWithDrawInstructions"
-                                : "RemoteViews#initActionAsync");
-                        for (Action action : mRV.mActions) {
-                            if (isCancelled()) {
-                                break;
-                            }
-                            // TODO: check if isCancelled in nested views.
-                            mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
-                        }
-                    } finally {
-                        Trace.endSection();
+                    mActions = new Action[count];
+                    for (int i = 0; i < count && !isCancelled(); i++) {
+                        // TODO: check if isCancelled in nested views.
+                        mActions[i] = mRV.mActions.get(i)
+                                .initActionAsync(mTree, mParent, mApplyParams);
                     }
                 } else {
                     mActions = null;
@@ -6459,7 +6437,14 @@
 
                 try {
                     if (mActions != null) {
-                        mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
+
+                        ActionApplyParams applyParams = mApplyParams.clone();
+                        if (applyParams.handler == null) {
+                            applyParams.handler = DEFAULT_INTERACTION_HANDLER;
+                        }
+                        for (Action a : mActions) {
+                            a.apply(viewTree.mRoot, mParent, applyParams);
+                        }
                     }
                     // If the parent of the view is has is a root, resolve the recycling.
                     if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6635,11 +6620,6 @@
     }
 
     private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
-        performApply(v, parent, params, mActions);
-    }
-
-    private void performApply(
-            View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
         params = params.clone();
         if (params.handler == null) {
             params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6650,15 +6630,8 @@
         }
         if (mActions != null) {
             final int count = mActions.size();
-            try {
-                Trace.beginSection(hasDrawInstructions()
-                        ? "RemoteViews#applyActionsWithDrawInstructions"
-                        : "RemoteViews#applyActions");
-                for (int i = 0; i < count; i++) {
-                    mActions.get(i).apply(v, parent, params);
-                }
-            } finally {
-                Trace.endSection();
+            for (int i = 0; i < count; i++) {
+                mActions.get(i).apply(v, parent, params);
             }
         }
     }
diff --git a/core/java/com/android/internal/accessibility/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING
index 1c67399..b2b3041 100644
--- a/core/java/com/android/internal/accessibility/TEST_MAPPING
+++ b/core/java/com/android/internal/accessibility/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    },
+    {
+      "path": "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING"
     }
   ]
 }
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 7bfb800..1204ef3 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -218,8 +218,25 @@
      */
     public static final int CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE = 117;
 
+    /**
+     * Track attempting to snap resize a desktop window via button or drag.
+     *
+     * <p>CUJ has 3 different tags:
+     * <ul>
+     *     <li>snap resizing resizable apps via maximize menu button: maximize_menu_resizable </li>
+     *     <li>snap resizing resizable via drag: drag_resizable </li>
+     *     <li>snap resizing non-resizable via drag: drag_non_resizable </li>
+     * </ul>
+     *
+     * <p>For non-resizable apps, the desktop window won't actually be resized, instead will return
+     * to its pre-dragged position. Attempting to snap resize a non-resizable app via the
+     * maximize menu will just result in no change, and a toast explaining the app can't be resized.
+     *
+     */
+    public static final int CUJ_DESKTOP_MODE_SNAP_RESIZE = 118;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_SNAP_RESIZE;
 
     /** @hide */
     @IntDef({
@@ -328,7 +345,8 @@
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
             CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
-            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
+            CUJ_DESKTOP_MODE_SNAP_RESIZE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -448,6 +466,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
     }
 
     private Cuj() {
@@ -678,6 +697,8 @@
                 return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE";
             case CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE:
                 return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE";
+            case CUJ_DESKTOP_MODE_SNAP_RESIZE:
+                return "DESKTOP_MODE_SNAP_RESIZE";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 1c6c6a7..656d4c7 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -211,6 +211,7 @@
                         if (mInstance != 0) {
                             native_replySimple(mInstance, unique, FUSE_OK);
                         }
+                        mCallbackMap.remove(checkInode(inode));
                         mBytesMap.stopUsing(inode);
                         recycleLocked(args);
                     }
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 2feb3d5..8771cde 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -29,6 +29,7 @@
 import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
 import static com.android.internal.protolog.ProtoLogMessage.STR_PARAMS;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -49,6 +50,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -419,6 +421,12 @@
         return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
     }
 
+    @Override
+    @NonNull
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return mLogGroups.values().stream().toList();
+    }
+
     public void registerGroups(IProtoLogGroup... protoLogGroups) {
         for (IProtoLogGroup group : protoLogGroups) {
             mLogGroups.put(group.name(), group);
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index e8d5195..b82c660 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.protolog.ProtoLog.REQUIRE_PROTOLOGTOOL;
 
+import android.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -26,6 +27,9 @@
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Class only create and used to server temporarily for when there is source code pre-processing by
  * the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
@@ -79,4 +83,10 @@
     public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
         return true;
     }
+
+    @Override
+    @NonNull
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return Collections.emptyList();
+    }
 }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 49ed55d..5517967 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -306,6 +306,12 @@
                 || group.isLogToLogcat();
     }
 
+    @Override
+    @NonNull
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return mLogGroups.values().stream().toList();
+    }
+
     private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
         final var groupsLoggingToLogcat = new ArrayList<String>();
         for (IProtoLogGroup protoLogGroup : protoLogGroups) {
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index f9b9894..660d3c9 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -20,6 +20,9 @@
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 /**
  * ProtoLog API - exposes static logging methods. Usage of this API is similar
  * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -49,6 +52,8 @@
 
     private static IProtoLog sProtoLogInstance;
 
+    private static final Object sInitLock = new Object();
+
     /**
      * Initialize ProtoLog in this process.
      * <p>
@@ -59,7 +64,17 @@
      */
     public static void init(IProtoLogGroup... groups) {
         if (android.tracing.Flags.perfettoProtologTracing()) {
-            sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+            synchronized (sInitLock) {
+                if (sProtoLogInstance != null) {
+                    // The ProtoLog instance has already been initialized in this process
+                    final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
+                    final var allGroups = new ArrayList<>(alreadyRegisteredGroups);
+                    allGroups.addAll(Arrays.stream(groups).toList());
+                    groups = allGroups.toArray(new IProtoLogGroup[0]);
+                }
+
+                sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+            }
         } else {
             // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
             // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index d5c2ac1..f06f08a 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.protolog.common;
 
+import java.util.List;
+
 /**
  * Interface for ProtoLog implementations.
  */
@@ -68,4 +70,9 @@
      * @return If we need to log this group and level to either ProtoLog or Logcat.
      */
     boolean isEnabled(IProtoLogGroup group, LogLevel level);
+
+    /**
+     * @return an immutable list of the registered ProtoLog groups in this ProtoLog instance.
+     */
+    List<IProtoLogGroup> getRegisteredGroups();
 }
diff --git a/core/res/OWNERS b/core/res/OWNERS
index b2b58d5..5293131 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -78,6 +78,11 @@
 per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
 per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
 
+# Input Method Framework
+per-file res/*/*input_method* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+per-file res/*/*_ime* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+per-file res/*/ime_* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+
 # TV Input Framework
 per-file res/values/config_tv_external_input_logging.xml = file:/services/core/java/com/android/server/tv/OWNERS
 
diff --git a/core/res/res/color/input_method_switch_on_item.xml b/core/res/res/color/input_method_switch_on_item.xml
new file mode 100644
index 0000000..49fe081
--- /dev/null
+++ b/core/res/res/color/input_method_switch_on_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true" android:color="?attr/materialColorOnSecondaryContainer" />
+    <item android:color="?attr/materialColorOnSurface" />
+</selector>
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index 09ed650..10d938c 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -70,6 +70,7 @@
                 android:ellipsize="marquee"
                 android:singleLine="true"
                 android:fontFamily="google-sans-text"
+                android:textColor="@color/input_method_switch_on_item"
                 android:textAppearance="?attr/textAppearanceListItem"/>
 
         </LinearLayout>
@@ -81,7 +82,7 @@
             android:gravity="center_vertical"
             android:layout_marginStart="12dp"
             android:src="@drawable/ic_check_24dp"
-            android:tint="?attr/materialColorOnSurface"
+            android:tint="@color/input_method_switch_on_item"
             android:visibility="gone"
             android:importantForAccessibility="no"/>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index de7477e..b6468ee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3080,6 +3080,11 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
+         system will be booted with the headless system user, or user 0. It has no effect if device
+         is not in Headless System User Mode (HSUM). -->
+    <bool name="config_bootToHeadlessSystemUser">false</bool>
+
     <!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
          with admin privileges and admin privileges can be granted/revoked from existing users. -->
     <bool name="config_enableMultipleAdmins">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0d16e9c..9a52bd4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -363,6 +363,7 @@
   <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_enableMultipleAdmins"/>
+  <java-symbol type="bool" name="config_bootToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_omnipresentCommunalUser"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 434885f..47d5274 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -22,10 +22,14 @@
 import com.android.window.flags.Flags
 
 /*
- * A shared class to check desktop mode flags state.
+ * An enum to check desktop mode flags state.
  *
- * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
- * value and the developer option override state (if applicable).
+ * This enum provides a centralized way to control the behavior of flags related to desktop
+ * windowing features which are aiming for developer preview before their release. It allows
+ * developer option to override the default behavior of these flags.
+ *
+ * NOTE: Flags should only be added to this enum when they have received Product and UX
+ * alignment that the feature is ready for developer preview, otherwise just do a flag check.
  */
 enum class DesktopModeFlags(
     // Function called to obtain aconfig flag value.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 8ce7837..17869e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -19,8 +19,6 @@
 import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
@@ -40,6 +38,7 @@
 import com.android.wm.shell.compatui.api.CompatUIEvent;
 import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.util.function.Consumer;
 
@@ -83,7 +82,7 @@
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
-        if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+        if (DesktopModeStatus.canEnterDesktopMode(context)
                 && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
             // Don't show the SCM button for freeform tasks
             mHasSizeCompat &= !taskInfo.isFreeform();
@@ -139,7 +138,7 @@
             boolean canShow) {
         final boolean prevHasSizeCompat = mHasSizeCompat;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
-        if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)
                 && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) {
             // Don't show the SCM button for freeform tasks
             mHasSizeCompat &= !taskInfo.isFreeform();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
new file mode 100644
index 0000000..a489c4f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger
+
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+
+/** Providers for various WmShell-specific coroutines-related constructs. */
+@Module
+class WMShellCoroutinesModule {
+  @Provides
+  @ShellMainThread
+  fun provideMainDispatcher(@ShellMainThread mainExecutor: ShellExecutor): CoroutineDispatcher =
+      mainExecutor.asCoroutineDispatcher()
+
+  @Provides
+  @ShellBackgroundThread
+  fun provideBackgroundDispatcher(
+      @ShellBackgroundThread backgroundExecutor: ShellExecutor
+  ): CoroutineDispatcher = backgroundExecutor.asCoroutineDispatcher()
+
+  @Provides
+  @WMSingleton
+  @ShellMainThread
+  fun provideApplicationScope(
+      @ShellMainThread applicationDispatcher: CoroutineDispatcher,
+  ): CoroutineScope = CoroutineScope(applicationDispatcher)
+
+  @Provides
+  @WMSingleton
+  @ShellBackgroundThread
+  fun provideBackgroundCoroutineScope(
+      @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher,
+  ): CoroutineScope = CoroutineScope(backgroundDispatcher)
+
+  @Provides
+  @WMSingleton
+  @ShellBackgroundThread
+  fun provideBackgroundCoroutineContext(
+      @ShellBackgroundThread backgroundDispatcher: CoroutineDispatcher
+  ): CoroutineContext = backgroundDispatcher + SupervisorJob()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index b6f2a25..02cbe01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -132,7 +132,8 @@
             sessionId,
             taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
             taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
-
+            /* visible_task_count */
+            taskUpdate.visibleTaskCount
         )
     }
 
@@ -159,6 +160,7 @@
             val taskY: Int,
             val minimizeReason: MinimizeReason? = null,
             val unminimizeReason: UnminimizeReason? = null,
+            val visibleTaskCount: Int,
         )
 
         // Default value used when the task was not minimized.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 2e36ffe..336e5e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -295,7 +295,8 @@
         postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
     ) {
         postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
-            val currentTaskUpdate = buildTaskUpdateForTask(taskInfo)
+            val currentTaskUpdate = buildTaskUpdateForTask(taskInfo,
+                postTransitionVisibleFreeformTasks.size())
             val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
             when {
                 // new tasks added
@@ -309,15 +310,17 @@
                 }
                 // old tasks that were resized or repositioned
                 // TODO(b/347935387): Log changes only once they are stable.
-                buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate ->
-                    desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
+                buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size())
+                        != currentTaskUpdate ->
+                            desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
             }
         }
 
         // find old tasks that were removed
         preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
             if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
-                desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+                desktopModeEventLogger.logTaskRemoved(sessionId,
+                    buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()))
                 Trace.setCounter(
                     Trace.TRACE_TAG_WINDOW_MANAGER,
                     VISIBLE_TASKS_COUNTER_NAME,
@@ -327,7 +330,7 @@
         }
     }
 
-    private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+    private fun buildTaskUpdateForTask(taskInfo: TaskInfo, visibleTasks: Int): TaskUpdate {
         val screenBounds = taskInfo.configuration.windowConfiguration.bounds
         val positionInParent = taskInfo.positionInParent
         return TaskUpdate(
@@ -337,6 +340,7 @@
             taskWidth = screenBounds.width(),
             taskX = positionInParent.x,
             taskY = positionInParent.y,
+            visibleTaskCount = visibleTasks,
         )
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index c97066a..ffd534b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -53,6 +53,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.internal.protolog.ProtoLog
@@ -322,7 +323,7 @@
             logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
             return false
         }
-        logV("moveBackgroundTaskToDesktop with taskId=%d, displayId=%d", taskId)
+        logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
         // TODO(342378842): Instead of using default display, support multiple displays
         val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
             DEFAULT_DISPLAY, wct, taskId)
@@ -693,6 +694,10 @@
     ) {
         releaseVisualIndicator()
         if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
+            interactionJankMonitor.begin(
+                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+            )
+
             // reposition non-resizable app back to its original position before being dragged
             returnToDragStartAnimator.start(
                 taskInfo.taskId,
@@ -701,6 +706,9 @@
                 endBounds = dragStartBounds
             )
         } else {
+            interactionJankMonitor.begin(
+                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+            )
             snapToHalfScreen(taskInfo, currentDragBounds, position)
         }
     }
@@ -1104,12 +1112,11 @@
                 addMoveToDesktopChanges(wct, task)
                 // In some launches home task is moved behind new task being launched. Make sure
                 // that's not the case for launches in desktop.
-                moveHomeTask(wct, toTop = false)
-                // Move existing minimized tasks behind Home
-                taskRepository.getFreeformTasksInZOrder(task.displayId)
-                    .filter { taskId -> taskRepository.isMinimizedTask(taskId) }
-                    .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
-                    .forEach { taskInfo -> wct.reorder(taskInfo.token, /* onTop= */ false) }
+                if (task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0) {
+                    bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+                    wct.reorder(task.token, true)
+                }
+
                 // Desktop Mode is already showing and we're launching a new Task - we might need to
                 // minimize another Task.
                 val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index 24a7d77..4c5258f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -24,6 +24,7 @@
 import android.view.SurfaceControl
 import android.widget.Toast
 import androidx.core.animation.addListener
+import com.android.internal.jank.Cuj
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.R
 import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
@@ -85,7 +86,7 @@
                                 R.string.desktop_mode_non_resizable_snap_text,
                                 Toast.LENGTH_SHORT
                             ).show()
-                            // TODO(b/339582583) - add Jank CUJ using interactionJankMonitor
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
                         }
                     )
                     addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index bf185a4..96719fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -117,8 +117,8 @@
                             finishCallback.onTransitionFinished(null)
                             initialBounds = null
                             boundsAnimator = null
-                            interactionJankMonitor.end(
-                                Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
                         }
                     )
                     addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 7ba6ec4..b102e40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -41,6 +41,7 @@
 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
 import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_CLEANUP_PIP_EXIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
@@ -122,6 +123,8 @@
     @Nullable
     private IBinder mMoveToBackTransition;
     private IBinder mRequestedEnterTransition;
+    private IBinder mCleanupTransition;
+
     private WindowContainerToken mRequestedEnterTask;
     /** The Task window that is currently in PIP windowing mode. */
     @Nullable
@@ -232,10 +235,12 @@
 
         // Exiting PIP.
         final int type = info.getType();
-        if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)) {
+        if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)
+                || transition.equals(mCleanupTransition)) {
             mExitDestinationBounds.setEmpty();
             mExitTransition = null;
             mMoveToBackTransition = null;
+            mCleanupTransition = null;
             mHasFadeOut = false;
             if (mFinishCallback != null) {
                 callFinishCallback(null /* wct */);
@@ -269,6 +274,9 @@
                     removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
                             pipTaskInfo);
                     break;
+                case TRANSIT_CLEANUP_PIP_EXIT:
+                    cleanupPipExitTransition(startTransaction, finishCallback);
+                    break;
                 default:
                     throw new IllegalStateException("mExitTransition with unexpected transit type="
                             + transitTypeToString(type));
@@ -768,7 +776,19 @@
                 mPipAnimationController.resetAnimatorState();
                 finishTransaction.remove(pipLeash);
             }
-            finishCallback.onTransitionFinished(wct);
+
+            if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+                // TODO(b/358226697): start a new transition with the WCT instead of applying it in
+                //  the {@link finishCallback}, to ensure shell creates a transition for it.
+                finishCallback.onTransitionFinished(wct);
+            } else {
+                // Apply wct in separate transition so that it can be correctly handled by the
+                // {@link FreeformTaskTransitionObserver} when desktop windowing (which does not
+                // utilize fixed rotation transitions for exiting pip) is enabled (See b/288910069).
+                mCleanupTransition = mTransitions.startTransition(
+                        TRANSIT_CLEANUP_PIP_EXIT, wct, this);
+                finishCallback.onTransitionFinished(null);
+            }
         };
         mFinishTransaction = finishTransaction;
 
@@ -914,6 +934,16 @@
         finishCallback.onTransitionFinished(null);
     }
 
+    /**
+     * For {@link Transitions#TRANSIT_CLEANUP_PIP_EXIT} which applies final config changes needed
+     * after the exit from pip transition animation finishes.
+     */
+    private void cleanupPipExitTransition(@NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null);
+    }
+
     /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
     private boolean isEnteringPip(@NonNull TransitionInfo info) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f0d3668..7dc336b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -193,6 +193,9 @@
     /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */
     public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18;
 
+    /** Transition to set windowing mode after exit pip transition is finished animating. */
+    public static final int TRANSIT_CLEANUP_PIP_EXIT = WindowManager.TRANSIT_FIRST_CUSTOM + 19;
+
     /** Transition type for desktop mode transitions. */
     public static final int TRANSIT_DESKTOP_MODE_TYPES =
             WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b1cb834..20a406f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -480,6 +480,8 @@
             Toast.makeText(mContext,
                     R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
         } else {
+            mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+                    Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
             mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
                     decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
                     left ? SnapPosition.LEFT : SnapPosition.RIGHT);
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index e3660fe..b812c59 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -25,6 +25,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
@@ -38,15 +39,20 @@
 @RunWith(BlockJUnit4ClassRunner::class)
 @Postsubmit
 open class MaximizeAppWindow
-{
+@JvmOverloads
+constructor(rotation: Rotation = Rotation.ROTATION_0, isResizable: Boolean = true) {
+
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
     private val wmHelper = WindowManagerStateHelper(instrumentation)
     private val device = UiDevice.getInstance(instrumentation)
-    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
 
-    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
-        Rotation.ROTATION_0)
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index 63e7387..03d970f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -25,6 +25,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
@@ -40,15 +41,24 @@
 @Postsubmit
 open class ResizeAppWithCornerResize
 @JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0,
+constructor(
+    val rotation: Rotation = Rotation.ROTATION_0,
     val horizontalChange: Int = 50,
-    val verticalChange: Int = -50) {
+    val verticalChange: Int = -50,
+    val appProperty: AppProperty = AppProperty.STANDARD
+) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
     private val wmHelper = WindowManagerStateHelper(instrumentation)
     private val device = UiDevice.getInstance(instrumentation)
-    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val testApp =
+        DesktopModeAppHelper(
+            when (appProperty) {
+                AppProperty.STANDARD -> SimpleAppHelper(instrumentation)
+                AppProperty.NON_RESIZABLE -> NonResizeableAppHelper(instrumentation)
+            }
+        )
 
     @Rule
     @JvmField
@@ -64,15 +74,24 @@
 
     @Test
     open fun resizeAppWithCornerResize() {
-        testApp.cornerResize(wmHelper,
+        testApp.cornerResize(
+            wmHelper,
             device,
             DesktopModeAppHelper.Corners.RIGHT_TOP,
             horizontalChange,
-            verticalChange)
+            verticalChange
+        )
     }
 
     @After
     fun teardown() {
         testApp.exit(wmHelper)
     }
+
+    companion object {
+        enum class AppProperty {
+            STANDARD,
+            NON_RESIZABLE
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
new file mode 100644
index 0000000..685a3ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SnapResizeAppWindowWithButton
+@JvmOverloads
+constructor(private val toLeft: Boolean = true, private val isResizable: Boolean = true) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun snapResizeAppWindowWithButton() {
+        testApp.snapResizeDesktopApp(wmHelper, device, instrumentation.context, toLeft)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
new file mode 100644
index 0000000..8a4aa63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SnapResizeAppWindowWithDrag
+@JvmOverloads
+constructor(private val toLeft: Boolean = true, private val isResizable: Boolean = true) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun snapResizeAppWindowWithDrag() {
+        testApp.dragToSnapResizeRegion(wmHelper, device, toLeft)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 70b3661..ca97229 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -104,7 +104,9 @@
                 /* session_id */
                 eq(SESSION_ID),
                 eq(UNSET_MINIMIZE_REASON),
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -131,7 +133,9 @@
                 /* session_id */
                 eq(SESSION_ID),
                 eq(UNSET_MINIMIZE_REASON),
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -159,7 +163,9 @@
                 /* session_id */
                 eq(SESSION_ID),
                 eq(UNSET_MINIMIZE_REASON),
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -190,7 +196,9 @@
                 /* minimize_reason */
                 eq(MinimizeReason.TASK_LIMIT.reason),
                 /* unminimize_reason */
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -221,7 +229,9 @@
                 /* minimize_reason */
                 eq(UNSET_MINIMIZE_REASON),
                 /* unminimize_reason */
-                eq(UnminimizeReason.TASKBAR_TAP.reason))
+                eq(UnminimizeReason.TASKBAR_TAP.reason),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -233,15 +243,17 @@
         private const val TASK_Y = 0
         private const val TASK_HEIGHT = 100
         private const val TASK_WIDTH = 100
+        private const val TASK_COUNT = 1
 
         private val TASK_UPDATE = TaskUpdate(
-            TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
+            TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
+            visibleTaskCount = TASK_COUNT,
         )
 
         private fun createTaskUpdate(
             minimizeReason: MinimizeReason? = null,
             unminimizeReason: UnminimizeReason? = null,
         ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
-            unminimizeReason)
+            unminimizeReason, TASK_COUNT)
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 74fc7b0..e49eb36 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -138,7 +138,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -152,7 +153,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -165,7 +167,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -178,7 +181,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -191,7 +195,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -201,7 +206,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -231,7 +237,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -261,7 +268,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -291,7 +299,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -324,7 +333,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -335,7 +345,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -345,7 +356,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -364,7 +376,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -386,7 +399,8 @@
             .build()
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -522,7 +536,8 @@
     val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
     callOnTransitionReady(transitionInfo2)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -538,7 +553,8 @@
     callOnTransitionReady(transitionInfo)
 
     verify(desktopModeEventLogger, times(1))
-        .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
+        .logTaskAdded(eq(sessionId),
+            eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2)))
     verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
   }
 
@@ -560,7 +576,8 @@
 
     verify(desktopModeEventLogger, times(1))
         .logTaskInfoChanged(
-            eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+            eq(sessionId),
+            eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)))
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
@@ -589,7 +606,8 @@
             eq(sessionId),
             eq(
                 DEFAULT_TASK_UPDATE.copy(
-                    taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+                    taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100,
+                    visibleTaskCount = 1)))
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
@@ -613,7 +631,8 @@
 
     verify(desktopModeEventLogger, times(1))
         .logTaskInfoChanged(
-            eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+            eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(
+                taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)))
     verifyZeroInteractions(desktopModeEventLogger)
 
     // task 2 resize
@@ -637,7 +656,9 @@
                 DEFAULT_TASK_UPDATE.copy(
                     instanceId = 2,
                     taskWidth = DEFAULT_TASK_WIDTH + 100,
-                    taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+                    taskHeight = DEFAULT_TASK_HEIGHT - 100,
+                    visibleTaskCount = 2)),
+            )
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
@@ -655,7 +676,8 @@
     callOnTransitionReady(transitionInfo)
 
     verify(desktopModeEventLogger, times(1))
-        .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
+        .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(
+            instanceId = 2, visibleTaskCount = 1)))
     verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
   }
 
@@ -694,6 +716,7 @@
     const val DEFAULT_TASK_WIDTH = 200
     const val DEFAULT_TASK_X = 30
     const val DEFAULT_TASK_Y = 70
+    const val DEFAULT_VISIBLE_TASK_COUNT = 0
     val DEFAULT_TASK_UPDATE =
         TaskUpdate(
             DEFAULT_TASK_ID,
@@ -702,6 +725,7 @@
             DEFAULT_TASK_WIDTH,
             DEFAULT_TASK_X,
             DEFAULT_TASK_Y,
+            visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT,
         )
 
     fun createTaskInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2e0af273..058a26a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1515,8 +1515,34 @@
     assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
         .isEqualTo(WINDOWING_MODE_FREEFORM)
 
-    assertThat(wct.hierarchyOps).hasSize(2)
-    wct.assertReorderAt(1, homeTask, toTop = false)
+    assertThat(wct.hierarchyOps).hasSize(1)
+  }
+
+  @Test
+  fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val homeTask = setUpHomeTask()
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+    val fullscreenTask = createFullscreenTask()
+    fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+    val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+    assertNotNull(wct, "should handle request")
+    assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+      .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+    // There are 5 hops that are happening in this case:
+    // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+    // 2. Bringing home task to front
+    // 3. Pending intent for the wallpaper
+    // 4. Bringing the existing freeform task to top
+    // 5. Bringing the fullscreen task back at the top
+    assertThat(wct.hierarchyOps).hasSize(5)
+    wct.assertReorderAt(1, homeTask, toTop = true)
+    wct.assertReorderAt(4, fullscreenTask, toTop = true)
   }
 
   @Test
@@ -1541,19 +1567,34 @@
     val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val fullscreenTask = createFullscreenTask()
-    val homeTask = setUpHomeTask(DEFAULT_DISPLAY)
 
     val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
 
     // Make sure we reorder the new task to top, and the back task to the bottom
-    assertThat(wct!!.hierarchyOps.size).isEqualTo(3)
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
     wct.assertReorderAt(0, fullscreenTask, toTop = true)
-    wct.assertReorderAt(1, homeTask, toTop = false)
-    wct.assertReorderAt(2, freeformTasks[0], toTop = false)
+    wct.assertReorderAt(1, freeformTasks[0], toTop = false)
   }
 
   @Test
-  fun handleRequest_fullscreenTaskToFreeform_alreadyBeyondLimit_existingAndNewTasksAreMinimized() {
+  fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+    freeformTasks.forEach { markTaskVisible(it) }
+    val fullscreenTask = createFullscreenTask()
+    fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+    val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+    // Make sure we reorder the new task to top, and the back task to the bottom
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+    wct.assertReorderAt(0, fullscreenTask, toTop = true)
+    wct.assertReorderAt(8, freeformTasks[0], toTop = false)
+  }
+
+  @Test
+  fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
     val minimizedTask = setUpFreeformTask()
@@ -1562,15 +1603,16 @@
     freeformTasks.forEach { markTaskVisible(it) }
     val homeTask = setUpHomeTask()
     val fullscreenTask = createFullscreenTask()
+    fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
 
     val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
 
-    assertThat(wct!!.hierarchyOps.size).isEqualTo(4)
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
     wct.assertReorderAt(0, fullscreenTask, toTop = true)
-    // Make sure we reorder the home task to the bottom, and minimized tasks below the home task.
-    wct.assertReorderAt(1, homeTask, toTop = false)
-    wct.assertReorderAt(2, minimizedTask, toTop = false)
-    wct.assertReorderAt(3, freeformTasks[0], toTop = false)
+    // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
+    // task is under the home task.
+    wct.assertReorderAt(1, homeTask, toTop = true)
+    wct.assertReorderAt(9, freeformTasks[0], toTop = false)
   }
 
   @Test
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 31c9db7..3a3bfb47 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -106,7 +106,7 @@
 
 private:
     sk_sp<SkColorFilter> createInstance() override {
-        return SkColorFilters::Matrix(mMatrix.data());
+        return SkColorFilters::Matrix(mMatrix.data(), SkColorFilters::Clamp::kNo);
     }
 
 private:
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 6117330..7974a37 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -19,6 +19,7 @@
 import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
 import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
 import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -33,6 +34,7 @@
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
+import static com.android.companiondevicemanager.Utils.RESULT_CODE_TO_REASON;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -51,6 +53,7 @@
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
+import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -231,7 +234,7 @@
         boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
         if (forCancelDialog) {
             Slog.i(TAG, "Cancelling the user confirmation");
-            cancel(RESULT_CANCELED);
+            cancel(RESULT_CANCELED, null);
             return;
         }
 
@@ -243,10 +246,15 @@
         if (appCallback == null) {
             return;
         }
-        Slog.e(TAG, "More than one AssociationRequests are processing.");
 
         try {
-            appCallback.onFailure(RESULT_INTERNAL_ERROR);
+            if (Flags.associationFailureCode()) {
+                appCallback.onFailure(
+                        RESULT_SECURITY_ERROR, "More than one AssociationRequests are processing.");
+            } else {
+                appCallback.onFailure(
+                        RESULT_INTERNAL_ERROR, "More than one AssociationRequests are processing.");
+            }
         } catch (RemoteException ignore) {
         }
     }
@@ -257,7 +265,7 @@
 
         // TODO: handle config changes without cancelling.
         if (!isDone()) {
-            cancel(RESULT_CANCELED); // will finish()
+            cancel(RESULT_CANCELED, null); // will finish()
         }
     }
 
@@ -331,7 +339,7 @@
                 && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
             synchronized (LOCK) {
                 if (sDiscoveryStarted) {
-                    cancel(RESULT_DISCOVERY_TIMEOUT);
+                    cancel(RESULT_DISCOVERY_TIMEOUT, null);
                 }
             }
         }
@@ -371,7 +379,7 @@
         mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
     }
 
-    private void cancel(int failureCode) {
+    private void cancel(int errorCode, @Nullable CharSequence error) {
         if (isDone()) {
             Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
             return;
@@ -385,13 +393,14 @@
 
         // First send callback to the app directly...
         try {
-            Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode);
-            mAppCallback.onFailure(failureCode);
+            CharSequence errorMessage = error != null
+                    ? error : RESULT_CODE_TO_REASON.get(errorCode);
+            mAppCallback.onFailure(errorCode, errorMessage);
         } catch (RemoteException ignore) {
         }
 
         // ... then set result and finish ("sending" onActivityResult()).
-        setResultAndFinish(null, failureCode);
+        setResultAndFinish(null, errorCode);
     }
 
     private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
@@ -436,7 +445,7 @@
             }
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            cancel(RESULT_INTERNAL_ERROR);
+            cancel(RESULT_INTERNAL_ERROR, e.getMessage());
             return;
         }
 
@@ -625,7 +634,7 @@
         // Disable the button, to prevent more clicks.
         v.setEnabled(false);
 
-        cancel(RESULT_USER_REJECTED);
+        cancel(RESULT_USER_REJECTED, null);
     }
 
     private void onShowHelperDialog(View view) {
@@ -755,8 +764,8 @@
             };
 
     @Override
-    public void onShowHelperDialogFailed() {
-        cancel(RESULT_INTERNAL_ERROR);
+    public void onShowHelperDialogFailed(CharSequence errorMessage) {
+        cancel(RESULT_INTERNAL_ERROR, errorMessage);
     }
 
     @Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index ec92987..b2d78da 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -54,7 +54,7 @@
     private Button mButton;
 
     interface CompanionVendorHelperDialogListener {
-        void onShowHelperDialogFailed();
+        void onShowHelperDialogFailed(CharSequence error);
         void onHelperDialogDismissed();
     }
 
@@ -110,7 +110,7 @@
             appLabel = getApplicationLabel(getContext(), packageName, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            mListener.onShowHelperDialogFailed();
+            mListener.onShowHelperDialogFailed(e.getMessage());
             return;
         }
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index 8c14f80..2f97132 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -16,6 +16,15 @@
 
 package com.android.companiondevicemanager;
 
+import static android.companion.CompanionDeviceManager.REASON_CANCELED;
+import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
+import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
+import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
+
+import static java.util.Collections.unmodifiableMap;
+
 import android.annotation.NonNull;
 import android.annotation.StringRes;
 import android.content.Context;
@@ -31,6 +40,9 @@
 import android.os.ResultReceiver;
 import android.text.Html;
 import android.text.Spanned;
+import android.util.ArrayMap;
+
+import java.util.Map;
 
 /**
  * Utilities.
@@ -40,6 +52,17 @@
             "android.companion.vendor_icon";
     private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_NAME =
             "android.companion.vendor_name";
+    // This map solely the common error messages that occur during the Association
+    // creation process.
+    static final Map<Integer, String> RESULT_CODE_TO_REASON;
+    static {
+        final Map<Integer, String> map = new ArrayMap<>();
+        map.put(RESULT_CANCELED, REASON_CANCELED);
+        map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
+        map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
+
+        RESULT_CODE_TO_REASON = unmodifiableMap(map);
+    }
 
     /**
      * Convert an instance of a "locally-defined" ResultReceiver to an instance of
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2e98c1f..f98b29a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -484,6 +484,7 @@
 
         <activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
             android:exported="true"
+            android:showForAllUsers="true"
             android:screenOrientation="userLandscape"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
@@ -494,6 +495,7 @@
 
         <activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
             android:exported="true"
+            android:showForAllUsers="true"
             android:screenOrientation="userLandscape"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7c0db8d..0b364ac 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -541,6 +541,16 @@
 }
 
 flag {
+    name: "clipboard_shared_transitions"
+    namespace: "systemui"
+    description: "Show shared transitions from clipboard"
+    bug: "360843770"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "clipboard_image_timeout"
     namespace: "systemui"
     description: "Wait for clipboard image to load before showing UI"
@@ -1346,3 +1356,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "face_message_defer_update"
+    namespace: "systemui"
+    description: "Only analyze the last n frames when determining whether to defer a face auth help message like low light"
+    bug: "351863611"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0bef05dc..9e292d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -68,7 +68,6 @@
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
@@ -82,7 +81,6 @@
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
-import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
@@ -137,14 +135,16 @@
                 .notificationHeadsUpHeight(stackScrollView)
                 .debugBackground(viewModel, DEBUG_HUN_COLOR)
                 .onGloballyPositioned { coordinates: LayoutCoordinates ->
+                    val positionInWindow = coordinates.positionInWindow()
                     val boundsInWindow = coordinates.boundsInWindow()
                     debugLog(viewModel) {
                         "HUNS onGloballyPositioned:" +
                             " size=${coordinates.size}" +
                             " bounds=$boundsInWindow"
                     }
-                    // Note: boundsInWindow doesn't scroll off the screen
-                    stackScrollView.setHeadsUpTop(boundsInWindow.top)
+                    // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
+                    // for top bound, which can scroll off screen while snoozing
+                    stackScrollView.setHeadsUpTop(positionInWindow.y)
                     stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                 }
     )
@@ -159,16 +159,10 @@
     stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
 ) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-    val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
-    val headsUpPadding =
-        with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
     val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
 
     var scrollOffset by remember { mutableFloatStateOf(0f) }
-    val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+    val minScrollOffset = -(stackScrollView.getHeadsUpInset().toFloat())
     val maxScrollOffset = 0f
 
     val scrollableState = rememberScrollableState { delta ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index f399436..0555346 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -103,10 +103,10 @@
                 when {
                     isSplitShade -> UnsquishingQS(squishiness)
                     fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> {
-                        Expanding(progress)
+                        Expanding { progress }
                     }
                     fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> {
-                        Collapsing(progress)
+                        Collapsing { progress }
                     }
                     fromScene == Scenes.Shade || toScene == Scenes.Shade -> {
                         UnsquishingQQS(squishiness)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cfe0bec..15a4a40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -25,6 +27,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FaceSensorInfo
@@ -58,7 +61,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -77,6 +82,8 @@
     @Mock private lateinit var securityModel: KeyguardSecurityModel
     @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
     @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
+    @Captor
+    private lateinit var keyguardUpdateMonitorCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
 
     private lateinit var underTest: BouncerMessageInteractor
 
@@ -207,6 +214,52 @@
         }
 
     @Test
+    fun resetMessageBackToDefault_faceAuthRestarts() =
+        testScope.runTest {
+            init()
+            verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+            val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+            underTest.setFaceAcquisitionMessage("not empty")
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+            keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+                BiometricSourceType.FACE,
+                BiometricFaceConstants.FACE_ACQUIRED_START
+            )
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isNull()
+        }
+
+    @Test
+    fun faceRestartDoesNotResetFingerprintMessage() =
+        testScope.runTest {
+            init()
+            verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+            val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+            underTest.setFingerprintAcquisitionMessage("not empty")
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+            keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+                BiometricSourceType.FACE,
+                BiometricFaceConstants.FACE_ACQUIRED_START
+            )
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+        }
+
+    @Test
     fun setFingerprintMessage_propagateValue() =
         testScope.runTest {
             init()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index c5518b0..2ba4bf9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -834,4 +835,86 @@
                     )
                 )
         }
+
+    /**
+     * KTF: LOCKSCREEN -> ALTERNATE_BOUNCER starts but then STL: GLANCEABLE_HUB -> BLANK interrupts.
+     *
+     * Verifies that we correctly cancel the previous KTF state before starting the glanceable hub
+     * transition.
+     */
+    @Test
+    fun transition_to_blank_after_ktf_started_another_transition() =
+        testScope.runTest {
+            // Another transition has already started to the alternate bouncer.
+            keyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    from = LOCKSCREEN,
+                    to = ALTERNATE_BOUNCER,
+                    animator = null,
+                    ownerName = "external",
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                ),
+            )
+
+            val allSteps by collectValues(keyguardTransitionRepository.transitions)
+            // Keep track of existing size to drop any pre-existing steps that we don't
+            // care about.
+            val numToDrop = allSteps.size
+
+            sceneTransitions.value = hubToBlank
+            runCurrent()
+            progress.emit(0.4f)
+            runCurrent()
+            // We land on blank.
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+            // We should cancel the previous ALTERNATE_BOUNCER transition and transition back
+            // to the GLANCEABLE_HUB before we can transition away from it.
+            assertThat(allSteps.drop(numToDrop))
+                .containsExactly(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = ALTERNATE_BOUNCER,
+                        transitionState = CANCELED,
+                        value = 0f,
+                        ownerName = "external",
+                    ),
+                    TransitionStep(
+                        from = ALTERNATE_BOUNCER,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = ALTERNATE_BOUNCER,
+                        to = GLANCEABLE_HUB,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                )
+                .inOrder()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1bc2e24..5a6f2be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1968,47 +1968,6 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToLockscreen_communalKtfRefactor() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-            runCurrent()
-            clearInvocations(transitionRepository)
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            progress.value = .1f
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
     @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDozing() =
         testScope.runTest {
@@ -2260,54 +2219,6 @@
         }
 
     @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToDreaming_communalKtfRefactor() =
-        testScope.runTest {
-            // GIVEN that we are dreaming and not dozing
-            powerInteractor.setAwakeForTest()
-            keyguardRepository.setDreaming(true)
-            keyguardRepository.setDreamingWithOverlay(true)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
-            advanceTimeBy(600L)
-
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-            runCurrent()
-            clearInvocations(transitionRepository)
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = flowOf(0f, 0.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.DREAMING,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
     @BrokenWithSceneContainer(339465026)
     @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6dbe94b..a407468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -360,6 +360,25 @@
         }
 
     @Test
+    fun alpha_transitionBetweenHubAndDream_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            // Default value check
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning between DREAM and HUB but don't finish.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+                throughTransitionState = TransitionState.STARTED,
+            )
+
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
     @EnableSceneContainer
     fun alpha_transitionToHub_isZero_scene_container() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 94fa9b9..e0a53f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -31,9 +31,8 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +43,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -55,12 +53,7 @@
     private val dispatcher = kosmos.testDispatcher
     private val zenModeRepository = kosmos.fakeZenModeRepository
 
-    private val underTest =
-        ModesTileDataInteractor(
-            context,
-            ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
-            dispatcher
-        )
+    private val underTest = ModesTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index d472d98..22913f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -255,7 +255,7 @@
             runCurrent()
             clearInvocations(qsImpl!!)
 
-            underTest.setState(QSSceneAdapter.State.Expanding(progress))
+            underTest.setState(QSSceneAdapter.State.Expanding { progress })
             with(qsImpl!!) {
                 verify(this).setQsVisible(true)
                 verify(this, never())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index 63ce67c..41b5988 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -20,7 +20,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.ui.adapter.ExpandingSubject.Companion.assertThatExpanding
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,33 +37,59 @@
 
     @Test
     fun expanding_squishiness1() {
-        assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness()).isEqualTo(1f)
+        assertThat(QSSceneAdapter.State.Expanding { 0.3f }.squishiness()).isEqualTo(1f)
     }
 
     @Test
     fun expandingSpecialValues() {
-        assertThat(QSSceneAdapter.State.QQS).isEqualTo(QSSceneAdapter.State.Expanding(0f))
-        assertThat(QSSceneAdapter.State.QS).isEqualTo(QSSceneAdapter.State.Expanding(1f))
+        assertThatExpanding(QSSceneAdapter.State.QQS)
+            .isEqualTo(QSSceneAdapter.State.Expanding { 0f })
+        assertThatExpanding(QSSceneAdapter.State.QS)
+            .isEqualTo(QSSceneAdapter.State.Expanding { 1f })
     }
 
     @Test
     fun collapsing() {
         val collapsingProgress = 0.3f
-        assertThat(Collapsing(collapsingProgress))
-            .isEqualTo(QSSceneAdapter.State.Expanding(1 - collapsingProgress))
+        assertThatExpanding(Collapsing { collapsingProgress })
+            .isEqualTo(QSSceneAdapter.State.Expanding { 1 - collapsingProgress })
     }
 
     @Test
     fun unsquishingQQS_expansionSameAsQQS() {
         val squishiness = 0.6f
-        assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion)
-            .isEqualTo(QSSceneAdapter.State.QQS.expansion)
+        assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion())
+            .isEqualTo(QSSceneAdapter.State.QQS.expansion())
     }
 
     @Test
     fun unsquishingQS_expansionSameAsQS() {
         val squishiness = 0.6f
-        assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion)
-            .isEqualTo(QSSceneAdapter.State.QS.expansion)
+        assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion())
+            .isEqualTo(QSSceneAdapter.State.QS.expansion())
+    }
+}
+
+private class ExpandingSubject(
+    metadata: FailureMetadata,
+    private val actual: QSSceneAdapter.State.Expanding?
+) : Subject(metadata, actual) {
+    fun isEqualTo(expected: QSSceneAdapter.State.Expanding) {
+        isNotNull()
+        check("expansion()")
+            .that(actual?.expansion?.invoke())
+            .isEqualTo(expected.expansion.invoke())
+    }
+
+    companion object {
+        fun expanding(): Factory<ExpandingSubject, QSSceneAdapter.State.Expanding> {
+            return Factory { metadata: FailureMetadata, actual: QSSceneAdapter.State.Expanding? ->
+                ExpandingSubject(metadata, actual)
+            }
+        }
+
+        fun assertThatExpanding(actual: QSSceneAdapter.State.Expanding): ExpandingSubject {
+            return assertAbout(expanding()).that(actual)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c15a4e5..163b9b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -133,6 +134,7 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                logger = kosmos.sceneLogger,
                 motionEventHandlerReceiver = {},
             )
             .apply { setTransitionState(transitionState) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 64a13de..8f8d2e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -1682,6 +1682,7 @@
             underTest.start()
 
             // run all pending dismiss succeeded/cancelled calls from setup:
+            runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
             val dismissCallback: IKeyguardDismissCallback = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index b315783..f856c55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
@@ -72,6 +73,7 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                logger = kosmos.sceneLogger,
                 motionEventHandlerReceiver = { motionEventHandler ->
                     [email protected] = motionEventHandler
                 },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index eac86e5..ce9b3be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -23,7 +23,6 @@
 import android.os.Handler
 import android.testing.TestableLooper
 import android.view.View
-import android.widget.FrameLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -46,6 +45,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.never
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -67,12 +68,9 @@
 
     @Mock private lateinit var session: SmartspaceSession
 
-    private lateinit var controller: CommunalSmartspaceController
+    private val preconditionListenerCaptor = argumentCaptor<SmartspacePrecondition.Listener>()
 
-    // TODO(b/272811280): Remove usage of real view
-    private val fakeParent by lazy {
-        FrameLayout(context)
-    }
+    private lateinit var controller: CommunalSmartspaceController
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -155,6 +153,26 @@
         verify(session).close()
     }
 
+    /** Ensures smartspace session begins when precondition is met if there is any listener. */
+    @Test
+    fun testConnectOnPreconditionMet() {
+        // Precondition not met
+        `when`(precondition.conditionsMet()).thenReturn(false)
+        controller.addListener(listener)
+
+        // Verify session not created because precondition not met
+        verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+        // Precondition met
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        verify(precondition).addListener(preconditionListenerCaptor.capture())
+        val preconditionListener = preconditionListenerCaptor.firstValue
+        preconditionListener.onCriteriaChanged()
+
+        // Verify session created
+        verify(smartspaceManager).createSmartspaceSession(any())
+    }
+
     /**
      * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
      * view is detached.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 11504aa..20d3a7b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy.domain.interactor
 
+import android.app.AutomaticZenRule
 import android.app.NotificationManager.Policy
 import android.provider.Settings
 import android.provider.Settings.Secure.ZEN_DURATION
@@ -217,4 +218,35 @@
             assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id))
                 .isEqualTo(Duration.ofMinutes(60))
         }
+
+    @Test
+    fun mainActiveMode_returnsMainActiveMode() =
+        testScope.runTest {
+            val mainActiveMode by collectLastValue(underTest.mainActiveMode)
+
+            zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+            zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+            runCurrent()
+            assertThat(mainActiveMode).isNull()
+
+            zenModeRepository.activateMode("Other")
+            runCurrent()
+            assertThat(mainActiveMode).isNotNull()
+            assertThat(mainActiveMode!!.id).isEqualTo("Other")
+
+            zenModeRepository.activateMode("Bedtime")
+            runCurrent()
+            assertThat(mainActiveMode).isNotNull()
+            assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+
+            zenModeRepository.deactivateMode("Other")
+            runCurrent()
+            assertThat(mainActiveMode).isNotNull()
+            assertThat(mainActiveMode!!.id).isEqualTo("Bedtime")
+
+            zenModeRepository.deactivateMode("Bedtime")
+            runCurrent()
+            assertThat(mainActiveMode).isNull()
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
index 094dc0a..e939f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.data.repository
 
+import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
@@ -26,7 +27,12 @@
 interface BouncerMessageRepository {
     val bouncerMessage: Flow<BouncerMessageModel>
 
-    fun setMessage(message: BouncerMessageModel)
+    fun setMessage(message: BouncerMessageModel, source: BiometricSourceType? = null)
+
+    /**
+     * Return the source of the current [bouncerMessage] if it was set from a biometric. Else, null.
+     */
+    fun getMessageSource(): BiometricSourceType?
 }
 
 @SysUISingleton
@@ -34,8 +40,14 @@
 
     private val _bouncerMessage = MutableStateFlow(BouncerMessageModel())
     override val bouncerMessage: Flow<BouncerMessageModel> = _bouncerMessage
+    private var messageSource: BiometricSourceType? = null
 
-    override fun setMessage(message: BouncerMessageModel) {
+    override fun setMessage(message: BouncerMessageModel, source: BiometricSourceType?) {
         _bouncerMessage.value = message
+        messageSource = source
+    }
+
+    override fun getMessageSource(): BiometricSourceType? {
+        return messageSource
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index 8e12646..85fffbf 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
+import android.hardware.biometrics.BiometricFaceConstants
 import android.hardware.biometrics.BiometricSourceType
 import android.os.CountDownTimer
 import com.android.keyguard.KeyguardSecurityModel
@@ -115,7 +116,8 @@
                                     isFingerprintAuthCurrentlyAllowedOnBouncer.value
                                 )
                                 .toMessage()
-                    }
+                    },
+                    biometricSourceType,
                 )
             }
 
@@ -123,7 +125,12 @@
                 biometricSourceType: BiometricSourceType?,
                 acquireInfo: Int
             ) {
-                super.onBiometricAcquired(biometricSourceType, acquireInfo)
+                if (
+                    repository.getMessageSource() == BiometricSourceType.FACE &&
+                        acquireInfo == BiometricFaceConstants.FACE_ACQUIRED_START
+                ) {
+                    repository.setMessage(defaultMessage)
+                }
             }
 
             override fun onBiometricAuthenticated(
@@ -131,7 +138,7 @@
                 biometricSourceType: BiometricSourceType?,
                 isStrongBiometric: Boolean
             ) {
-                repository.setMessage(defaultMessage)
+                repository.setMessage(defaultMessage, biometricSourceType)
             }
         }
 
@@ -291,7 +298,8 @@
                 currentSecurityMode,
                 value,
                 isFingerprintAuthCurrentlyAllowedOnBouncer.value
-            )
+            ),
+            BiometricSourceType.FINGERPRINT,
         )
     }
 
@@ -302,7 +310,8 @@
                 currentSecurityMode,
                 value,
                 isFingerprintAuthCurrentlyAllowedOnBouncer.value
-            )
+            ),
+            BiometricSourceType.FACE,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 040af90..aabfbd1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.Flags.clipboardImageTimeout;
+import static com.android.systemui.Flags.clipboardSharedTransitions;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -33,7 +34,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -207,7 +207,7 @@
         mClipboardUtils = clipboardUtils;
         mBgExecutor = bgExecutor;
 
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             mView.setCallbacks(this);
         } else {
             mView.setCallbacks(mClipboardCallbacks);
@@ -220,7 +220,7 @@
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
-            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            if (clipboardSharedTransitions()) {
                 finish(CLIPBOARD_OVERLAY_TIMED_OUT);
             } else {
                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
@@ -232,7 +232,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                    if (clipboardSharedTransitions()) {
                         finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                     } else {
                         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -248,7 +248,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (SCREENSHOT_ACTION.equals(intent.getAction())) {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                    if (clipboardSharedTransitions()) {
                         finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                     } else {
                         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -481,7 +481,7 @@
                 remoteAction.ifPresent(action -> {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
                     mView.post(() -> mView.setActionChip(action, () -> {
-                        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        if (clipboardSharedTransitions()) {
                             finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
                         } else {
                             mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
@@ -528,7 +528,7 @@
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
-                            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                            if (clipboardSharedTransitions()) {
                                 finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                             } else {
                                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
@@ -575,6 +575,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
+                // check again after animation to see if we should still be minimized
+                if (mIsMinimized && !shouldShowMinimized(mWindow.getWindowInsets())) {
+                    animateFromMinimized();
+                }
                 if (mOnUiUpdate != null) {
                     mOnUiUpdate.run();
                 }
@@ -690,14 +694,14 @@
 
     @Override
     public void onDismissButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
         }
     }
 
     @Override
     public void onRemoteCopyButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
                     IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
         }
@@ -705,7 +709,7 @@
 
     @Override
     public void onShareButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
                 finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
                         IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
@@ -715,7 +719,7 @@
 
     @Override
     public void onPreviewTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             switch (mClipboardModel.getType()) {
                 case TEXT:
                     finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 6343752..5bbb46d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -223,19 +223,15 @@
             // KTF for this case and just collect the new progress instead.
             collectProgress(transition)
         } else if (transition.toScene == CommunalScenes.Communal) {
-            if (currentTransitionId != null) {
-                if (currentToState == KeyguardState.GLANCEABLE_HUB) {
-                    transitionKtfTo(transitionInteractor.getStartedFromState())
-                }
+            if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+                transitionKtfTo(transitionInteractor.getStartedFromState())
             }
             startTransitionToGlanceableHub()
             collectProgress(transition)
         } else if (transition.toScene == CommunalScenes.Blank) {
-            if (currentTransitionId != null) {
-                // Another transition started before this one is completed. Transition to the
-                // GLANCEABLE_HUB state so that we can properly transition away from it.
-                transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
-            }
+            // Another transition started before this one is completed. Transition to the
+            // GLANCEABLE_HUB state so that we can properly transition away from it.
+            transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
             startTransitionFromGlanceableHub()
             collectProgress(transition)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 80db535..012c844 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -65,6 +65,12 @@
     var preconditionListener =
         object : SmartspacePrecondition.Listener {
             override fun onCriteriaChanged() {
+                if (session == null && hasActiveSessionListeners()) {
+                    Log.d(TAG, "Precondition criteria changed. Attempting to connect session.")
+                    connectSession()
+                    return
+                }
+
                 reloadSmartspace()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5f3a57..4d75d66 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -337,10 +337,6 @@
     // TODO(b/278714186) Tracking Bug
     @JvmField
     val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
-    // TODO(b/279405451): Tracking Bug
-    @JvmField
-    val CLIPBOARD_SHARED_TRANSITIONS =
-            unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 06f77bf..54964d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
@@ -45,6 +47,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.any
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
@@ -198,29 +201,37 @@
             .distinctUntilChanged()
 
     /**
-     * Keyguard should not show while the communal hub is fully visible. This check is added since
-     * at the moment, closing the notification shade will cause the keyguard alpha to be set back to
-     * 1. Also ensure keyguard is never visible when GONE.
+     * Keyguard states which should fully hide the keyguard.
+     *
+     * Note: [GONE] is not included as it is handled separately.
+     */
+    private val hiddenKeyguardStates = listOf(OCCLUDED, DREAMING, GLANCEABLE_HUB)
+
+    /**
+     * Keyguard should not show if fully transitioned into a hidden keyguard state or if
+     * transitioning between hidden states.
      */
     private val hideKeyguard: Flow<Boolean> =
-        combine(
-                communalInteractor.isIdleOnCommunal,
+        (hiddenKeyguardStates.map { state ->
                 keyguardTransitionInteractor
-                    .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+                    .transitionValue(state)
                     .map { it == 1f }
-                    .onStart { emit(false) },
-                keyguardTransitionInteractor
-                    .transitionValue(OCCLUDED)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-                keyguardTransitionInteractor
-                    .transitionValue(KeyguardState.DREAMING)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-            ) { isIdleOnCommunal, isGone, isOccluded, isDreaming ->
-                isIdleOnCommunal || isGone || isOccluded || isDreaming
-            }
-            .distinctUntilChanged()
+                    .onStart { emit(false) }
+            } +
+                listOf(
+                    communalInteractor.isIdleOnCommunal,
+                    keyguardTransitionInteractor
+                        .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+                        .map { it == 1f }
+                        .onStart { emit(false) },
+                    keyguardTransitionInteractor
+                        .isInTransitionWhere(
+                            fromStatePredicate = { hiddenKeyguardStates.contains(it) },
+                            toStatePredicate = { hiddenKeyguardStates.contains(it) },
+                        )
+                        .onStart { emit(false) },
+                ))
+            .any()
 
     /** Last point that the root view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 5d81d4f..9f9c8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -53,6 +53,10 @@
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
 import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -65,6 +69,7 @@
 import com.android.systemui.util.LifecycleFragment
 import java.util.function.Consumer
 import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -77,6 +82,8 @@
 @Inject
 constructor(
     private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+    @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
+    @Named(QS_PANEL) private val qsMediaHost: MediaHost,
 ) : LifecycleFragment(), QS {
 
     private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
@@ -99,6 +106,8 @@
         QSComposeFragment.isUnexpectedlyInLegacyMode()
         viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
 
+        qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+        qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
         setListenerCollections()
     }
 
@@ -274,7 +283,7 @@
     ) {}
 
     override fun isFullyCollapsed(): Boolean {
-        return !viewModel.isQSVisible
+        return viewModel.qsExpansionValue <= 0f
     }
 
     override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 9e109e4..7d52216 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources
 import android.graphics.Rect
+import androidx.annotation.FloatRange
 import androidx.lifecycle.LifecycleCoroutineScope
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.qualifiers.Main
@@ -79,11 +80,17 @@
             _qsVisible.value = value
         }
 
-    private val _qsExpansion = MutableStateFlow(0f)
+    // This can only be negative if undefined (in which case it will be -1f), else it will be
+    // in [0, 1]. In some cases, it could be set back to -1f internally to indicate that it's
+    // different to every value in [0, 1].
+    @FloatRange(from = -1.0, to = 1.0) private val _qsExpansion = MutableStateFlow(-1f)
     var qsExpansionValue: Float
         get() = _qsExpansion.value
         set(value) {
-            _qsExpansion.value = value
+            if (value < 0f) {
+                _qsExpansion.value = -1f
+            }
+            _qsExpansion.value = value.coerceIn(0f, 1f)
         }
 
     private val _panelFraction = MutableStateFlow(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index dfcf216..ac6ebe7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -147,17 +147,17 @@
     sealed interface State {
 
         val isVisible: Boolean
-        val expansion: Float
+        val expansion: () -> Float
         val squishiness: () -> Float
 
         data object CLOSED : State {
             override val isVisible = false
-            override val expansion = 0f
+            override val expansion = { 0f }
             override val squishiness = { 1f }
         }
 
         /** State for expanding between QQS and QS */
-        data class Expanding(override val expansion: Float) : State {
+        class Expanding(override val expansion: () -> Float) : State {
             override val isVisible = true
             override val squishiness = { 1f }
         }
@@ -170,7 +170,7 @@
          */
         class UnsquishingQQS(override val squishiness: () -> Float) : State {
             override val isVisible = true
-            override val expansion = 0f
+            override val expansion = { 0f }
         }
 
         /**
@@ -181,16 +181,16 @@
          */
         class UnsquishingQS(override val squishiness: () -> Float) : State {
             override val isVisible = true
-            override val expansion = 1f
+            override val expansion = { 1f }
         }
 
         companion object {
             // These are special cases of the expansion.
-            val QQS = Expanding(0f)
-            val QS = Expanding(1f)
+            val QQS = Expanding { 0f }
+            val QS = Expanding { 1f }
 
             /** Collapsing from QS to QQS. [progress] is 0f in QS and 1f in QQS. */
-            fun Collapsing(progress: Float) = Expanding(1f - progress)
+            fun Collapsing(progress: () -> Float) = Expanding { 1f - progress() }
         }
     }
 }
@@ -418,14 +418,14 @@
 
     private fun QSImpl.applyState(state: QSSceneAdapter.State) {
         setQsVisible(state.isVisible)
-        setExpanded(state.isVisible && state.expansion > 0f)
+        setExpanded(state.isVisible && state.expansion() > 0f)
         setListening(state.isVisible)
     }
 
     override fun applyLatestExpansionAndSquishiness() {
         val qsImpl = _qsImpl.value
         val state = state.value
-        qsImpl?.setQsExpansion(state.expansion, 1f, 0f, state.squishiness())
+        qsImpl?.setQsExpansion(state.expansion(), 1f, 0f, state.squishiness())
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 5885193..75cb017d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -28,8 +28,6 @@
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.getValue
-import com.android.systemui.util.kotlin.pairwiseBy
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -83,20 +81,7 @@
      * Note that during a transition between scenes, more than one scene might be rendered but only
      * one is considered the committed/current scene.
      */
-    val currentScene: StateFlow<SceneKey> =
-        repository.currentScene
-            .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
-                logger.logSceneChangeCommitted(
-                    from = from,
-                    to = to,
-                )
-                to
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = repository.currentScene.value,
-            )
+    val currentScene: StateFlow<SceneKey> = repository.currentScene
 
     /**
      * The current state of the transition.
@@ -234,14 +219,15 @@
             return
         }
 
-        logger.logSceneChangeRequested(
+        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
+
+        logger.logSceneChanged(
             from = currentSceneKey,
             to = resolvedScene,
             reason = loggingReason,
             isInstant = false,
         )
 
-        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
         repository.changeScene(resolvedScene, transitionKey)
     }
 
@@ -274,7 +260,7 @@
             return
         }
 
-        logger.logSceneChangeRequested(
+        logger.logSceneChanged(
             from = currentSceneKey,
             to = resolvedScene,
             reason = loggingReason,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index cf1518e..94c94e2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -43,7 +43,7 @@
         )
     }
 
-    fun logSceneChangeRequested(
+    fun logSceneChanged(
         from: SceneKey,
         to: SceneKey,
         reason: String,
@@ -60,7 +60,7 @@
             },
             messagePrinter = {
                 buildString {
-                    append("Scene change requested: $str1 → $str2")
+                    append("Scene changed: $str1 → $str2")
                     if (isInstant) {
                         append(" (instant)")
                     }
@@ -70,21 +70,6 @@
         )
     }
 
-    fun logSceneChangeCommitted(
-        from: SceneKey,
-        to: SceneKey,
-    ) {
-        logBuffer.log(
-            tag = TAG,
-            level = LogLevel.INFO,
-            messageInitializer = {
-                str1 = from.toString()
-                str2 = to.toString()
-            },
-            messagePrinter = { "Scene change committed: $str1 → $str2" },
-        )
-    }
-
     fun logSceneTransition(transitionState: ObservableTransitionState) {
         when (transitionState) {
             is ObservableTransitionState.Transition -> {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 7f7f0f1..f8a9f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -42,6 +43,7 @@
     private val sceneInteractor: SceneInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
+    private val logger: SceneLogger,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
 ) : SysUiViewModel() {
     /**
@@ -135,16 +137,29 @@
                 else -> null
             }
 
-        return interactionTypeOrNull?.let { interactionType ->
-            // It's important that the falsing system is always queried, even if no enforcement will
-            // occur. This helps build up the right signal in the system.
-            val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+        val fromScene = currentScene.value
+        val isAllowed =
+            interactionTypeOrNull?.let { interactionType ->
+                // It's important that the falsing system is always queried, even if no enforcement
+                // will occur. This helps build up the right signal in the system.
+                val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
 
-            // Only enforce falsing if moving from the lockscreen scene to a new scene.
-            val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+                // Only enforce falsing if moving from the lockscreen scene to a new scene.
+                val fromLockscreenScene = fromScene == Scenes.Lockscreen
 
-            !fromLockscreenScene || !isFalseTouch
-        } ?: true
+                !fromLockscreenScene || !isFalseTouch
+            } ?: true
+
+        if (isAllowed) {
+            // A scene change is guaranteed; log it.
+            logger.logSceneChanged(
+                from = fromScene,
+                to = toScene,
+                reason = "user interaction",
+                isInstant = false,
+            )
+        }
+        return isAllowed
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 50ea3bb..448f7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -99,6 +99,9 @@
     ) {
         val displays = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
+        if (displays.isEmpty()) {
+            Log.wtf(TAG, "No displays found for screenshot.")
+        }
         displays.forEach { display ->
             val displayId = display.displayId
             var screenshotHandler: ScreenshotHandler =
@@ -219,8 +222,7 @@
     }
 
     private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
-        val controller =
-            screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
+        val controller = screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
         screenshotController = controller
         return controller
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index 3fe3162..29450a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.first
 
@@ -48,7 +48,7 @@
             return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
         }
 
-        if (DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context)) {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
             content.rootTasks.firstOrNull()?.also {
                 if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
                     return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index ee1944e..ad27da9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -214,8 +214,7 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
-                if (mCurrentDraggingBoundary != CropBoundary.NONE
-                        && mActivePointerId == event.getPointerId(mActivePointerId)) {
+                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     updateListener(MotionEvent.ACTION_UP, event.getX(0));
                     return true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7c3072d..6a2c602 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -139,6 +139,9 @@
     /** Fraction of shade expansion. */
     private float mExpansionFraction;
 
+    /** Fraction of QS expansion. 0 when in shade, 1 when in QS. */
+    private float mQsExpansionFraction;
+
     /** Height of the notifications panel when expansion completes. */
     private float mStackEndHeight;
 
@@ -208,6 +211,14 @@
     }
 
     /**
+     * @param expansionFraction Fraction of QS expansion.
+     */
+    public void setQsExpansionFraction(float expansionFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mQsExpansionFraction = expansionFraction;
+    }
+
+    /**
      * @param isSwipingUp Whether we are swiping up.
      */
     public void setSwipingUp(boolean isSwipingUp) {
@@ -258,6 +269,14 @@
     }
 
     /**
+     * @return Fraction of QS expansion.
+     */
+    public float getQsExpansionFraction() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        return mQsExpansionFraction;
+    }
+
+    /**
      * @see #getStackHeight()
      */
     public void setStackHeight(float stackHeight) {
@@ -837,6 +856,7 @@
         pw.println("mAppearFraction=" + mAppearFraction);
         pw.println("mAppearing=" + mAppearing);
         pw.println("mExpansionFraction=" + mExpansionFraction);
+        pw.println("mQsExpansionFraction=" + mQsExpansionFraction);
         pw.println("mExpandingVelocity=" + mExpandingVelocity);
         pw.println("mOverScrollTopAmount=" + mOverScrollTopAmount);
         pw.println("mOverScrollBottomAmount=" + mOverScrollBottomAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 07b0b83..f26f840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1244,6 +1244,7 @@
     @Override
     public void setHeadsUpTop(float headsUpTop) {
         mAmbientState.setHeadsUpTop(headsUpTop);
+        requestChildrenUpdate();
     }
 
     @Override
@@ -1563,6 +1564,12 @@
         }
     }
 
+    @Override
+    public void setQsExpandFraction(float expandFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mAmbientState.setQsExpansionFraction(expandFraction);
+    }
+
     /**
      * Update the height of the panel.
      *
@@ -2534,6 +2541,11 @@
         return getTopHeadsUpIntrinsicHeight();
     }
 
+    @Override
+    public int getHeadsUpInset() {
+        return mHeadsUpInset;
+    }
+
     /**
      * Calculate the gap height between two different views
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f35d666..55f0566 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -132,6 +132,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpNotificationViewControllerEmptyImpl;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -773,7 +774,7 @@
                     mHeadsUpManager,
                     statusBarService.get(),
                     getHeadsUpCallback(),
-                    new HeadsUpNotificationViewControllerEmptyImpl()
+                    getHeadsUpNotificationViewController()
             );
         }
         mNotificationRoundnessManager = notificationRoundnessManager;
@@ -1851,6 +1852,32 @@
         return mTouchHandler;
     }
 
+    private HeadsUpNotificationViewController getHeadsUpNotificationViewController() {
+        HeadsUpNotificationViewController headsUpViewController;
+        if (SceneContainerFlag.isEnabled()) {
+            headsUpViewController = new HeadsUpNotificationViewController() {
+                @Override
+                public void setHeadsUpDraggingStartingHeight(int startHeight) {
+                    // do nothing
+                }
+
+                @Override
+                public void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+                    setTrackingHeadsUp(expandableNotificationRow);
+                }
+
+                @Override
+                public void startExpand(float newX, float newY, boolean startTracking,
+                        float expandedHeight) {
+                    // do nothing
+                }
+            };
+        } else {
+            headsUpViewController = new HeadsUpNotificationViewControllerEmptyImpl();
+        }
+        return headsUpViewController;
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aee1d3e..0c2b5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static androidx.core.math.MathUtils.clamp;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -890,7 +888,14 @@
                 continue;
             }
             ExpandableViewState childState = row.getViewState();
-            if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+            boolean shouldSetTopHeadsUpEntry;
+            if (SceneContainerFlag.isEnabled()) {
+                shouldSetTopHeadsUpEntry = row.isHeadsUp();
+            } else {
+                shouldSetTopHeadsUpEntry = row.mustStayOnScreen();
+            }
+            if (topHeadsUpEntry == null && shouldSetTopHeadsUpEntry
+                    && !childState.headsUpIsVisible) {
                 topHeadsUpEntry = row;
                 childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
             }
@@ -898,7 +903,7 @@
             float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
             if (mIsExpanded) {
                 if (SceneContainerFlag.isEnabled()) {
-                    if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                    if (shouldHunBeVisibleWhenScrolled(row.isHeadsUp(),
                             childState.headsUpIsVisible, row.showingPulsing(),
                             ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
                         // the height of this child before clamping it to the top
@@ -909,10 +914,19 @@
                                 /* viewState = */ childState
                         );
                         float baseZ = ambientState.getBaseZHeight();
-                        if (headsUpTranslation < ambientState.getStackTop()) {
-                            // HUN displayed above the stack top, it needs a fix shadow
-                            childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
-                        } else {
+                        if (headsUpTranslation > ambientState.getStackTop()
+                                && row.isAboveShelf()) {
+                            // HUN displayed outside of the stack during transition from Gone/LS;
+                            // add a shadow that corresponds to the transition progress.
+                            float fraction = 1 - ambientState.getExpansionFraction();
+                            childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+                        } else if (headsUpTranslation < ambientState.getStackTop()
+                                && row.isAboveShelf()) {
+                            // HUN displayed outside of the stack during transition from QS;
+                            // add a shadow that corresponds to the transition progress.
+                            float fraction = ambientState.getQsExpansionFraction();
+                            childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+                        } else if (headsUpTranslation > ambientState.getStackTop()) {
                             // HUN displayed within the stack, add a shadow if it overlaps with
                             // other elements.
                             //
@@ -927,6 +941,8 @@
                                     /* baseZ = */ baseZ,
                                     /* viewState = */ childState
                             );
+                        } else {
+                            childState.setZTranslation(baseZ);
                         }
                         if (isTopEntry && row.isAboveShelf()) {
                             clampHunToMaxTranslation(
@@ -1081,7 +1097,7 @@
         if (scrollingContentTopPadding > 0f) {
             // scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
             // of the scrolling content. Use this to animate to the full shadow.
-            shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+            shadowFraction = Math.clamp(overlap / scrollingContentTopPadding, 0f, 1f);
         }
 
         if (overlap > 0.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 6226fe7..288924d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -80,9 +80,15 @@
     /** sets the current expand fraction */
     fun setExpandFraction(expandFraction: Float)
 
+    /** sets the current QS expand fraction */
+    fun setQsExpandFraction(expandFraction: Float)
+
     /** Sets whether the view is displayed in doze mode. */
     fun setDozing(dozing: Boolean)
 
+    /** Gets the inset for HUNs when they are not visible */
+    fun getHeadsUpInset(): Int
+
     /** Adds a listener to be notified, when the stack height might have changed. */
     fun addStackHeightChangedListener(runnable: Runnable)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 950b14d..40761e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -85,9 +85,14 @@
             launch {
                 viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
             }
+            launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } }
             launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
             launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
-            launch { viewModel.shouldResetStackTop.filter { it }.collect { view.setStackTop(0f) } }
+            launch {
+                viewModel.shouldResetStackTop
+                    .filter { it }
+                    .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) }
+            }
 
             launchAndDispose {
                 view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index ed69e6f..6489264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -124,6 +124,9 @@
             .distinctUntilChanged()
             .dumpWhileCollecting("expandFraction")
 
+    val qsExpandFraction: Flow<Float> =
+        shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+
     val shouldResetStackTop: Flow<Boolean> =
         sceneInteractor.transitionState
             .mapNotNull { state ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 7586133..f16fcb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -42,6 +42,7 @@
 class ZenModeInteractor
 @Inject
 constructor(
+    private val context: Context,
     private val zenModeRepository: ZenModeRepository,
     private val notificationSettingsRepository: NotificationSettingsRepository,
 ) {
@@ -78,6 +79,26 @@
     val activeModes: Flow<List<ZenMode>> =
         modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged()
 
+    /** Flow returning the most prioritized of the active modes, if any. */
+    val mainActiveMode: Flow<ZenMode?> =
+        activeModes.map { modes -> getMainActiveMode(modes) }.distinctUntilChanged()
+
+    /**
+     * Given the list of modes (which may include zero or more currently active modes), returns the
+     * most prioritized of the active modes, if any.
+     */
+    private fun getMainActiveMode(modes: List<ZenMode>): ZenMode? {
+        return modes.sortedWith(ZenMode.PRIORITIZING_COMPARATOR).firstOrNull { it.isActive }
+    }
+
+    suspend fun getModeIcon(mode: ZenMode): Icon {
+        return mode.getIcon(context, iconLoader).await().asIcon()
+    }
+
+    suspend fun getLockscreenModeIcon(mode: ZenMode): Icon {
+        return mode.getLockscreenIcon(context, iconLoader).await().asIcon()
+    }
+
     /**
      * Given the list of modes (which may include zero or more currently active modes), returns an
      * icon representing the active mode, if any (or, if multiple modes are active, to the most
@@ -86,16 +107,7 @@
      * package).
      */
     suspend fun getActiveModeIcon(context: Context, modes: List<ZenMode>): Icon? {
-        return modes
-            .sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
-            .firstOrNull { it.isActive }
-            ?.getLockscreenIcon(context, iconLoader)
-            ?.await()
-            ?.asIcon()
-    }
-
-    suspend fun getModeIcon(context: Context, mode: ZenMode): Icon {
-        return mode.getIcon(context, iconLoader).await().asIcon()
+        return getMainActiveMode(modes)?.let { m -> getLockscreenModeIcon(m) }
     }
 
     fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 02b5e49..be90bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -88,7 +88,7 @@
                 modesList.map { mode ->
                     ModeTileViewModel(
                         id = mode.id,
-                        icon = zenModeInteractor.getModeIcon(context, mode),
+                        icon = zenModeInteractor.getModeIcon(mode),
                         text = mode.name,
                         subtext = getTileSubtext(mode),
                         enabled = mode.isActive,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c425e82..5fc1971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -26,7 +27,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -47,6 +47,8 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.view.WindowInsets;
 import android.view.textclassifier.TextLinks;
 
@@ -130,7 +132,6 @@
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
     }
 
     /**
@@ -299,8 +300,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
@@ -311,6 +312,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped() {
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
@@ -324,8 +326,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
@@ -336,6 +338,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onDismissTapped() {
         initController();
 
@@ -350,7 +353,6 @@
 
     @Test
     public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mCallbacks.onSwipeDismissInitiated(mAnimator);
         mCallbacks.onDismissButtonTapped();
@@ -362,6 +364,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_multipleDismissals_dismissesOnce() {
         initController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index c2f035f1..19735e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -93,7 +93,7 @@
     private val tileDataInteractor =
         ModesTileDataInteractor(
             context,
-            ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
+            ZenModeInteractor(context, zenModeRepository, mock<NotificationSettingsRepository>()),
             testDispatcher
         )
     private val mapper =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index a5fbfb5..be9fcc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -18,11 +18,13 @@
 
 import android.content.ComponentName
 import android.content.Context
+import android.content.res.Resources
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.R
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.screenshot.data.model.DisplayContentModel
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -57,6 +59,7 @@
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class WorkProfilePolicyTest {
@@ -66,12 +69,17 @@
     @JvmField @Rule(order = 2) val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     @Mock lateinit var mContext: Context
+    @Mock lateinit var mResources: Resources
 
     private val kosmos = Kosmos()
     private lateinit var policy: WorkProfilePolicy
 
     @Before
     fun setUp() {
+        // Set desktop mode supported
+        whenever(mContext.resources).thenReturn(mResources)
+        whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+
         policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ad029d7..7e79019 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -296,6 +296,7 @@
             collapsedHeight = 100,
             intrinsicHeight = intrinsicHunHeight,
         )
+        ambientState.qsExpansionFraction = 1.0f
         whenever(notificationRow.isAboveShelf).thenReturn(true)
 
         // When
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index aef0828..66be7e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy.domain.interactor
 
+import android.content.testableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
@@ -23,6 +24,7 @@
 
 val Kosmos.zenModeInteractor by Fixture {
     ZenModeInteractor(
+        context = testableContext,
         zenModeRepository = zenModeRepository,
         notificationSettingsRepository = notificationSettingsRepository,
     )
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 9c6bcdf..53885fc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -140,7 +140,7 @@
         bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
                 safeExecuteAppFunctionCallback,
                 /*bindFlags=*/ Context.BIND_AUTO_CREATE,
-                /*timeoutInMillis=*/ mServiceConfig.getExecutionTimeoutConfig());
+                /*timeoutInMillis=*/ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
     }
 
     private void bindAppFunctionServiceUnchecked(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
index 35c6c78..4bc6e70 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
@@ -26,5 +26,5 @@
     /**
      * Returns the maximum time to wait for an app function execution to be complete.
      */
-    long getExecutionTimeoutConfig();
+    long getExecuteAppFunctionTimeoutMillis();
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
index b203ead..e090317 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
@@ -22,16 +22,17 @@
  * Implementation of {@link ServiceConfig}
  */
 public class ServiceConfigImpl implements ServiceConfig {
-    static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT = "execution_timeout";
-    static final long DEFAULT_EXECUTION_TIMEOUT_MS = 5000L;
+    static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT =
+            "execute_app_function_timeout_millis";
+    static final long DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS = 5000L;
 
 
     @Override
-    public long getExecutionTimeoutConfig() {
+    public long getExecuteAppFunctionTimeoutMillis() {
         return DeviceConfig.getLong(
                 NAMESPACE_APP_FUNCTIONS,
                 DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT,
-                DEFAULT_EXECUTION_TIMEOUT_MS
+                DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS
         );
     }
 }
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 8abbe56..22eefb3 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -73,6 +73,8 @@
 
 /**
  * Utility methods to read backup tar file.
+ * Exteranl depenency:
+ *  <li> @android.provider.Settings.Secure.V_TO_U_RESTORE_ALLOWLIST
  */
 public class TarBackupReader {
     private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index b3a2da4..d56f17b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -20,6 +20,7 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
 import static android.content.ComponentName.createRelative;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
@@ -40,6 +41,7 @@
 import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.ComponentName;
 import android.content.Context;
@@ -182,7 +184,11 @@
             String errorMessage = "3p apps are not allowed to create associations on watch.";
             Slog.e(TAG, errorMessage);
             try {
-                callback.onFailure(RESULT_INTERNAL_ERROR);
+                if (Flags.associationFailureCode()) {
+                    callback.onFailure(RESULT_SECURITY_ERROR, errorMessage);
+                } else {
+                    callback.onFailure(RESULT_INTERNAL_ERROR, errorMessage);
+                }
             } catch (RemoteException e) {
                 // ignored
             }
@@ -251,9 +257,12 @@
         } catch (SecurityException e) {
             // Since, at this point the caller is our own UI, we need to catch the exception on
             // forward it back to the application via the callback.
-            Slog.e(TAG, e.getMessage());
             try {
-                callback.onFailure(RESULT_INTERNAL_ERROR);
+                if (Flags.associationFailureCode()) {
+                    callback.onFailure(RESULT_SECURITY_ERROR, e.getMessage());
+                } else {
+                    callback.onFailure(RESULT_INTERNAL_ERROR, e.getMessage());
+                }
             } catch (RemoteException ignore) {
             }
             return;
@@ -378,7 +387,7 @@
             // Send the association back via the app's callback
             if (callback != null) {
                 try {
-                    callback.onFailure(RESULT_INTERNAL_ERROR);
+                    callback.onFailure(RESULT_INTERNAL_ERROR, "Association doesn't exist.");
                 } catch (RemoteException ignore) {
                 }
             }
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 8da58cf..d3e808f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -689,13 +689,15 @@
     }
 
     /** A helper class used to wait for an input device to be registered. */
-    private class WaitForDevice implements  AutoCloseable {
+    private class WaitForDevice implements AutoCloseable {
         private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+        private final String mDeviceName;
         private final InputManager.InputDeviceListener mListener;
 
         private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
 
         WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) {
+            mDeviceName = deviceName;
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
@@ -741,15 +743,17 @@
             try {
                 if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
                     throw new DeviceCreationException(
-                            "Timed out waiting for virtual device to be created.");
+                            "Timed out waiting for virtual input device " + mDeviceName
+                                    + " to be created.");
                 }
             } catch (InterruptedException e) {
                 throw new DeviceCreationException(
-                        "Interrupted while waiting for virtual device to be created.", e);
+                        "Interrupted while waiting for virtual input device " + mDeviceName
+                                + " to be created.", e);
             }
             if (mInputDeviceId == IInputConstants.INVALID_INPUT_DEVICE_ID) {
                 throw new IllegalStateException(
-                        "Virtual input device was created with an invalid "
+                        "Virtual input device " + mDeviceName + " was created with an invalid "
                                 + "id=" + mInputDeviceId);
             }
             return mInputDeviceId;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index e84250d..47203fb 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -296,7 +296,9 @@
                     this::onSyncRequestNotified);
             setPropertyChangedListenerLocked();
             updateConfigs();
-            registerConnectivityModuleHealthListener();
+            if (!Flags.refactorCrashrecovery()) {
+                registerConnectivityModuleHealthListener();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 68d9221..f1bdc05 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,7 +50,6 @@
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
@@ -101,15 +100,12 @@
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
-import static android.os.Process.NETWORK_STACK_UID;
-import static android.os.Process.NFC_UID;
 import static android.os.Process.PHONE_UID;
 import static android.os.Process.PROC_OUT_LONG;
 import static android.os.Process.PROC_SPACE_TERM;
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SCHED_FIFO;
 import static android.os.Process.SCHED_RESET_ON_FORK;
-import static android.os.Process.SE_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SIGNAL_USR1;
 import static android.os.Process.SYSTEM_UID;
@@ -145,8 +141,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
@@ -155,7 +149,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -265,10 +258,7 @@
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -316,9 +306,6 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.media.audiofx.AudioEffect;
-import android.net.ConnectivityManager;
-import android.net.Proxy;
 import android.net.Uri;
 import android.os.AppZygote;
 import android.os.BatteryStats;
@@ -374,9 +361,7 @@
 import android.server.ServerProtoEnums;
 import android.system.Os;
 import android.system.OsConstants;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -386,7 +371,6 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
-import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -435,11 +419,11 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.DisplayThread;
-import com.android.server.IntentResolver;
 import com.android.server.IoThread;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -451,7 +435,6 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
-import com.android.server.am.ComponentAliasResolver.Resolution;
 import com.android.server.am.LowMemDetector.MemFactor;
 import com.android.server.appop.AppOpsService;
 import com.android.server.compat.PlatformCompat;
@@ -462,14 +445,12 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.net.NetworkManagementInternal;
 import com.android.server.os.NativeTombstoneManager;
-import com.android.server.pm.Computer;
 import com.android.server.pm.Installer;
 import com.android.server.pm.SaferIntentUtils;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
 import com.android.server.stats.pull.StatsPullAtomService;
@@ -512,7 +493,6 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -537,7 +517,6 @@
 
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
     private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
@@ -565,9 +544,6 @@
 
     static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
 
-    // Maximum number of receivers an app can register.
-    private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
-
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -652,15 +628,6 @@
     static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
     static final String EXTRA_EXTRA_ATTACHMENT_URI =
             "android.intent.extra.EXTRA_ATTACHMENT_URI";
-    /**
-     * It is now required for apps to explicitly set either
-     * {@link android.content.Context#RECEIVER_EXPORTED} or
-     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
-     * unprotected broadcast in code.
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
      * The maximum number of bytes that {@link #setProcessStateSummary} accepts.
@@ -737,11 +704,9 @@
     // so that dispatch of foreground broadcasts gets precedence.
     private BroadcastQueue mBroadcastQueue;
 
-    @GuardedBy("this")
-    BroadcastStats mLastBroadcastStats;
-
-    @GuardedBy("this")
-    BroadcastStats mCurBroadcastStats;
+    // TODO: Add a consistent way of accessing the methods within this class. Currently, some
+    // methods require access while holding a lock, while others do not.
+    BroadcastController mBroadcastController;
 
     TraceErrorLogger mTraceErrorLogger;
 
@@ -763,6 +728,7 @@
 
     final AppErrors mAppErrors;
     final PackageWatchdog mPackageWatchdog;
+    final CrashRecoveryHelper mCrashRecoveryHelper;
 
     @GuardedBy("mDeliveryGroupPolicyIgnoredActions")
     private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet();
@@ -860,12 +826,6 @@
     };
 
     /**
-     * Broadcast actions that will always be deliverable to unlaunched/background apps
-     */
-    @GuardedBy("this")
-    private ArraySet<String> mBackgroundLaunchBroadcasts;
-
-    /**
      * When an app has restrictions on the other apps that can have associations with it,
      * it appears here with a set of the allowed apps and also track debuggability of the app.
      */
@@ -1133,97 +1093,6 @@
     private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
     private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
 
-    /**
-     * Keeps track of all IIntentReceivers that have been registered for broadcasts.
-     * Hash keys are the receiver IBinder, hash value is a ReceiverList.
-     */
-    @GuardedBy("this")
-    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
-
-    /**
-     * Resolver for broadcast intents to registered receivers.
-     * Holds BroadcastFilter (subclass of IntentFilter).
-     */
-    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
-            = new IntentResolver<BroadcastFilter, BroadcastFilter>() {
-        @Override
-        protected boolean allowFilterResult(
-                BroadcastFilter filter, List<BroadcastFilter> dest) {
-            IBinder target = filter.receiverList.receiver.asBinder();
-            for (int i = dest.size() - 1; i >= 0; i--) {
-                if (dest.get(i).receiverList.receiver.asBinder() == target) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
-                int match, int userId, long customFlags) {
-            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
-                    || userId == filter.owningUserId) {
-                return super.newResult(computer, filter, match, userId, customFlags);
-            }
-            return null;
-        }
-
-        @Override
-        protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
-            return input;
-        }
-
-        @Override
-        protected BroadcastFilter[] newArray(int size) {
-            return new BroadcastFilter[size];
-        }
-
-        @Override
-        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
-            return packageName.equals(filter.packageName);
-        }
-    };
-
-    /**
-     * State of all active sticky broadcasts per user.  Keys are the action of the
-     * sticky Intent, values are an ArrayList of all broadcasted intents with
-     * that action (which should usually be one).  The SparseArray is keyed
-     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
-     * for stickies that are sent to all users.
-     */
-    @GuardedBy("mStickyBroadcasts")
-    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
-            new SparseArray<>();
-
-    @VisibleForTesting
-    static final class StickyBroadcast {
-        public Intent intent;
-        public boolean deferUntilActive;
-        public int originalCallingUid;
-        /** The snapshot process state of the app who sent this broadcast */
-        public int originalCallingAppProcessState;
-        public String resolvedDataType;
-
-        public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
-                int originalCallingUid, int originalCallingAppProcessState,
-                String resolvedDataType) {
-            final StickyBroadcast b = new StickyBroadcast();
-            b.intent = intent;
-            b.deferUntilActive = deferUntilActive;
-            b.originalCallingUid = originalCallingUid;
-            b.originalCallingAppProcessState = originalCallingAppProcessState;
-            b.resolvedDataType = resolvedDataType;
-            return b;
-        }
-
-        @Override
-        public String toString() {
-            return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
-                    + originalCallingUid + ", originalCallingAppProcessState="
-                    + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
-        }
-    }
-
     final ActiveServices mServices;
 
     final static class Association {
@@ -1685,7 +1554,7 @@
     // Encapsulates the global setting "hidden_api_blacklist_exemptions"
     final HiddenApiSettings mHiddenApiBlacklist;
 
-    private final PlatformCompat mPlatformCompat;
+    final PlatformCompat mPlatformCompat;
 
     PackageManagerInternal mPackageManagerInt;
     PermissionManagerServiceInternal mPermissionManagerInt;
@@ -2326,10 +2195,12 @@
                 mService.mBatteryStatsService.systemServicesReady();
                 mService.mServices.systemServicesReady();
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
-                mService.startBroadcastObservers();
+                mService.mBroadcastController.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                 if (!refactorCrashrecovery()) {
                     mService.mPackageWatchdog.onPackagesReady();
+                } else {
+                    mService.mCrashRecoveryHelper.registerConnectivityModuleHealthListener();
                 }
                 mService.scheduleHomeTimeout();
             }
@@ -2500,6 +2371,7 @@
         mUiContext = null;
         mAppErrors = injector.getAppErrors();
         mPackageWatchdog = null;
+        mCrashRecoveryHelper = null;
         mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */,
             null /* storageFile */, null /* handler */);
         mBatteryStatsService = mInjector.getBatteryStatsService();
@@ -2537,6 +2409,7 @@
         mPendingStartActivityUids = new PendingStartActivityUids();
         mUseFifoUiScheduling = false;
         mBroadcastQueue = injector.getBroadcastQueue(this);
+        mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2579,9 +2452,11 @@
                 : new OomAdjuster(this, mProcessList, activeUids);
 
         mBroadcastQueue = mInjector.getBroadcastQueue(this);
+        mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
+        mCrashRecoveryHelper = new CrashRecoveryHelper(mUiContext);
         mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
         mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
         mUidObserverController = new UidObserverController(mUiHandler);
@@ -2646,6 +2521,7 @@
 
     void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
         mBroadcastQueue = broadcastQueue;
+        mBroadcastController.setBroadcastQueueForTest(broadcastQueue);
     }
 
     BroadcastQueue getBroadcastQueue() {
@@ -2680,18 +2556,6 @@
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
     }
 
-    private ArraySet<String> getBackgroundLaunchBroadcasts() {
-        if (mBackgroundLaunchBroadcasts == null) {
-            mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
-        }
-        return mBackgroundLaunchBroadcasts;
-    }
-
-    private String getWearRemoteIntentAction() {
-        return mContext.getResources().getString(
-                    com.android.internal.R.string.config_wearRemoteIntentAction);
-    }
-
     /**
      * Ensures that the given package name has an explicit set of allowed associations.
      * If it does not, give it an empty set.
@@ -2762,7 +2626,7 @@
     }
 
     /** Updates allowed associations for app info (specifically, based on debuggability).  */
-    private void updateAssociationForApp(ApplicationInfo appInfo) {
+    void updateAssociationForApp(ApplicationInfo appInfo) {
         ensureAllowedAssociations();
         PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
         if (pai != null) {
@@ -3888,7 +3752,7 @@
         forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
     }
 
-    private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+    void forceStopPackage(final String packageName, int userId, int userRunningFlags,
             String reason) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -4216,7 +4080,7 @@
         mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags);
     }
 
-    private void cleanupDisabledPackageComponentsLocked(
+    void cleanupDisabledPackageComponentsLocked(
             String packageName, int userId, String[] changedClasses) {
 
         Set<String> disabledClasses = null;
@@ -4454,9 +4318,7 @@
 
         if (packageName == null) {
             // Remove all sticky broadcasts from this user.
-            synchronized (mStickyBroadcasts) {
-                mStickyBroadcasts.remove(userId);
-            }
+            mBroadcastController.removeStickyBroadcasts(userId);
         }
 
         ArrayList<ContentProviderRecord> providers = new ArrayList<>();
@@ -9329,10 +9191,6 @@
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
-    private void startBroadcastObservers() {
-        mBroadcastQueue.start(mContext.getContentResolver());
-    }
-
     private void updateForceBackgroundCheck(boolean enabled) {
         synchronized (this) {
             synchronized (mProcLock) {
@@ -10524,14 +10382,15 @@
                 pw.println(
                         "-------------------------------------------------------------------------------");
             }
-            dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
                 pw.println(
                         "-------------------------------------------------------------------------------");
             }
             if (dumpAll || dumpPackage != null) {
-                dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll,
+                        dumpPackage);
                 pw.println();
                 if (dumpAll) {
                     pw.println(
@@ -10782,7 +10641,7 @@
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 // output proto is ActivityManagerServiceDumpBroadcastsProto
                 synchronized (this) {
-                    writeBroadcastsToProtoLocked(proto);
+                    mBroadcastController.writeBroadcastsToProtoLocked(proto);
                 }
             } else if ("provider".equals(cmd)) {
                 String[] newArgs;
@@ -10846,7 +10705,7 @@
                     proto.end(activityToken);
 
                     long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
-                    writeBroadcastsToProtoLocked(proto);
+                    mBroadcastController.writeBroadcastsToProtoLocked(proto);
                     proto.end(broadcastToken);
 
                     long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
@@ -10906,7 +10765,8 @@
                     opti++;
                 }
                 synchronized (this) {
-                    dumpBroadcastsLocked(fd, pw, args, opti, /* dumpAll= */ true, dumpPackage);
+                    mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti,
+                            /* dumpAll= */ true, dumpPackage);
                 }
             } else if ("broadcast-stats".equals(cmd)) {
                 if (opti < args.length) {
@@ -10915,10 +10775,11 @@
                 }
                 synchronized (this) {
                     if (dumpCheckinFormat) {
-                        dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
-                                dumpPackage);
+                        mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti,
+                                dumpCheckin, dumpPackage);
                     } else {
-                        dumpBroadcastStatsLocked(fd, pw, args, opti, true, dumpPackage);
+                        mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, true,
+                                dumpPackage);
                     }
                 }
             } else if ("intents".equals(cmd) || "i".equals(cmd)) {
@@ -11072,7 +10933,8 @@
 
         // No piece of data specified, dump everything.
         if (dumpCheckinFormat) {
-            dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin, dumpPackage);
+            mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
+                    dumpPackage);
         } else {
             if (dumpClient) {
                 // dumpEverything() will take the lock when needed, and momentarily drop
@@ -11783,42 +11645,6 @@
         }
     }
 
-    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
-        if (mRegisteredReceivers.size() > 0) {
-            Iterator it = mRegisteredReceivers.values().iterator();
-            while (it.hasNext()) {
-                ReceiverList r = (ReceiverList)it.next();
-                r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
-            }
-        }
-        mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
-        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
-        synchronized (mStickyBroadcasts) {
-            for (int user = 0; user < mStickyBroadcasts.size(); user++) {
-                long token = proto.start(
-                        ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
-                proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
-                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
-                        : mStickyBroadcasts.valueAt(user).entrySet()) {
-                    long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
-                    proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
-                    for (StickyBroadcast broadcast : ent.getValue()) {
-                        broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
-                                false, true, true, false);
-                    }
-                    proto.end(actionToken);
-                }
-                proto.end(token);
-            }
-        }
-
-        long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
-        proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER, mHandler.toString());
-        mHandler.getLooper().dumpDebug(proto,
-            ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
-        proto.end(handlerToken);
-    }
-
     void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         pw.println(
@@ -11854,219 +11680,6 @@
         }
     }
 
-    @NeverCompile
-    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        boolean dumpConstants = true;
-        boolean dumpHistory = true;
-        boolean needSep = false;
-        boolean onlyHistory = false;
-        boolean printedAnything = false;
-        boolean onlyReceivers = false;
-        int filteredUid = Process.INVALID_UID;
-
-        if ("history".equals(dumpPackage)) {
-            if (opti < args.length && "-s".equals(args[opti])) {
-                dumpAll = false;
-            }
-            onlyHistory = true;
-            dumpPackage = null;
-        }
-        if ("receivers".equals(dumpPackage)) {
-            onlyReceivers = true;
-            dumpPackage = null;
-            if (opti + 2 <= args.length) {
-                for (int i = opti; i < args.length; i++) {
-                    String arg = args[i];
-                    switch (arg) {
-                        case "--uid":
-                            filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
-                            if (filteredUid == Process.INVALID_UID) {
-                                return;
-                            }
-                            break;
-                        default:
-                            pw.printf("Invalid argument at index %d: %s\n", i, arg);
-                            return;
-                    }
-                }
-            }
-        }
-        if (DEBUG_BROADCAST) {
-            Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
-                    + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory, onlyReceivers,
-                    filteredUid);
-        }
-
-        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
-        if (!onlyHistory && dumpAll) {
-            if (mRegisteredReceivers.size() > 0) {
-                boolean printed = false;
-                Iterator it = mRegisteredReceivers.values().iterator();
-                while (it.hasNext()) {
-                    ReceiverList r = (ReceiverList)it.next();
-                    if (dumpPackage != null && (r.app == null ||
-                            !dumpPackage.equals(r.app.info.packageName))) {
-                        continue;
-                    }
-                    if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
-                        if (DEBUG_BROADCAST) {
-                            Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
-                                    + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
-                        }
-                        continue;
-                    }
-                    if (!printed) {
-                        pw.println("  Registered Receivers:");
-                        needSep = true;
-                        printed = true;
-                        printedAnything = true;
-                    }
-                    pw.print("  * "); pw.println(r);
-                    r.dump(pw, "    ");
-                }
-            } else {
-                if (onlyReceivers) {
-                    pw.println("  (no registered receivers)");
-                }
-            }
-
-            if (!onlyReceivers) {
-                if (mReceiverResolver.dump(pw, needSep
-                        ? "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
-                        "    ", dumpPackage, false, false)) {
-                    needSep = true;
-                    printedAnything = true;
-                }
-            }
-        }
-
-        if (!onlyReceivers) {
-            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
-                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
-            printedAnything |= needSep;
-        }
-
-        needSep = true;
-
-        synchronized (mStickyBroadcasts) {
-            if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
-                    && dumpPackage == null) {
-                for (int user = 0; user < mStickyBroadcasts.size(); user++) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printedAnything = true;
-                    pw.print("  Sticky broadcasts for user ");
-                    pw.print(mStickyBroadcasts.keyAt(user));
-                    pw.println(":");
-                    StringBuilder sb = new StringBuilder(128);
-                    for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
-                            : mStickyBroadcasts.valueAt(user).entrySet()) {
-                        pw.print("  * Sticky action ");
-                        pw.print(ent.getKey());
-                        if (dumpAll) {
-                            pw.println(":");
-                            ArrayList<StickyBroadcast> broadcasts = ent.getValue();
-                            final int N = broadcasts.size();
-                            for (int i = 0; i < N; i++) {
-                                final Intent intent = broadcasts.get(i).intent;
-                                final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
-                                sb.setLength(0);
-                                sb.append("    Intent: ");
-                                intent.toShortString(sb, false, true, false, false);
-                                pw.print(sb);
-                                if (deferUntilActive) {
-                                    pw.print(" [D]");
-                                }
-                                pw.println();
-                                pw.print("      originalCallingUid: ");
-                                pw.println(broadcasts.get(i).originalCallingUid);
-                                pw.println();
-                                Bundle bundle = intent.getExtras();
-                                if (bundle != null) {
-                                    pw.print("      extras: ");
-                                    pw.println(bundle);
-                                }
-                            }
-                        } else {
-                            pw.println("");
-                        }
-                    }
-                }
-            }
-        }
-
-        if (!onlyHistory && !onlyReceivers && dumpAll) {
-            pw.println();
-            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
-                    + mBroadcastQueue.describeStateLocked());
-            pw.println("  mHandler:");
-            mHandler.dump(new PrintWriterPrinter(pw), "    ");
-            needSep = true;
-            printedAnything = true;
-        }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
-    }
-
-    @NeverCompile
-    void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        if (mCurBroadcastStats == null) {
-            return;
-        }
-
-        pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
-        final long now = SystemClock.elapsedRealtime();
-        if (mLastBroadcastStats != null) {
-            pw.print("  Last stats (from ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
-            pw.print(" to ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
-            pw.print(", ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
-                    - mLastBroadcastStats.mStartUptime, pw);
-            pw.println(" uptime):");
-            if (!mLastBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
-                pw.println("    (nothing)");
-            }
-            pw.println();
-        }
-        pw.print("  Current stats (from ");
-        TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
-        pw.print(" to now, ");
-        TimeUtils.formatDuration(SystemClock.uptimeMillis()
-                - mCurBroadcastStats.mStartUptime, pw);
-        pw.println(" uptime):");
-        if (!mCurBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
-            pw.println("    (nothing)");
-        }
-    }
-
-    @NeverCompile
-    void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean fullCheckin, String dumpPackage) {
-        if (mCurBroadcastStats == null) {
-            return;
-        }
-
-        if (mLastBroadcastStats != null) {
-            mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
-            if (fullCheckin) {
-                mLastBroadcastStats = null;
-                return;
-            }
-        }
-        mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
-        if (fullCheckin) {
-            mCurBroadcastStats = null;
-        }
-    }
-
     void dumpPermissions(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
 
@@ -14588,33 +14201,6 @@
     // BROADCASTS
     // =========================================================
 
-    private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
-        if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
-            return false;
-        }
-        // Easy case -- we have the app's ProcessRecord.
-        if (record != null) {
-            return record.info.isInstantApp();
-        }
-        // Otherwise check with PackageManager.
-        IPackageManager pm = AppGlobals.getPackageManager();
-        try {
-            if (callerPackage == null) {
-                final String[] packageNames = pm.getPackagesForUid(uid);
-                if (packageNames == null || packageNames.length == 0) {
-                    throw new IllegalArgumentException("Unable to determine caller package name");
-                }
-                // Instant Apps can't use shared uids, so its safe to only check the first package.
-                callerPackage = packageNames[0];
-            }
-            mAppOpsService.checkPackage(uid, callerPackage);
-            return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
-            return true;
-        }
-    }
-
     /**
      * @deprecated Use {@link #registerReceiverWithFeature}
      */
@@ -14629,657 +14215,12 @@
     public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
             String callerFeatureId, String receiverId, IIntentReceiver receiver,
             IntentFilter filter, String permission, int userId, int flags) {
-        traceRegistrationBegin(receiverId, receiver, filter, userId);
-        try {
-            return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
-                    receiverId, receiver, filter, permission, userId, flags);
-        } finally {
-            traceRegistrationEnd();
-        }
-    }
-
-    private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
-            IntentFilter filter, int userId) {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            final StringBuilder sb = new StringBuilder("registerReceiver: ");
-            sb.append(Binder.getCallingUid()); sb.append('/');
-            sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
-            final int actionsCount = filter.safeCountActions();
-            if (actionsCount > 0) {
-                for (int i = 0; i < actionsCount; ++i) {
-                    sb.append(filter.getAction(i));
-                    if (i != actionsCount - 1) sb.append(',');
-                }
-            } else {
-                sb.append("null");
-            }
-            sb.append('/');
-            sb.append('u'); sb.append(userId); sb.append('/');
-            sb.append(receiver == null ? "null" : receiver.asBinder());
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
-        }
-    }
-
-    private static void traceRegistrationEnd() {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
-    private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
-            String callerPackage, String callerFeatureId, String receiverId,
-            IIntentReceiver receiver, IntentFilter filter, String permission,
-            int userId, int flags) {
-        enforceNotIsolatedCaller("registerReceiver");
-        ArrayList<StickyBroadcast> stickyBroadcasts = null;
-        ProcessRecord callerApp = null;
-        final boolean visibleToInstantApps
-                = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
-
-        int callingUid;
-        int callingPid;
-        boolean instantApp;
-        synchronized (mProcLock) {
-            callerApp = getRecordForAppLOSP(caller);
-            if (callerApp == null) {
-                Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
-                return null;
-            }
-            if (callerApp.info.uid != SYSTEM_UID
-                    && !callerApp.getPkgList().containsKey(callerPackage)
-                    && !"android".equals(callerPackage)) {
-                throw new SecurityException("Given caller package " + callerPackage
-                        + " is not running in process " + callerApp);
-            }
-            callingUid = callerApp.info.uid;
-            callingPid = callerApp.getPid();
-
-            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
-        }
-        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
-                ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
-
-        // Warn if system internals are registering for important broadcasts
-        // without also using a priority to ensure they process the event
-        // before normal apps hear about it
-        if (UserHandle.isCore(callingUid)) {
-            final int priority = filter.getPriority();
-            final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
-                    || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
-            if (!systemPriority) {
-                final int N = filter.countActions();
-                for (int i = 0; i < N; i++) {
-                    // TODO: expand to additional important broadcasts over time
-                    final String action = filter.getAction(i);
-                    if (action.startsWith("android.intent.action.USER_")
-                            || action.startsWith("android.intent.action.PACKAGE_")
-                            || action.startsWith("android.intent.action.UID_")
-                            || action.startsWith("android.intent.action.EXTERNAL_")
-                            || action.startsWith("android.bluetooth.")
-                            || action.equals(Intent.ACTION_SHUTDOWN)) {
-                        if (DEBUG_BROADCAST) {
-                            Slog.wtf(TAG,
-                                    "System internals registering for " + filter.toLongString()
-                                            + " with app priority; this will race with apps!",
-                                    new Throwable());
-                        }
-
-                        // When undefined, assume that system internals need
-                        // to hear about the event first; they can use
-                        // SYSTEM_LOW_PRIORITY if they need to hear last
-                        if (priority == 0) {
-                            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-
-        Iterator<String> actions = filter.actionsIterator();
-        if (actions == null) {
-            ArrayList<String> noAction = new ArrayList<String>(1);
-            noAction.add(null);
-            actions = noAction.iterator();
-        }
-        boolean onlyProtectedBroadcasts = true;
-
-        // Collect stickies of users and check if broadcast is only registered for protected
-        // broadcasts
-        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
-        synchronized (mStickyBroadcasts) {
-            while (actions.hasNext()) {
-                String action = actions.next();
-                for (int id : userIds) {
-                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                            mStickyBroadcasts.get(id);
-                    if (stickies != null) {
-                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
-                        if (broadcasts != null) {
-                            if (stickyBroadcasts == null) {
-                                stickyBroadcasts = new ArrayList<>();
-                            }
-                            stickyBroadcasts.addAll(broadcasts);
-                        }
-                    }
-                }
-                if (onlyProtectedBroadcasts) {
-                    try {
-                        onlyProtectedBroadcasts &=
-                                AppGlobals.getPackageManager().isProtectedBroadcast(action);
-                    } catch (RemoteException e) {
-                        onlyProtectedBroadcasts = false;
-                        Slog.w(TAG, "Remote exception", e);
-                    }
-                }
-            }
-        }
-
-        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
-            SdkSandboxManagerLocal sdkSandboxManagerLocal =
-                    LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
-            if (sdkSandboxManagerLocal == null) {
-                throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
-                        + " whether SDK sandbox uid can register to broadcast receivers.");
-            }
-            if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
-                    /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
-                throw new SecurityException("SDK sandbox not allowed to register receiver"
-                        + " with the given IntentFilter: " + filter.toLongString());
-            }
-        }
-
-        // If the change is enabled, but neither exported or not exported is set, we need to log
-        // an error so the consumer can know to explicitly set the value for their flag.
-        // If the caller is registering for a sticky broadcast with a null receiver, we won't
-        // require a flag
-        final boolean explicitExportStateDefined =
-                (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
-        if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
-                (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
-            throw new IllegalArgumentException(
-                    "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
-                            + "flag");
-        }
-
-        // Don't enforce the flag check if we're EITHER registering for only protected
-        // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
-        // not be used generally, so we will be marking them as exported by default
-        boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
-                DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
-
-        // A receiver that is visible to instant apps must also be exported.
-        final boolean unexportedReceiverVisibleToInstantApps =
-                ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
-                        (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
-        if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
-            throw new IllegalArgumentException(
-                    "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
-                            + "RECEIVER_NOT_EXPORTED flag");
-        }
-
-        if (!onlyProtectedBroadcasts) {
-            if (receiver == null && !explicitExportStateDefined) {
-                // sticky broadcast, no flag specified (flag isn't required)
-                flags |= Context.RECEIVER_EXPORTED;
-            } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
-                throw new SecurityException(
-                        callerPackage + ": One of RECEIVER_EXPORTED or "
-                                + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
-                                + "isn't being registered exclusively for system broadcasts");
-                // Assume default behavior-- flag check is not enforced
-            } else if (!requireExplicitFlagForDynamicReceivers && (
-                    (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
-                // Change is not enabled, assume exported unless otherwise specified.
-                flags |= Context.RECEIVER_EXPORTED;
-            }
-        } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
-            flags |= Context.RECEIVER_EXPORTED;
-        }
-
-        // Dynamic receivers are exported by default for versions prior to T
-        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
-
-        ArrayList<StickyBroadcast> allSticky = null;
-        if (stickyBroadcasts != null) {
-            final ContentResolver resolver = mContext.getContentResolver();
-            // Look for any matching sticky broadcasts...
-            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
-                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
-                Intent intent = broadcast.intent;
-                // Don't provided intents that aren't available to instant apps.
-                if (instantApp &&
-                        (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
-                    continue;
-                }
-                // If intent has scheme "content", it will need to access
-                // provider that needs to lock mProviderMap in ActivityThread
-                // and also it may need to wait application response, so we
-                // cannot lock ActivityManagerService here.
-                final int match;
-                if (Flags.avoidResolvingType()) {
-                    match = filter.match(intent.getAction(), broadcast.resolvedDataType,
-                        intent.getScheme(), intent.getData(), intent.getCategories(),
-                        TAG, false /* supportsWildcards */, null /* ignoreActions */,
-                        intent.getExtras());
-                } else {
-                    match = filter.match(resolver, intent, true, TAG);
-                }
-                if (match >= 0) {
-                    if (allSticky == null) {
-                        allSticky = new ArrayList<>();
-                    }
-                    allSticky.add(broadcast);
-                }
-            }
-        }
-
-        // The first sticky in the list is returned directly back to the client.
-        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
-        if (receiver == null) {
-            return sticky;
-        }
-
-        // SafetyNet logging for b/177931370. If any process other than system_server tries to
-        // listen to this broadcast action, then log it.
-        if (callingPid != Process.myPid()) {
-            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
-                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
-                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
-            }
-        }
-
-        synchronized (this) {
-            IApplicationThread thread;
-            if (callerApp != null && ((thread = callerApp.getThread()) == null
-                    || thread.asBinder() != caller.asBinder())) {
-                // Original caller already died
-                return null;
-            }
-            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
-            if (rl == null) {
-                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
-                        userId, receiver);
-                if (rl.app != null) {
-                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
-                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
-                        throw new IllegalStateException("Too many receivers, total of "
-                                + totalReceiversForApp + ", registered for pid: "
-                                + rl.pid + ", callerPackage: " + callerPackage);
-                    }
-                    rl.app.mReceivers.addReceiver(rl);
-                } else {
-                    try {
-                        receiver.asBinder().linkToDeath(rl, 0);
-                    } catch (RemoteException e) {
-                        return sticky;
-                    }
-                    rl.linkedToDeath = true;
-                }
-                mRegisteredReceivers.put(receiver.asBinder(), rl);
-            } else if (rl.uid != callingUid) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for uid " + callingUid
-                        + " was previously registered for uid " + rl.uid
-                        + " callerPackage is " + callerPackage);
-            } else if (rl.pid != callingPid) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for pid " + callingPid
-                        + " was previously registered for pid " + rl.pid
-                        + " callerPackage is " + callerPackage);
-            } else if (rl.userId != userId) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for user " + userId
-                        + " was previously registered for user " + rl.userId
-                        + " callerPackage is " + callerPackage);
-            }
-            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
-                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
-                    exported);
-            if (rl.containsFilter(filter)) {
-                Slog.w(TAG, "Receiver with filter " + filter
-                        + " already registered for pid " + rl.pid
-                        + ", callerPackage is " + callerPackage);
-            } else {
-                rl.add(bf);
-                if (!bf.debugCheck()) {
-                    Slog.w(TAG, "==> For Dynamic broadcast");
-                }
-                mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
-            }
-
-            // Enqueue broadcasts for all existing stickies that match
-            // this filter.
-            if (allSticky != null) {
-                ArrayList receivers = new ArrayList();
-                receivers.add(bf);
-                sticky = null;
-
-                final int stickyCount = allSticky.size();
-                for (int i = 0; i < stickyCount; i++) {
-                    final StickyBroadcast broadcast = allSticky.get(i);
-                    final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
-                    // TODO(b/281889567): consider using checkComponentPermission instead of
-                    //  canAccessUnexportedComponents
-                    if (sticky == null && (exported || originalStickyCallingUid == callingUid
-                            || ActivityManager.canAccessUnexportedComponents(
-                            originalStickyCallingUid))) {
-                        sticky = broadcast.intent;
-                    }
-                    BroadcastQueue queue = mBroadcastQueue;
-                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
-                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
-                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
-                            receivers, null, null, 0, null, null, false, true, true, -1,
-                            originalStickyCallingUid, BackgroundStartPrivileges.NONE,
-                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
-                            null /* filterExtrasForReceiver */,
-                            broadcast.originalCallingAppProcessState);
-                    queue.enqueueBroadcastLocked(r);
-                }
-            }
-
-            return sticky;
-        }
+        return mBroadcastController.registerReceiverWithFeature(caller, callerPackage,
+                callerFeatureId, receiverId, receiver, filter, permission, userId, flags);
     }
 
     public void unregisterReceiver(IIntentReceiver receiver) {
-        traceUnregistrationBegin(receiver);
-        try {
-            unregisterReceiverTraced(receiver);
-        } finally {
-            traceUnregistrationEnd();
-        }
-    }
-
-    private static void traceUnregistrationBegin(IIntentReceiver receiver) {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
-                            receiver == null ? "null" : receiver.asBinder()));
-        }
-    }
-
-    private static void traceUnregistrationEnd() {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
-    private void unregisterReceiverTraced(IIntentReceiver receiver) {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
-
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            boolean doTrim = false;
-            synchronized(this) {
-                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
-                if (rl != null) {
-                    final BroadcastRecord r = rl.curBroadcast;
-                    if (r != null) {
-                        final boolean doNext = r.queue.finishReceiverLocked(
-                                rl.app, r.resultCode, r.resultData, r.resultExtras,
-                                r.resultAbort, false);
-                        if (doNext) {
-                            doTrim = true;
-                        }
-                    }
-                    if (rl.app != null) {
-                        rl.app.mReceivers.removeReceiver(rl);
-                    }
-                    removeReceiverLocked(rl);
-                    if (rl.linkedToDeath) {
-                        rl.linkedToDeath = false;
-                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
-                    }
-                }
-
-                // If we actually concluded any broadcasts, we might now be able
-                // to trim the recipients' apps from our working set
-                if (doTrim) {
-                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
-                    return;
-                }
-            }
-
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    void removeReceiverLocked(ReceiverList rl) {
-        mRegisteredReceivers.remove(rl.receiver.asBinder());
-        for (int i = rl.size() - 1; i >= 0; i--) {
-            mReceiverResolver.removeFilter(rl.get(i));
-        }
-    }
-
-    private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
-        mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
-    }
-
-    private List<ResolveInfo> collectReceiverComponents(
-            Intent intent, String resolvedType, int callingUid, int callingPid,
-            int[] users, int[] broadcastAllowList) {
-        // TODO: come back and remove this assumption to triage all broadcasts
-        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
-
-        List<ResolveInfo> receivers = null;
-        HashSet<ComponentName> singleUserReceivers = null;
-        boolean scannedFirstReceivers = false;
-        for (int user : users) {
-            // Skip users that have Shell restrictions
-            if (callingUid == SHELL_UID
-                    && mUserController.hasUserRestriction(
-                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
-                continue;
-            }
-            List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
-                    intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
-            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
-                // If this is not the system user, we need to check for
-                // any receivers that should be filtered out.
-                for (int i = 0; i < newReceivers.size(); i++) {
-                    ResolveInfo ri = newReceivers.get(i);
-                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
-                        newReceivers.remove(i);
-                        i--;
-                    }
-                }
-            }
-            // Replace the alias receivers with their targets.
-            if (newReceivers != null) {
-                for (int i = newReceivers.size() - 1; i >= 0; i--) {
-                    final ResolveInfo ri = newReceivers.get(i);
-                    final Resolution<ResolveInfo> resolution =
-                            mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
-                                    pmFlags, user, callingUid, callingPid);
-                    if (resolution == null) {
-                        // It was an alias, but the target was not found.
-                        newReceivers.remove(i);
-                        continue;
-                    }
-                    if (resolution.isAlias()) {
-                        newReceivers.set(i, resolution.getTarget());
-                    }
-                }
-            }
-            if (newReceivers != null && newReceivers.size() == 0) {
-                newReceivers = null;
-            }
-
-            if (receivers == null) {
-                receivers = newReceivers;
-            } else if (newReceivers != null) {
-                // We need to concatenate the additional receivers
-                // found with what we have do far.  This would be easy,
-                // but we also need to de-dup any receivers that are
-                // singleUser.
-                if (!scannedFirstReceivers) {
-                    // Collect any single user receivers we had already retrieved.
-                    scannedFirstReceivers = true;
-                    for (int i = 0; i < receivers.size(); i++) {
-                        ResolveInfo ri = receivers.get(i);
-                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                            ComponentName cn = new ComponentName(
-                                    ri.activityInfo.packageName, ri.activityInfo.name);
-                            if (singleUserReceivers == null) {
-                                singleUserReceivers = new HashSet<ComponentName>();
-                            }
-                            singleUserReceivers.add(cn);
-                        }
-                    }
-                }
-                // Add the new results to the existing results, tracking
-                // and de-dupping single user receivers.
-                for (int i = 0; i < newReceivers.size(); i++) {
-                    ResolveInfo ri = newReceivers.get(i);
-                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                        ComponentName cn = new ComponentName(
-                                ri.activityInfo.packageName, ri.activityInfo.name);
-                        if (singleUserReceivers == null) {
-                            singleUserReceivers = new HashSet<ComponentName>();
-                        }
-                        if (!singleUserReceivers.contains(cn)) {
-                            singleUserReceivers.add(cn);
-                            receivers.add(ri);
-                        }
-                    } else {
-                        receivers.add(ri);
-                    }
-                }
-            }
-        }
-        if (receivers != null && broadcastAllowList != null) {
-            for (int i = receivers.size() - 1; i >= 0; i--) {
-                final int receiverAppId = UserHandle.getAppId(
-                        receivers.get(i).activityInfo.applicationInfo.uid);
-                if (receiverAppId >= Process.FIRST_APPLICATION_UID
-                        && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
-                    receivers.remove(i);
-                }
-            }
-        }
-        return receivers;
-    }
-
-    private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
-            String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
-        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
-            // Don't yell about broadcasts sent via shell
-            return;
-        }
-
-        final String action = intent.getAction();
-        if (isProtectedBroadcast
-                || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
-                || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
-                || Intent.ACTION_MEDIA_BUTTON.equals(action)
-                || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
-                || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
-                || Intent.ACTION_MASTER_CLEAR.equals(action)
-                || Intent.ACTION_FACTORY_RESET.equals(action)
-                || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
-                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
-                || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
-                || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
-                || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
-                || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
-            // Broadcast is either protected, or it's a public action that
-            // we've relaxed, so it's fine for system internals to send.
-            return;
-        }
-
-        // This broadcast may be a problem...  but there are often system components that
-        // want to send an internal broadcast to themselves, which is annoying to have to
-        // explicitly list each action as a protected broadcast, so we will check for that
-        // one safe case and allow it: an explicit broadcast, only being received by something
-        // that has protected itself.
-        if (intent.getPackage() != null || intent.getComponent() != null) {
-            if (receivers == null || receivers.size() == 0) {
-                // Intent is explicit and there's no receivers.
-                // This happens, e.g. , when a system component sends a broadcast to
-                // its own runtime receiver, and there's no manifest receivers for it,
-                // because this method is called twice for each broadcast,
-                // for runtime receivers and manifest receivers and the later check would find
-                // no receivers.
-                return;
-            }
-            boolean allProtected = true;
-            for (int i = receivers.size()-1; i >= 0; i--) {
-                Object target = receivers.get(i);
-                if (target instanceof ResolveInfo) {
-                    ResolveInfo ri = (ResolveInfo)target;
-                    if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
-                        allProtected = false;
-                        break;
-                    }
-                } else {
-                    BroadcastFilter bf = (BroadcastFilter)target;
-                    if (bf.exported && bf.requiredPermission == null) {
-                        allProtected = false;
-                        break;
-                    }
-                }
-            }
-            if (allProtected) {
-                // All safe!
-                return;
-            }
-        }
-
-        // The vast majority of broadcasts sent from system internals
-        // should be protected to avoid security holes, so yell loudly
-        // to ensure we examine these cases.
-        if (callerApp != null) {
-            Log.wtf(TAG, "Sending non-protected broadcast " + action
-                            + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
-                    new Throwable());
-        } else {
-            Log.wtf(TAG, "Sending non-protected broadcast " + action
-                            + " from system uid " + UserHandle.formatUid(callingUid)
-                            + " pkg " + callerPackage,
-                    new Throwable());
-        }
-    }
-
-    // Apply permission policy around the use of specific broadcast options
-    void enforceBroadcastOptionPermissionsInternal(
-            @Nullable Bundle options, int callingUid) {
-        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
-                callingUid);
-    }
-
-    void enforceBroadcastOptionPermissionsInternal(
-            @Nullable BroadcastOptions options, int callingUid) {
-        if (options != null && callingUid != Process.SYSTEM_UID) {
-            if (options.isAlarmBroadcast()) {
-                if (DEBUG_BROADCAST_LIGHT) {
-                    Slog.w(TAG, "Non-system caller " + callingUid
-                            + " may not flag broadcast as alarm");
-                }
-                throw new SecurityException(
-                        "Non-system callers may not flag broadcasts as alarm");
-            }
-            if (options.isInteractive()) {
-                enforceCallingPermission(
-                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
-                        "setInteractive");
-            }
-        }
+        mBroadcastController.unregisterReceiver(receiver);
     }
 
     @GuardedBy("this")
@@ -15290,1033 +14231,14 @@
             String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered,
             boolean sticky, int callingPid,
             int callingUid, int realCallingUid, int realCallingPid, int userId) {
-        return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
-                resolvedType, null, resultTo, resultCode, resultData, resultExtras,
+        return mBroadcastController.broadcastIntentLocked(callerApp, callerPackage, callerFeatureId,
+                intent, resolvedType, null, resultTo, resultCode, resultData, resultExtras,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
                 ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
                 BackgroundStartPrivileges.NONE,
                 null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
     }
 
-    @GuardedBy("this")
-    final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
-            @Nullable String callerFeatureId, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions,
-            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
-            boolean ordered, boolean sticky, int callingPid, int callingUid,
-            int realCallingUid, int realCallingPid, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
-        final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
-                callingUid, realCallingUid, userId);
-        try {
-            final BroadcastSentEventRecord broadcastSentEventRecord =
-                    new BroadcastSentEventRecord();
-            final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
-                    intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
-                    resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
-                    appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
-                    callingPid, callingUid, realCallingUid, realCallingPid, userId,
-                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
-                    broadcastSentEventRecord);
-            broadcastSentEventRecord.setResult(res);
-            broadcastSentEventRecord.logToStatsd();
-            return res;
-        } finally {
-            traceBroadcastIntentEnd(cookie);
-        }
-    }
-
-    private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
-            boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
-        if (!Flags.traceReceiverRegistration()) {
-            return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            final StringBuilder sb = new StringBuilder("broadcastIntent: ");
-            sb.append(callingUid); sb.append('/');
-            final String action = intent.getAction();
-            sb.append(action == null ? null : action); sb.append('/');
-            sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
-            sb.append(ordered ? "O" : "_");
-            sb.append(sticky ? "S" : "_");
-            sb.append(resultTo != null ? "C" : "_");
-            sb.append('/');
-            sb.append('u'); sb.append(userId);
-            if (callingUid != realCallingUid) {
-                sb.append('/');
-                sb.append("sender="); sb.append(realCallingUid);
-            }
-            return BroadcastQueue.traceBegin(sb.toString());
-        }
-        return 0;
-    }
-
-    private static void traceBroadcastIntentEnd(int cookie) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            BroadcastQueue.traceEnd(cookie);
-        }
-    }
-
-    @GuardedBy("this")
-    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
-            @Nullable String callerFeatureId, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions,
-            String[] excludedPermissions, String[] excludedPackages, int appOp,
-            BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
-            int callingUid, int realCallingUid, int realCallingPid, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
-            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
-        // Ensure all internal loopers are registered for idle checks
-        BroadcastLoopers.addMyLooper();
-
-        if (Process.isSdkSandboxUid(realCallingUid)) {
-            final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
-                    SdkSandboxManagerLocal.class);
-            if (sdkSandboxManagerLocal == null) {
-                throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
-                        + " a broadcast from an SDK sandbox uid.");
-            }
-            if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
-                throw new SecurityException(
-                        "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
-                        + " uid. Given caller package " + callerPackage + " (pid=" + callingPid
-                        + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
-                        + ")");
-            }
-        }
-
-        if ((resultTo != null) && (resultToApp == null)) {
-            if (resultTo.asBinder() instanceof BinderProxy) {
-                // Warn when requesting results without a way to deliver them
-                Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
-                        + " with resultTo requires resultToApp", new Throwable());
-            } else {
-                // If not a BinderProxy above, then resultTo is an in-process
-                // receiver, so splice in system_server process
-                resultToApp = getProcessRecordLocked("system", SYSTEM_UID);
-            }
-        }
-
-        intent = new Intent(intent);
-        broadcastSentEventRecord.setIntent(intent);
-        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
-        broadcastSentEventRecord.setSenderUid(callingUid);
-        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
-        broadcastSentEventRecord.setSticky(sticky);
-        broadcastSentEventRecord.setOrdered(ordered);
-        broadcastSentEventRecord.setResultRequested(resultTo != null);
-        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
-        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
-        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
-                realCallingPid));
-
-        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
-        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
-        if (callerInstantApp) {
-            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-        }
-
-        if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
-                Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
-                        + "Assuming restrictive whitelist.");
-                broadcastAllowList = new int[]{};
-        }
-
-        // By default broadcasts do not go to stopped apps.
-        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-
-        // If we have not finished booting, don't allow this to launch new processes.
-        if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        }
-
-        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
-                        + " ordered=" + ordered + " userid=" + userId
-                        + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
-        if ((resultTo != null) && !ordered) {
-            if (!UserHandle.isCore(callingUid)) {
-                String msg = "Unauthorized unordered resultTo broadcast "
-                             + intent + " sent from uid " + callingUid;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-        }
-
-        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
-                ALLOW_NON_FULL, "broadcast", callerPackage);
-
-        // Make sure that the user who is receiving this broadcast or its parent is running.
-        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
-            if ((callingUid != SYSTEM_UID
-                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
-                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
-                Slog.w(TAG, "Skipping broadcast of " + intent
-                        + ": user " + userId + " and its parent (if any) are stopped");
-                scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
-                        brOptions, callingUid, callerPackage);
-                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
-            }
-        }
-
-        final String action = intent.getAction();
-        if (brOptions != null) {
-            if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
-                // See if the caller is allowed to do this.  Note we are checking against
-                // the actual real caller (not whoever provided the operation as say a
-                // PendingIntent), because that who is actually supplied the arguments.
-                if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED
-                        && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED
-                        && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    String msg = "Permission Denial: " + intent.getAction()
-                            + " broadcast from " + callerPackage + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires "
-                            + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
-                            + START_ACTIVITIES_FROM_BACKGROUND + " or "
-                            + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                }
-            }
-            if (brOptions.isDontSendToRestrictedApps()
-                    && !isUidActiveLOSP(callingUid)
-                    && isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
-                Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
-                        + " has background restrictions");
-                return ActivityManager.START_CANCELED;
-            }
-            if (brOptions.allowsBackgroundActivityStarts()) {
-                // See if the caller is allowed to do this.  Note we are checking against
-                // the actual real caller (not whoever provided the operation as say a
-                // PendingIntent), because that who is actually supplied the arguments.
-                if (checkComponentPermission(
-                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    String msg = "Permission Denial: " + intent.getAction()
-                            + " broadcast from " + callerPackage + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires "
-                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                } else {
-                    // We set the token to null since if it wasn't for it we'd allow anyway here
-                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
-                }
-            }
-
-            if (brOptions.getIdForResponseEvent() > 0) {
-                enforcePermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
-                        callingPid, callingUid, "recordResponseEventWhileInBackground");
-            }
-        }
-
-        // Verify that protected broadcasts are only being sent by system code,
-        // and that system code is only sending protected broadcasts.
-        final boolean isProtectedBroadcast;
-        try {
-            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception", e);
-            scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                    userId, brOptions, callingUid, callerPackage);
-            return ActivityManager.BROADCAST_SUCCESS;
-        }
-
-        final boolean isCallerSystem;
-        switch (UserHandle.getAppId(callingUid)) {
-            case ROOT_UID:
-            case SYSTEM_UID:
-            case PHONE_UID:
-            case BLUETOOTH_UID:
-            case NFC_UID:
-            case SE_UID:
-            case NETWORK_STACK_UID:
-                isCallerSystem = true;
-                break;
-            default:
-                isCallerSystem = (callerApp != null) && callerApp.isPersistent();
-                break;
-        }
-
-        // First line security check before anything else: stop non-system apps from
-        // sending protected broadcasts.
-        if (!isCallerSystem) {
-            if (isProtectedBroadcast) {
-                String msg = "Permission Denial: not allowed to send broadcast "
-                        + action + " from pid="
-                        + callingPid + ", uid=" + callingUid;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-
-            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
-                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
-                // Special case for compatibility: we don't want apps to send this,
-                // but historically it has not been protected and apps may be using it
-                // to poke their own app widget.  So, instead of making it protected,
-                // just limit it to the caller.
-                if (callerPackage == null) {
-                    String msg = "Permission Denial: not allowed to send broadcast "
-                            + action + " from unknown caller.";
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                } else if (intent.getComponent() != null) {
-                    // They are good enough to send to an explicit component...  verify
-                    // it is being sent to the calling app.
-                    if (!intent.getComponent().getPackageName().equals(
-                            callerPackage)) {
-                        String msg = "Permission Denial: not allowed to send broadcast "
-                                + action + " to "
-                                + intent.getComponent().getPackageName() + " from "
-                                + callerPackage;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                } else {
-                    // Limit broadcast to their own package.
-                    intent.setPackage(callerPackage);
-                }
-            }
-        }
-
-        boolean timeoutExempt = false;
-
-        if (action != null) {
-            if (getBackgroundLaunchBroadcasts().contains(action)) {
-                if (DEBUG_BACKGROUND_CHECK) {
-                    Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
-                }
-                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            }
-
-            // TODO: b/329211459 - Remove this after background remote intent is fixed.
-            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
-                    && getWearRemoteIntentAction().equals(action)) {
-                final int callerProcState = callerApp != null
-                        ? callerApp.getCurProcState()
-                        : ActivityManager.PROCESS_STATE_NONEXISTENT;
-                if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
-                        > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
-                    return ActivityManager.START_CANCELED;
-                }
-            }
-
-            switch (action) {
-                case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
-                    UserManagerInternal umInternal = LocalServices.getService(
-                            UserManagerInternal.class);
-                    UserInfo userInfo = umInternal.getUserInfo(userId);
-                    if (userInfo != null && userInfo.isCloneProfile()) {
-                        userId = umInternal.getProfileParentId(userId);
-                    }
-                    break;
-                case Intent.ACTION_UID_REMOVED:
-                case Intent.ACTION_PACKAGE_REMOVED:
-                case Intent.ACTION_PACKAGE_CHANGED:
-                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
-                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
-                case Intent.ACTION_PACKAGES_SUSPENDED:
-                case Intent.ACTION_PACKAGES_UNSUSPENDED:
-                    // Handle special intents: if this broadcast is from the package
-                    // manager about a package being removed, we need to remove all of
-                    // its activities from the history stack.
-                    if (checkComponentPermission(
-                            android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
-                            callingPid, callingUid, -1, true)
-                            != PackageManager.PERMISSION_GRANTED) {
-                        String msg = "Permission Denial: " + intent.getAction()
-                                + " broadcast from " + callerPackage + " (pid=" + callingPid
-                                + ", uid=" + callingUid + ")"
-                                + " requires "
-                                + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                    switch (action) {
-                        case Intent.ACTION_UID_REMOVED:
-                            final int uid = getUidFromIntent(intent);
-                            if (uid >= 0) {
-                                mBatteryStatsService.removeUid(uid);
-                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                                    mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
-                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
-                                } else {
-                                    mAppOpsService.uidRemoved(uid);
-                                    mServices.onUidRemovedLocked(uid);
-                                }
-                            }
-                            break;
-                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
-                            // If resources are unavailable just force stop all those packages
-                            // and flush the attribute cache as well.
-                            String list[] =
-                                    intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            if (list != null && list.length > 0) {
-                                for (int i = 0; i < list.length; i++) {
-                                    forceStopPackageLocked(list[i], -1, false, true, true,
-                                            false, false, false, userId, "storage unmount");
-                                }
-                                mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
-                                sendPackageBroadcastLocked(
-                                        ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
-                                        list, userId);
-                            }
-                            break;
-                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
-                            mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
-                            break;
-                        case Intent.ACTION_PACKAGE_REMOVED:
-                        case Intent.ACTION_PACKAGE_CHANGED:
-                            Uri data = intent.getData();
-                            String ssp;
-                            if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
-                                boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
-                                final boolean replacing =
-                                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                                final boolean killProcess =
-                                        !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
-                                final boolean fullUninstall = removed && !replacing;
-
-                                if (removed) {
-                                    if (killProcess) {
-                                        forceStopPackageLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                false, true, true, false, fullUninstall, false,
-                                                userId, "pkg removed");
-                                        getPackageManagerInternal()
-                                                .onPackageProcessKilledForUninstall(ssp);
-                                    } else {
-                                        // Kill any app zygotes always, since they can't fork new
-                                        // processes with references to the old code
-                                        forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                userId);
-                                    }
-                                    final int cmd = killProcess
-                                            ? ApplicationThreadConstants.PACKAGE_REMOVED
-                                            : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
-                                    sendPackageBroadcastLocked(cmd,
-                                            new String[] {ssp}, userId);
-                                    if (fullUninstall) {
-                                        // Remove all permissions granted from/to this package
-                                        mUgmInternal.removeUriPermissionsForPackage(ssp, userId,
-                                                true, false);
-
-                                        mAtmInternal.removeRecentTasksByPackageName(ssp, userId);
-
-                                        mServices.forceStopPackageLocked(ssp, userId);
-                                        mAtmInternal.onPackageUninstalled(ssp, userId);
-                                        mBatteryStatsService.notePackageUninstalled(ssp);
-                                    }
-                                } else {
-                                    if (killProcess) {
-                                        int reason;
-                                        int subReason;
-                                        if (replacing) {
-                                            reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
-                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-                                        } else {
-                                            reason =
-                                                    ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
-                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-                                        }
-
-                                        final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
-                                                -1);
-                                        synchronized (mProcLock) {
-                                            mProcessList.killPackageProcessesLSP(ssp,
-                                                    UserHandle.getAppId(extraUid),
-                                                    userId, ProcessList.INVALID_ADJ,
-                                                    reason,
-                                                    subReason,
-                                                    "change " + ssp);
-                                        }
-                                    }
-                                    cleanupDisabledPackageComponentsLocked(ssp, userId,
-                                            intent.getStringArrayExtra(
-                                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
-                                    mServices.schedulePendingServiceStartLocked(ssp, userId);
-                                }
-                            }
-                            break;
-                        case Intent.ACTION_PACKAGES_SUSPENDED:
-                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
-                            final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
-                                    intent.getAction());
-                            final String[] packageNames = intent.getStringArrayExtra(
-                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            final int userIdExtra = intent.getIntExtra(
-                                    Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
-                            mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
-                                    userIdExtra);
-
-                            final boolean quarantined = intent.getBooleanExtra(
-                                    Intent.EXTRA_QUARANTINED, false);
-                            if (suspended && quarantined && packageNames != null) {
-                                for (int i = 0; i < packageNames.length; i++) {
-                                    forceStopPackage(packageNames[i], userId,
-                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
-                                }
-                            }
-
-                            break;
-                    }
-                    break;
-                case Intent.ACTION_PACKAGE_REPLACED:
-                {
-                    final Uri data = intent.getData();
-                    final String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        ApplicationInfo aInfo = null;
-                        try {
-                            aInfo = AppGlobals.getPackageManager()
-                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
-                        } catch (RemoteException ignore) {}
-                        if (aInfo == null) {
-                            Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
-                                    + " ssp=" + ssp + " data=" + data);
-                            scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                                    userId, brOptions, callingUid, callerPackage);
-                            return ActivityManager.BROADCAST_SUCCESS;
-                        }
-                        updateAssociationForApp(aInfo);
-                        mAtmInternal.onPackageReplaced(aInfo);
-                        mServices.updateServiceApplicationInfoLocked(aInfo);
-                        sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
-                                new String[] {ssp}, userId);
-                    }
-                    break;
-                }
-                case Intent.ACTION_PACKAGE_ADDED:
-                {
-                    // Special case for adding a package: by default turn on compatibility mode.
-                    Uri data = intent.getData();
-                    String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        final boolean replacing =
-                                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                        mAtmInternal.onPackageAdded(ssp, replacing);
-
-                        try {
-                            ApplicationInfo ai = AppGlobals.getPackageManager().
-                                    getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
-                            mBatteryStatsService.notePackageInstalled(ssp,
-                                    ai != null ? ai.longVersionCode : 0);
-                        } catch (RemoteException e) {
-                        }
-                    }
-                    break;
-                }
-                case Intent.ACTION_PACKAGE_DATA_CLEARED:
-                {
-                    Uri data = intent.getData();
-                    String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        mAtmInternal.onPackageDataCleared(ssp, userId);
-                    }
-                    break;
-                }
-                case Intent.ACTION_TIMEZONE_CHANGED:
-                    // If this is the time zone changed action, queue up a message that will reset
-                    // the timezone of all currently running processes. This message will get
-                    // queued up before the broadcast happens.
-                    mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
-                    break;
-                case Intent.ACTION_TIME_CHANGED:
-                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
-                    // the tri-state value it may contain and "unknown".
-                    // For convenience we re-use the Intent extra values.
-                    final int NO_EXTRA_VALUE_FOUND = -1;
-                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
-                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
-                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
-                    // Only send a message if the time preference is available.
-                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
-                        Message updateTimePreferenceMsg =
-                                mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
-                                        timeFormatPreferenceMsgValue, 0);
-                        mHandler.sendMessage(updateTimePreferenceMsg);
-                    }
-                    mBatteryStatsService.noteCurrentTimeChanged();
-                    break;
-                case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
-                    mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
-                    break;
-                case Proxy.PROXY_CHANGE_ACTION:
-                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
-                    break;
-                case android.hardware.Camera.ACTION_NEW_PICTURE:
-                case android.hardware.Camera.ACTION_NEW_VIDEO:
-                    // In N we just turned these off; in O we are turing them back on partly,
-                    // only for registered receivers.  This will still address the main problem
-                    // (a spam of apps waking up when a picture is taken putting significant
-                    // memory pressure on the system at a bad point), while still allowing apps
-                    // that are already actively running to know about this happening.
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    break;
-                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
-                    mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
-                    break;
-                case "com.android.launcher.action.INSTALL_SHORTCUT":
-                    // As of O, we no longer support this broadcasts, even for pre-O apps.
-                    // Apps should now be using ShortcutManager.pinRequestShortcut().
-                    Log.w(TAG, "Broadcast " + action
-                            + " no longer supported. It will not be delivered.");
-                    scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                            userId, brOptions, callingUid, callerPackage);
-                    return ActivityManager.BROADCAST_SUCCESS;
-                case Intent.ACTION_PRE_BOOT_COMPLETED:
-                    timeoutExempt = true;
-                    break;
-                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
-                    if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
-                            callerPackage)) {
-                        scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                                userId, brOptions, callingUid, callerPackage);
-                        // Returning success seems to be the pattern here
-                        return ActivityManager.BROADCAST_SUCCESS;
-                    }
-                    break;
-            }
-
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                final int uid = getUidFromIntent(intent);
-                if (uid != -1) {
-                    final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
-                    if (uidRec != null) {
-                        uidRec.updateHasInternetPermission();
-                    }
-                }
-            }
-        }
-
-        // Add to the sticky list if requested.
-        if (sticky) {
-            if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
-                    callingPid, callingUid)
-                    != PackageManager.PERMISSION_GRANTED) {
-                String msg =
-                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
-                            + " pid="
-                                + callingPid
-                                + ", uid="
-                                + callingUid
-                                + " requires "
-                                + android.Manifest.permission.BROADCAST_STICKY;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-            if (requiredPermissions != null && requiredPermissions.length > 0) {
-                Slog.w(TAG, "Can't broadcast sticky intent " + intent
-                        + " and enforce permissions " + Arrays.toString(requiredPermissions));
-                scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                        userId, brOptions, callingUid, callerPackage);
-                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
-            }
-            if (intent.getComponent() != null) {
-                throw new SecurityException(
-                        "Sticky broadcasts can't target a specific component");
-            }
-            synchronized (mStickyBroadcasts) {
-                // We use userId directly here, since the "all" target is maintained
-                // as a separate set of sticky broadcasts.
-                if (userId != UserHandle.USER_ALL) {
-                    // But first, if this is not a broadcast to all users, then
-                    // make sure it doesn't conflict with an existing broadcast to
-                    // all users.
-                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
-                            UserHandle.USER_ALL);
-                    if (stickies != null) {
-                        ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                        if (list != null) {
-                            int N = list.size();
-                            int i;
-                            for (i = 0; i < N; i++) {
-                                if (intent.filterEquals(list.get(i).intent)) {
-                                    throw new IllegalArgumentException("Sticky broadcast " + intent
-                                            + " for user " + userId
-                                            + " conflicts with existing global broadcast");
-                                }
-                            }
-                        }
-                    }
-                }
-                ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                        mStickyBroadcasts.get(userId);
-                if (stickies == null) {
-                    stickies = new ArrayMap<>();
-                    mStickyBroadcasts.put(userId, stickies);
-                }
-                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                if (list == null) {
-                    list = new ArrayList<>();
-                    stickies.put(intent.getAction(), list);
-                }
-                final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
-                        callingUid, brOptions, resultTo, ordered,
-                        BroadcastRecord.calculateUrgent(intent, brOptions));
-                final int stickiesCount = list.size();
-                int i;
-                for (i = 0; i < stickiesCount; i++) {
-                    if (intent.filterEquals(list.get(i).intent)) {
-                        // This sticky already exists, replace it.
-                        list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
-                                callingUid, callerAppProcessState, resolvedType));
-                        break;
-                    }
-                }
-                if (i >= stickiesCount) {
-                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
-                            callingUid, callerAppProcessState, resolvedType));
-                }
-            }
-        }
-
-        int[] users;
-        if (userId == UserHandle.USER_ALL) {
-            // Caller wants broadcast to go to all started users.
-            users = mUserController.getStartedUserArray();
-        } else {
-            // Caller wants broadcast to go to one specific user.
-            users = new int[] {userId};
-        }
-
-        var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
-                true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
-        args.platformCompat = mPlatformCompat;
-
-        // Figure out who all will receive this broadcast.
-        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
-        List receivers = null;
-        List<BroadcastFilter> registeredReceivers = null;
-        // Need to resolve the intent to interested receivers...
-        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-            receivers = collectReceiverComponents(
-                    intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
-        }
-        if (intent.getComponent() == null) {
-            final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
-            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
-                // Query one target user at a time, excluding shell-restricted users
-                for (int i = 0; i < users.length; i++) {
-                    if (mUserController.hasUserRestriction(
-                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
-                        continue;
-                    }
-                    List<BroadcastFilter> registeredReceiversForUser =
-                            mReceiverResolver.queryIntent(snapshot, intent,
-                                    resolvedType, false /*defaultOnly*/, users[i]);
-                    if (registeredReceivers == null) {
-                        registeredReceivers = registeredReceiversForUser;
-                    } else if (registeredReceiversForUser != null) {
-                        registeredReceivers.addAll(registeredReceiversForUser);
-                    }
-                }
-            } else {
-                registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
-                        resolvedType, false /*defaultOnly*/, userId);
-            }
-            if (registeredReceivers != null) {
-                SaferIntentUtils.blockNullAction(args, registeredReceivers);
-            }
-        }
-        BroadcastQueue.traceEnd(cookie);
-
-        final boolean replacePending =
-                (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
-                + " replacePending=" + replacePending);
-        if (registeredReceivers != null && broadcastAllowList != null) {
-            // if a uid whitelist was provided, remove anything in the application space that wasn't
-            // in it.
-            for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
-                final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
-                if (owningAppId >= Process.FIRST_APPLICATION_UID
-                        && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
-                    registeredReceivers.remove(i);
-                }
-            }
-        }
-
-        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
-
-        // Merge into one list.
-        int ir = 0;
-        if (receivers != null) {
-            // A special case for PACKAGE_ADDED: do not allow the package
-            // being added to see this broadcast.  This prevents them from
-            // using this as a back door to get run as soon as they are
-            // installed.  Maybe in the future we want to have a special install
-            // broadcast or such for apps, but we'd like to deliberately make
-            // this decision.
-            String skipPackages[] = null;
-            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
-                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
-                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
-                Uri data = intent.getData();
-                if (data != null) {
-                    String pkgName = data.getSchemeSpecificPart();
-                    if (pkgName != null) {
-                        skipPackages = new String[] { pkgName };
-                    }
-                }
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
-                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            }
-            if (skipPackages != null && (skipPackages.length > 0)) {
-                for (String skipPackage : skipPackages) {
-                    if (skipPackage != null) {
-                        int NT = receivers.size();
-                        for (int it=0; it<NT; it++) {
-                            ResolveInfo curt = (ResolveInfo)receivers.get(it);
-                            if (curt.activityInfo.packageName.equals(skipPackage)) {
-                                receivers.remove(it);
-                                it--;
-                                NT--;
-                            }
-                        }
-                    }
-                }
-            }
-
-            int NT = receivers != null ? receivers.size() : 0;
-            int it = 0;
-            ResolveInfo curt = null;
-            BroadcastFilter curr = null;
-            while (it < NT && ir < NR) {
-                if (curt == null) {
-                    curt = (ResolveInfo)receivers.get(it);
-                }
-                if (curr == null) {
-                    curr = registeredReceivers.get(ir);
-                }
-                if (curr.getPriority() >= curt.priority) {
-                    // Insert this broadcast record into the final list.
-                    receivers.add(it, curr);
-                    ir++;
-                    curr = null;
-                    it++;
-                    NT++;
-                } else {
-                    // Skip to the next ResolveInfo in the final list.
-                    it++;
-                    curt = null;
-                }
-            }
-        }
-        while (ir < NR) {
-            if (receivers == null) {
-                receivers = new ArrayList();
-            }
-            receivers.add(registeredReceivers.get(ir));
-            ir++;
-        }
-
-        if (isCallerSystem) {
-            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
-                    isProtectedBroadcast, receivers);
-        }
-
-        if ((receivers != null && receivers.size() > 0)
-                || resultTo != null) {
-            BroadcastQueue queue = mBroadcastQueue;
-            SaferIntentUtils.filterNonExportedComponents(args, receivers);
-            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
-                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
-                    receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
-                    ordered, sticky, false, userId,
-                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
-                    callerAppProcessState);
-            broadcastSentEventRecord.setBroadcastRecord(r);
-
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
-            queue.enqueueBroadcastLocked(r);
-        } else {
-            // There was nobody interested in the broadcast, but we still want to record
-            // that it happened.
-            if (intent.getComponent() == null && intent.getPackage() == null
-                    && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                // This was an implicit broadcast... let's record it for posterity.
-                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
-            }
-        }
-
-        return ActivityManager.BROADCAST_SUCCESS;
-    }
-
-    @GuardedBy("this")
-    private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
-            Intent intent, int userId, BroadcastOptions options, int callingUid,
-            String callingPackage) {
-        if (resultTo == null) {
-            return;
-        }
-        final ProcessRecord app = resultToApp;
-        final IApplicationThread thread  = (app != null) ? app.getOnewayThread() : null;
-        if (thread != null) {
-            try {
-                final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
-                thread.scheduleRegisteredReceiver(
-                        resultTo, intent, Activity.RESULT_CANCELED, null, null,
-                        false, false, true, userId, app.mState.getReportedProcState(),
-                        shareIdentity ? callingUid : Process.INVALID_UID,
-                        shareIdentity ? callingPackage : null);
-            } catch (RemoteException e) {
-                final String msg = "Failed to schedule result of " + intent + " via "
-                        + app + ": " + e;
-                app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                Slog.d(TAG, msg);
-            }
-        }
-    }
-
-    @GuardedBy("this")
-    private int getRealProcessStateLocked(ProcessRecord app, int pid) {
-        if (app == null) {
-            synchronized (mPidsSelfLocked) {
-                app = mPidsSelfLocked.get(pid);
-            }
-        }
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            return app.mState.getCurProcState();
-        }
-        return PROCESS_STATE_NONEXISTENT;
-    }
-
-    @GuardedBy("this")
-    private int getRealUidStateLocked(ProcessRecord app, int pid) {
-        if (app == null) {
-            synchronized (mPidsSelfLocked) {
-                app = mPidsSelfLocked.get(pid);
-            }
-        }
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            final UidRecord uidRecord = app.getUidRecord();
-            if (uidRecord != null) {
-                return uidRecord.getCurProcState();
-            }
-        }
-        return PROCESS_STATE_NONEXISTENT;
-    }
-
-    @VisibleForTesting
-    ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
-        synchronized (mStickyBroadcasts) {
-            final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
-                    mStickyBroadcasts.get(userId);
-            if (stickyBroadcasts == null) {
-                return null;
-            }
-            return stickyBroadcasts.get(action);
-        }
-    }
-
-    /**
-     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
-     */
-    private int getUidFromIntent(Intent intent) {
-        if (intent == null) {
-            return -1;
-        }
-        final Bundle intentExtras = intent.getExtras();
-        return intent.hasExtra(Intent.EXTRA_UID)
-                ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
-    }
-
-    final void rotateBroadcastStatsIfNeededLocked() {
-        final long now = SystemClock.elapsedRealtime();
-        if (mCurBroadcastStats == null ||
-                (mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
-            mLastBroadcastStats = mCurBroadcastStats;
-            if (mLastBroadcastStats != null) {
-                mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
-                mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
-            }
-            mCurBroadcastStats = new BroadcastStats();
-        }
-    }
-
-    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
-            int skipCount, long dispatchTime) {
-        rotateBroadcastStatsIfNeededLocked();
-        mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
-    }
-
-    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
-        rotateBroadcastStatsIfNeededLocked();
-        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
-    }
-
-    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-    }
-
-    final Intent verifyBroadcastLocked(Intent intent) {
-        if (intent != null) {
-            intent.prepareToEnterSystemServer();
-        }
-
-        int flags = intent.getFlags();
-
-        if (!mProcessesReady) {
-            // if the caller really truly claims to know what they're doing, go
-            // ahead and allow the broadcast without launching any receivers
-            if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
-                // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
-            } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
-                        + " before boot completion");
-                throw new IllegalStateException("Cannot broadcast before boot completed");
-            }
-        }
-
-        if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
-            throw new IllegalArgumentException(
-                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
-        }
-
-        if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
-            switch (Binder.getCallingUid()) {
-                case ROOT_UID:
-                case SHELL_UID:
-                    break;
-                default:
-                    Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
-                            + Binder.getCallingUid());
-                    intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
-                    break;
-            }
-        }
-
-        return intent;
-    }
-
     /**
      * @deprecated Use {@link #broadcastIntentWithFeature}
      */
@@ -16338,110 +14260,14 @@
             String[] requiredPermissions, String[] excludedPermissions,
             String[] excludedPackages, int appOp, Bundle bOptions,
             boolean serialized, boolean sticky, int userId) {
-        enforceNotIsolatedCaller("broadcastIntent");
-
-        synchronized(this) {
-            intent = verifyBroadcastLocked(intent);
-
-            final ProcessRecord callerApp = getRecordForAppLOSP(caller);
-            final int callingPid = Binder.getCallingPid();
-            final int callingUid = Binder.getCallingUid();
-
-            // We're delivering the result to the caller
-            final ProcessRecord resultToApp = callerApp;
-
-            // Permission regimes around sender-supplied broadcast options.
-            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
-
-            final ComponentName cn = intent.getComponent();
-
-            Trace.traceBegin(
-                    Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
-
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                return broadcastIntentLocked(callerApp,
-                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
-                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
-                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
-                        appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
-                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            }
-        }
-    }
-
-    // Not the binder call surface
-    int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
-            int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
-            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
-            boolean serialized, boolean sticky, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList) {
-        synchronized(this) {
-            intent = verifyBroadcastLocked(intent);
-
-            final long origId = Binder.clearCallingIdentity();
-            String[] requiredPermissions = requiredPermission == null ? null
-                    : new String[] {requiredPermission};
-            try {
-                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
-                        resultToApp, resultTo, resultCode, resultData, resultExtras,
-                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
-                        uid, realCallingUid, realCallingPid, userId,
-                        backgroundStartPrivileges, broadcastAllowList,
-                        null /* filterExtrasForReceiver */);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
+        return mBroadcastController.broadcastIntentWithFeature(caller, callingFeatureId, intent,
+                resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+                excludedPermissions, excludedPackages, appOp, bOptions, serialized, sticky, userId);
     }
 
     @Override
     public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
-        // Refuse possible leaked file descriptors
-        if (intent != null && intent.hasFileDescriptors() == true) {
-            throw new IllegalArgumentException("File descriptors passed in Intent");
-        }
-
-        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
-
-        if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: unbroadcastIntent() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.BROADCAST_STICKY;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        synchronized (mStickyBroadcasts) {
-            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
-            if (stickies != null) {
-                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                if (list != null) {
-                    int N = list.size();
-                    int i;
-                    for (i = 0; i < N; i++) {
-                        if (intent.filterEquals(list.get(i).intent)) {
-                            list.remove(i);
-                            break;
-                        }
-                    }
-                    if (list.size() <= 0) {
-                        stickies.remove(intent.getAction());
-                    }
-                }
-                if (stickies.size() <= 0) {
-                    mStickyBroadcasts.remove(userId);
-                }
-            }
-        }
+        mBroadcastController.unbroadcastIntent(caller, intent, userId);
     }
 
     void backgroundServicesFinishedLocked(int userId) {
@@ -16450,31 +14276,32 @@
 
     public void finishReceiver(IBinder caller, int resultCode, String resultData,
             Bundle resultExtras, boolean resultAbort, int flags) {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+        mBroadcastController.finishReceiver(caller, resultCode, resultData, resultExtras,
+                resultAbort, flags);
+    }
 
-        // Refuse possible leaked file descriptors
-        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
-            throw new IllegalArgumentException("File descriptors passed in Bundle");
-        }
+    @VisibleForTesting
+    ArrayList<BroadcastController.StickyBroadcast> getStickyBroadcastsForTest(String action,
+            int userId) {
+        return mBroadcastController.getStickyBroadcastsForTest(action, userId);
+    }
 
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized(this) {
-                final ProcessRecord callerApp = getRecordForAppLOSP(caller);
-                if (callerApp == null) {
-                    Slog.w(TAG, "finishReceiver: no app for " + caller);
-                    return;
-                }
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        mBroadcastController.notifyBroadcastFinishedLocked(original);
+    }
 
-                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
-                        resultData, resultExtras, resultAbort, true);
-                // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
-            }
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        mBroadcastController.addBroadcastStatLocked(action, srcPackage, receiveCount, skipCount,
+                dispatchTime);
+    }
 
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        mBroadcastController.addBackgroundCheckViolationLocked(action, targetPackage);
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mBroadcastController.removeReceiverLocked(rl);
     }
 
     // =========================================================
@@ -17839,7 +15666,7 @@
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
+    void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -18829,7 +16656,7 @@
 
         @Override
         public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
-            enforceBroadcastOptionPermissionsInternal(options, callingUid);
+            mBroadcastController.enforceBroadcastOptionPermissionsInternal(options, callingUid);
         }
 
         /**
@@ -19219,7 +17046,7 @@
                 @Nullable int[] broadcastAllowList) {
             synchronized (ActivityManagerService.this) {
                 final ProcessRecord resultToApp = getRecordForAppLOSP(resultToThread);
-                return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId,
+                return mBroadcastController.broadcastIntentInPackage(packageName, featureId,
                         uid, realCallingUid, realCallingPid, intent, resolvedType, resultToApp,
                         resultTo, resultCode, resultData, resultExtras, requiredPermission,
                         bOptions, serialized, sticky, userId,
@@ -19236,13 +17063,13 @@
                 @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
                 @Nullable Bundle bOptions) {
             synchronized (ActivityManagerService.this) {
-                intent = verifyBroadcastLocked(intent);
+                intent = mBroadcastController.verifyBroadcastLocked(intent);
 
                 final int callingPid = Binder.getCallingPid();
                 final int callingUid = Binder.getCallingUid();
                 final long origId = Binder.clearCallingIdentity();
                 try {
-                    return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
+                    return mBroadcastController.broadcastIntentLocked(null /*callerApp*/,
                             null /*callerPackage*/, null /*callingFeatureId*/, intent,
                             null /* resolvedType */, null /* resultToApp */, resultTo,
                             0 /* resultCode */, null /* resultData */,
@@ -21159,26 +18986,6 @@
         }
     }
 
-    /**
-     * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
-     * message on {@code pw} when it cannot be parsed.
-     *
-     * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
-     */
-    private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
-        if (index > args.length) {
-            pw.println("Missing argument");
-            return invalidValue;
-        }
-        String arg = args[index];
-        try {
-            return Integer.parseInt(arg);
-        } catch (Exception e) {
-            pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
-            return invalidValue;
-        }
-    }
-
     private void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken,
             @MediaProjectionTokenEvent int event) {
         synchronized (mMediaProjectionTokenMap) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
new file mode 100644
index 0000000..32026b2
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -0,0 +1,2410 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.os.Process.BLUETOOTH_UID;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.PHONE_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SE_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerService.CLEAR_DNS_CACHE_MSG;
+import static com.android.server.am.ActivityManagerService.HANDLE_TRUST_STORAGE_UPDATE_MSG;
+import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityManagerService.UPDATE_HTTP_PROXY_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_PREFERENCE_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_ZONE;
+import static com.android.server.am.ActivityManagerService.checkComponentPermission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationThreadConstants;
+import android.app.BackgroundStartPrivileges;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.appwidget.AppWidgetManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.audiofx.AudioEffect;
+import android.net.ConnectivityManager;
+import android.net.Proxy;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.BinderProxy;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.pm.Computer;
+import com.android.server.pm.SaferIntentUtils;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.utils.Slogf;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+class BroadcastController {
+    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+    /**
+     * It is now required for apps to explicitly set either
+     * {@link android.content.Context#RECEIVER_EXPORTED} or
+     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
+     * unprotected broadcast in code.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
+
+    // Maximum number of receivers an app can register.
+    private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final ActivityManagerService mService;
+    @NonNull
+    private BroadcastQueue mBroadcastQueue;
+
+    @GuardedBy("mService")
+    BroadcastStats mLastBroadcastStats;
+
+    @GuardedBy("mService")
+    BroadcastStats mCurBroadcastStats;
+
+    /**
+     * Broadcast actions that will always be deliverable to unlaunched/background apps
+     */
+    @GuardedBy("mService")
+    private ArraySet<String> mBackgroundLaunchBroadcasts;
+
+    /**
+     * State of all active sticky broadcasts per user.  Keys are the action of the
+     * sticky Intent, values are an ArrayList of all broadcasted intents with
+     * that action (which should usually be one).  The SparseArray is keyed
+     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
+     * for stickies that are sent to all users.
+     */
+    @GuardedBy("mStickyBroadcasts")
+    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
+            new SparseArray<>();
+
+    /**
+     * Keeps track of all IIntentReceivers that have been registered for broadcasts.
+     * Hash keys are the receiver IBinder, hash value is a ReceiverList.
+     */
+    @GuardedBy("mService")
+    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
+
+    /**
+     * Resolver for broadcast intents to registered receivers.
+     * Holds BroadcastFilter (subclass of IntentFilter).
+     */
+    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver =
+            new IntentResolver<>() {
+        @Override
+        protected boolean allowFilterResult(
+                BroadcastFilter filter, List<BroadcastFilter> dest) {
+            IBinder target = filter.receiverList.receiver.asBinder();
+            for (int i = dest.size() - 1; i >= 0; i--) {
+                if (dest.get(i).receiverList.receiver.asBinder() == target) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
+                int match, int userId, long customFlags) {
+            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
+                    || userId == filter.owningUserId) {
+                return super.newResult(computer, filter, match, userId, customFlags);
+            }
+            return null;
+        }
+
+        @Override
+        protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
+            return input;
+        }
+
+        @Override
+        protected BroadcastFilter[] newArray(int size) {
+            return new BroadcastFilter[size];
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+            return packageName.equals(filter.packageName);
+        }
+    };
+
+    BroadcastController(Context context, ActivityManagerService service, BroadcastQueue queue) {
+        mContext = context;
+        mService = service;
+        mBroadcastQueue = queue;
+    }
+
+    void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+        mBroadcastQueue = broadcastQueue;
+    }
+
+    Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
+            String callerFeatureId, String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, String permission, int userId, int flags) {
+        traceRegistrationBegin(receiverId, receiver, filter, userId);
+        try {
+            return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
+                    receiverId, receiver, filter, permission, userId, flags);
+        } finally {
+            traceRegistrationEnd();
+        }
+    }
+
+    private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, int userId) {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            final StringBuilder sb = new StringBuilder("registerReceiver: ");
+            sb.append(Binder.getCallingUid()); sb.append('/');
+            sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+            final int actionsCount = filter.safeCountActions();
+            if (actionsCount > 0) {
+                for (int i = 0; i < actionsCount; ++i) {
+                    sb.append(filter.getAction(i));
+                    if (i != actionsCount - 1) sb.append(',');
+                }
+            } else {
+                sb.append("null");
+            }
+            sb.append('/');
+            sb.append('u'); sb.append(userId); sb.append('/');
+            sb.append(receiver == null ? "null" : receiver.asBinder());
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
+        }
+    }
+
+    private static void traceRegistrationEnd() {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
+            String callerPackage, String callerFeatureId, String receiverId,
+            IIntentReceiver receiver, IntentFilter filter, String permission,
+            int userId, int flags) {
+        mService.enforceNotIsolatedCaller("registerReceiver");
+        ArrayList<StickyBroadcast> stickyBroadcasts = null;
+        ProcessRecord callerApp = null;
+        final boolean visibleToInstantApps =
+                (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+        int callingUid;
+        int callingPid;
+        boolean instantApp;
+        synchronized (mService.mProcLock) {
+            callerApp = mService.getRecordForAppLOSP(caller);
+            if (callerApp == null) {
+                Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
+                return null;
+            }
+            if (callerApp.info.uid != SYSTEM_UID
+                    && !callerApp.getPkgList().containsKey(callerPackage)
+                    && !"android".equals(callerPackage)) {
+                throw new SecurityException("Given caller package " + callerPackage
+                        + " is not running in process " + callerApp);
+            }
+            callingUid = callerApp.info.uid;
+            callingPid = callerApp.getPid();
+
+            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
+        }
+        userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+
+        // Warn if system internals are registering for important broadcasts
+        // without also using a priority to ensure they process the event
+        // before normal apps hear about it
+        if (UserHandle.isCore(callingUid)) {
+            final int priority = filter.getPriority();
+            final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
+                    || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
+            if (!systemPriority) {
+                final int N = filter.countActions();
+                for (int i = 0; i < N; i++) {
+                    // TODO: expand to additional important broadcasts over time
+                    final String action = filter.getAction(i);
+                    if (action.startsWith("android.intent.action.USER_")
+                            || action.startsWith("android.intent.action.PACKAGE_")
+                            || action.startsWith("android.intent.action.UID_")
+                            || action.startsWith("android.intent.action.EXTERNAL_")
+                            || action.startsWith("android.bluetooth.")
+                            || action.equals(Intent.ACTION_SHUTDOWN)) {
+                        if (DEBUG_BROADCAST) {
+                            Slog.wtf(TAG,
+                                    "System internals registering for " + filter.toLongString()
+                                            + " with app priority; this will race with apps!",
+                                    new Throwable());
+                        }
+
+                        // When undefined, assume that system internals need
+                        // to hear about the event first; they can use
+                        // SYSTEM_LOW_PRIORITY if they need to hear last
+                        if (priority == 0) {
+                            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        Iterator<String> actions = filter.actionsIterator();
+        if (actions == null) {
+            ArrayList<String> noAction = new ArrayList<String>(1);
+            noAction.add(null);
+            actions = noAction.iterator();
+        }
+        boolean onlyProtectedBroadcasts = true;
+
+        // Collect stickies of users and check if broadcast is only registered for protected
+        // broadcasts
+        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
+        synchronized (mStickyBroadcasts) {
+            while (actions.hasNext()) {
+                String action = actions.next();
+                for (int id : userIds) {
+                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                            mStickyBroadcasts.get(id);
+                    if (stickies != null) {
+                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
+                        if (broadcasts != null) {
+                            if (stickyBroadcasts == null) {
+                                stickyBroadcasts = new ArrayList<>();
+                            }
+                            stickyBroadcasts.addAll(broadcasts);
+                        }
+                    }
+                }
+                if (onlyProtectedBroadcasts) {
+                    try {
+                        onlyProtectedBroadcasts &=
+                                AppGlobals.getPackageManager().isProtectedBroadcast(action);
+                    } catch (RemoteException e) {
+                        onlyProtectedBroadcasts = false;
+                        Slog.w(TAG, "Remote exception", e);
+                    }
+                }
+            }
+        }
+
+        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+            SdkSandboxManagerLocal sdkSandboxManagerLocal =
+                    LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+            if (sdkSandboxManagerLocal == null) {
+                throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+                        + " whether SDK sandbox uid can register to broadcast receivers.");
+            }
+            if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+                    /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+                throw new SecurityException("SDK sandbox not allowed to register receiver"
+                        + " with the given IntentFilter: " + filter.toLongString());
+            }
+        }
+
+        // If the change is enabled, but neither exported or not exported is set, we need to log
+        // an error so the consumer can know to explicitly set the value for their flag.
+        // If the caller is registering for a sticky broadcast with a null receiver, we won't
+        // require a flag
+        final boolean explicitExportStateDefined =
+                (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
+        if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
+                (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
+            throw new IllegalArgumentException(
+                    "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
+                            + "flag");
+        }
+
+        // Don't enforce the flag check if we're EITHER registering for only protected
+        // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
+        // not be used generally, so we will be marking them as exported by default
+        boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+                DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+
+        // A receiver that is visible to instant apps must also be exported.
+        final boolean unexportedReceiverVisibleToInstantApps =
+                ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
+                        (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
+        if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
+            throw new IllegalArgumentException(
+                    "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
+                            + "RECEIVER_NOT_EXPORTED flag");
+        }
+
+        if (!onlyProtectedBroadcasts) {
+            if (receiver == null && !explicitExportStateDefined) {
+                // sticky broadcast, no flag specified (flag isn't required)
+                flags |= Context.RECEIVER_EXPORTED;
+            } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
+                throw new SecurityException(
+                        callerPackage + ": One of RECEIVER_EXPORTED or "
+                                + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+                                + "isn't being registered exclusively for system broadcasts");
+                // Assume default behavior-- flag check is not enforced
+            } else if (!requireExplicitFlagForDynamicReceivers && (
+                    (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+                // Change is not enabled, assume exported unless otherwise specified.
+                flags |= Context.RECEIVER_EXPORTED;
+            }
+        } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+            flags |= Context.RECEIVER_EXPORTED;
+        }
+
+        // Dynamic receivers are exported by default for versions prior to T
+        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
+
+        ArrayList<StickyBroadcast> allSticky = null;
+        if (stickyBroadcasts != null) {
+            final ContentResolver resolver = mContext.getContentResolver();
+            // Look for any matching sticky broadcasts...
+            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
+                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
+                Intent intent = broadcast.intent;
+                // Don't provided intents that aren't available to instant apps.
+                if (instantApp && (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+                        == 0) {
+                    continue;
+                }
+                // If intent has scheme "content", it will need to access
+                // provider that needs to lock mProviderMap in ActivityThread
+                // and also it may need to wait application response, so we
+                // cannot lock ActivityManagerService here.
+                final int match;
+                if (Flags.avoidResolvingType()) {
+                    match = filter.match(intent.getAction(), broadcast.resolvedDataType,
+                            intent.getScheme(), intent.getData(), intent.getCategories(),
+                            TAG, false /* supportsWildcards */, null /* ignoreActions */,
+                            intent.getExtras());
+                } else {
+                    match = filter.match(resolver, intent, true, TAG);
+                }
+                if (match >= 0) {
+                    if (allSticky == null) {
+                        allSticky = new ArrayList<>();
+                    }
+                    allSticky.add(broadcast);
+                }
+            }
+        }
+
+        // The first sticky in the list is returned directly back to the client.
+        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
+        if (receiver == null) {
+            return sticky;
+        }
+
+        // SafetyNet logging for b/177931370. If any process other than system_server tries to
+        // listen to this broadcast action, then log it.
+        if (callingPid != Process.myPid()) {
+            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
+                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
+                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
+            }
+        }
+
+        synchronized (mService) {
+            IApplicationThread thread;
+            if (callerApp != null && ((thread = callerApp.getThread()) == null
+                    || thread.asBinder() != caller.asBinder())) {
+                // Original caller already died
+                return null;
+            }
+            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+            if (rl == null) {
+                rl = new ReceiverList(mService, callerApp, callingPid, callingUid,
+                        userId, receiver);
+                if (rl.app != null) {
+                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
+                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
+                        throw new IllegalStateException("Too many receivers, total of "
+                                + totalReceiversForApp + ", registered for pid: "
+                                + rl.pid + ", callerPackage: " + callerPackage);
+                    }
+                    rl.app.mReceivers.addReceiver(rl);
+                } else {
+                    try {
+                        receiver.asBinder().linkToDeath(rl, 0);
+                    } catch (RemoteException e) {
+                        return sticky;
+                    }
+                    rl.linkedToDeath = true;
+                }
+                mRegisteredReceivers.put(receiver.asBinder(), rl);
+            } else if (rl.uid != callingUid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for uid " + callingUid
+                                + " was previously registered for uid " + rl.uid
+                                + " callerPackage is " + callerPackage);
+            } else if (rl.pid != callingPid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for pid " + callingPid
+                                + " was previously registered for pid " + rl.pid
+                                + " callerPackage is " + callerPackage);
+            } else if (rl.userId != userId) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for user " + userId
+                                + " was previously registered for user " + rl.userId
+                                + " callerPackage is " + callerPackage);
+            }
+            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
+                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
+                    exported);
+            if (rl.containsFilter(filter)) {
+                Slog.w(TAG, "Receiver with filter " + filter
+                        + " already registered for pid " + rl.pid
+                        + ", callerPackage is " + callerPackage);
+            } else {
+                rl.add(bf);
+                if (!bf.debugCheck()) {
+                    Slog.w(TAG, "==> For Dynamic broadcast");
+                }
+                mReceiverResolver.addFilter(mService.getPackageManagerInternal().snapshot(), bf);
+            }
+
+            // Enqueue broadcasts for all existing stickies that match
+            // this filter.
+            if (allSticky != null) {
+                ArrayList receivers = new ArrayList();
+                receivers.add(bf);
+                sticky = null;
+
+                final int stickyCount = allSticky.size();
+                for (int i = 0; i < stickyCount; i++) {
+                    final StickyBroadcast broadcast = allSticky.get(i);
+                    final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
+                    // TODO(b/281889567): consider using checkComponentPermission instead of
+                    //  canAccessUnexportedComponents
+                    if (sticky == null && (exported || originalStickyCallingUid == callingUid
+                            || ActivityManager.canAccessUnexportedComponents(
+                            originalStickyCallingUid))) {
+                        sticky = broadcast.intent;
+                    }
+                    BroadcastQueue queue = mBroadcastQueue;
+                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
+                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
+                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
+                            receivers, null, null, 0, null, null, false, true, true, -1,
+                            originalStickyCallingUid, BackgroundStartPrivileges.NONE,
+                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+                            null /* filterExtrasForReceiver */,
+                            broadcast.originalCallingAppProcessState);
+                    queue.enqueueBroadcastLocked(r);
+                }
+            }
+
+            return sticky;
+        }
+    }
+
+    void unregisterReceiver(IIntentReceiver receiver) {
+        traceUnregistrationBegin(receiver);
+        try {
+            unregisterReceiverTraced(receiver);
+        } finally {
+            traceUnregistrationEnd();
+        }
+    }
+
+    private static void traceUnregistrationBegin(IIntentReceiver receiver) {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
+                            receiver == null ? "null" : receiver.asBinder()));
+        }
+    }
+
+    private static void traceUnregistrationEnd() {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    private void unregisterReceiverTraced(IIntentReceiver receiver) {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            boolean doTrim = false;
+            synchronized (mService) {
+                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+                if (rl != null) {
+                    final BroadcastRecord r = rl.curBroadcast;
+                    if (r != null) {
+                        final boolean doNext = r.queue.finishReceiverLocked(
+                                rl.app, r.resultCode, r.resultData, r.resultExtras,
+                                r.resultAbort, false);
+                        if (doNext) {
+                            doTrim = true;
+                        }
+                    }
+                    if (rl.app != null) {
+                        rl.app.mReceivers.removeReceiver(rl);
+                    }
+                    removeReceiverLocked(rl);
+                    if (rl.linkedToDeath) {
+                        rl.linkedToDeath = false;
+                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
+                    }
+                }
+
+                // If we actually concluded any broadcasts, we might now be able
+                // to trim the recipients' apps from our working set
+                if (doTrim) {
+                    mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+                    return;
+                }
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mRegisteredReceivers.remove(rl.receiver.asBinder());
+        for (int i = rl.size() - 1; i >= 0; i--) {
+            mReceiverResolver.removeFilter(rl.get(i));
+        }
+    }
+
+    int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
+            Intent intent, String resolvedType, IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle resultExtras,
+            String[] requiredPermissions, String[] excludedPermissions,
+            String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean serialized, boolean sticky, int userId) {
+        mService.enforceNotIsolatedCaller("broadcastIntent");
+
+        synchronized (mService) {
+            intent = verifyBroadcastLocked(intent);
+
+            final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+            final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
+
+            // We're delivering the result to the caller
+            final ProcessRecord resultToApp = callerApp;
+
+            // Permission regimes around sender-supplied broadcast options.
+            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
+
+            final ComponentName cn = intent.getComponent();
+
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                return broadcastIntentLocked(callerApp,
+                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
+                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+                        appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
+                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            }
+        }
+    }
+
+    // Not the binder call surface
+    int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
+            int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
+            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
+            boolean serialized, boolean sticky, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList) {
+        synchronized (mService) {
+            intent = verifyBroadcastLocked(intent);
+
+            final long origId = Binder.clearCallingIdentity();
+            String[] requiredPermissions = requiredPermission == null ? null
+                    : new String[] {requiredPermission};
+            try {
+                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                        resultToApp, resultTo, resultCode, resultData, resultExtras,
+                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
+                        uid, realCallingUid, realCallingPid, userId,
+                        backgroundStartPrivileges, broadcastAllowList,
+                        null /* filterExtrasForReceiver */);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean ordered, boolean sticky, int callingPid, int callingUid,
+            int realCallingUid, int realCallingPid, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+        final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
+                callingUid, realCallingUid, userId);
+        try {
+            final BroadcastSentEventRecord broadcastSentEventRecord =
+                    new BroadcastSentEventRecord();
+            final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+                    intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+                    resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+                    appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+                    callingPid, callingUid, realCallingUid, realCallingPid, userId,
+                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
+                    broadcastSentEventRecord);
+            broadcastSentEventRecord.setResult(res);
+            broadcastSentEventRecord.logToStatsd();
+            return res;
+        } finally {
+            traceBroadcastIntentEnd(cookie);
+        }
+    }
+
+    private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
+            boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
+        if (!Flags.traceReceiverRegistration()) {
+            return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            final StringBuilder sb = new StringBuilder("broadcastIntent: ");
+            sb.append(callingUid); sb.append('/');
+            final String action = intent.getAction();
+            sb.append(action == null ? null : action); sb.append('/');
+            sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
+            sb.append(ordered ? "O" : "_");
+            sb.append(sticky ? "S" : "_");
+            sb.append(resultTo != null ? "C" : "_");
+            sb.append('/');
+            sb.append('u'); sb.append(userId);
+            if (callingUid != realCallingUid) {
+                sb.append('/');
+                sb.append("sender="); sb.append(realCallingUid);
+            }
+            return BroadcastQueue.traceBegin(sb.toString());
+        }
+        return 0;
+    }
+
+    private static void traceBroadcastIntentEnd(int cookie) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            BroadcastQueue.traceEnd(cookie);
+        }
+    }
+
+    @GuardedBy("mService")
+    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp,
+            BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
+            int callingUid, int realCallingUid, int realCallingPid, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
+        // Ensure all internal loopers are registered for idle checks
+        BroadcastLoopers.addMyLooper();
+
+        if (Process.isSdkSandboxUid(realCallingUid)) {
+            final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
+                    SdkSandboxManagerLocal.class);
+            if (sdkSandboxManagerLocal == null) {
+                throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
+                        + " a broadcast from an SDK sandbox uid.");
+            }
+            if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
+                throw new SecurityException(
+                        "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
+                                + " uid. Given caller package " + callerPackage
+                                + " (pid=" + callingPid + ", realCallingUid=" + realCallingUid
+                                + ", callingUid= " + callingUid + ")");
+            }
+        }
+
+        if ((resultTo != null) && (resultToApp == null)) {
+            if (resultTo.asBinder() instanceof BinderProxy) {
+                // Warn when requesting results without a way to deliver them
+                Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
+                        + " with resultTo requires resultToApp", new Throwable());
+            } else {
+                // If not a BinderProxy above, then resultTo is an in-process
+                // receiver, so splice in system_server process
+                resultToApp = mService.getProcessRecordLocked("system", SYSTEM_UID);
+            }
+        }
+
+        intent = new Intent(intent);
+        broadcastSentEventRecord.setIntent(intent);
+        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
+        broadcastSentEventRecord.setSenderUid(callingUid);
+        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
+        broadcastSentEventRecord.setSticky(sticky);
+        broadcastSentEventRecord.setOrdered(ordered);
+        broadcastSentEventRecord.setResultRequested(resultTo != null);
+        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
+        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
+        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
+                realCallingPid));
+
+        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
+        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
+        if (callerInstantApp) {
+            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        }
+
+        if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
+            Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
+                    + "Assuming restrictive whitelist.");
+            broadcastAllowList = new int[]{};
+        }
+
+        // By default broadcasts do not go to stopped apps.
+        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+
+        // If we have not finished booting, don't allow this to launch new processes.
+        if (!mService.mProcessesReady
+                && (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        }
+
+        if (DEBUG_BROADCAST_LIGHT) {
+            Slog.v(TAG_BROADCAST,
+                    (sticky ? "Broadcast sticky: " : "Broadcast: ") + intent
+                            + " ordered=" + ordered + " userid=" + userId
+                            + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
+        }
+        if ((resultTo != null) && !ordered) {
+            if (!UserHandle.isCore(callingUid)) {
+                String msg = "Unauthorized unordered resultTo broadcast "
+                        + intent + " sent from uid " + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+        }
+
+        userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_NON_FULL, "broadcast", callerPackage);
+
+        // Make sure that the user who is receiving this broadcast or its parent is running.
+        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+        if (userId != UserHandle.USER_ALL && !mService.mUserController.isUserOrItsParentRunning(
+                userId)) {
+            if ((callingUid != SYSTEM_UID
+                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
+                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+                Slog.w(TAG, "Skipping broadcast of " + intent
+                        + ": user " + userId + " and its parent (if any) are stopped");
+                scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
+                        brOptions, callingUid, callerPackage);
+                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
+            }
+        }
+
+        final String action = intent.getAction();
+        if (brOptions != null) {
+            if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
+                // See if the caller is allowed to do this.  Note we are checking against
+                // the actual real caller (not whoever provided the operation as say a
+                // PendingIntent), because that who is actually supplied the arguments.
+                if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage + " (pid=" + callingPid
+                            + ", uid=" + callingUid + ")"
+                            + " requires "
+                            + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
+                            + START_ACTIVITIES_FROM_BACKGROUND + " or "
+                            + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+            }
+            if (brOptions.isDontSendToRestrictedApps()
+                    && !mService.isUidActiveLOSP(callingUid)
+                    && mService.isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
+                Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
+                        + " has background restrictions");
+                return ActivityManager.START_CANCELED;
+            }
+            if (brOptions.allowsBackgroundActivityStarts()) {
+                // See if the caller is allowed to do this.  Note we are checking against
+                // the actual real caller (not whoever provided the operation as say a
+                // PendingIntent), because that who is actually supplied the arguments.
+                if (checkComponentPermission(
+                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage + " (pid=" + callingPid
+                            + ", uid=" + callingUid + ")"
+                            + " requires "
+                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else {
+                    // We set the token to null since if it wasn't for it we'd allow anyway here
+                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
+                }
+            }
+
+            if (brOptions.getIdForResponseEvent() > 0) {
+                mService.enforcePermission(
+                        android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
+                        callingPid, callingUid, "recordResponseEventWhileInBackground");
+            }
+        }
+
+        // Verify that protected broadcasts are only being sent by system code,
+        // and that system code is only sending protected broadcasts.
+        final boolean isProtectedBroadcast;
+        try {
+            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Remote exception", e);
+            scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                    userId, brOptions, callingUid, callerPackage);
+            return ActivityManager.BROADCAST_SUCCESS;
+        }
+
+        final boolean isCallerSystem;
+        switch (UserHandle.getAppId(callingUid)) {
+            case ROOT_UID:
+            case SYSTEM_UID:
+            case PHONE_UID:
+            case BLUETOOTH_UID:
+            case NFC_UID:
+            case SE_UID:
+            case NETWORK_STACK_UID:
+                isCallerSystem = true;
+                break;
+            default:
+                isCallerSystem = (callerApp != null) && callerApp.isPersistent();
+                break;
+        }
+
+        // First line security check before anything else: stop non-system apps from
+        // sending protected broadcasts.
+        if (!isCallerSystem) {
+            if (isProtectedBroadcast) {
+                String msg = "Permission Denial: not allowed to send broadcast "
+                        + action + " from pid="
+                        + callingPid + ", uid=" + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+
+            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+                // Special case for compatibility: we don't want apps to send this,
+                // but historically it has not been protected and apps may be using it
+                // to poke their own app widget.  So, instead of making it protected,
+                // just limit it to the caller.
+                if (callerPackage == null) {
+                    String msg = "Permission Denial: not allowed to send broadcast "
+                            + action + " from unknown caller.";
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else if (intent.getComponent() != null) {
+                    // They are good enough to send to an explicit component...  verify
+                    // it is being sent to the calling app.
+                    if (!intent.getComponent().getPackageName().equals(
+                            callerPackage)) {
+                        String msg = "Permission Denial: not allowed to send broadcast "
+                                + action + " to "
+                                + intent.getComponent().getPackageName() + " from "
+                                + callerPackage;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                } else {
+                    // Limit broadcast to their own package.
+                    intent.setPackage(callerPackage);
+                }
+            }
+        }
+
+        boolean timeoutExempt = false;
+
+        if (action != null) {
+            if (getBackgroundLaunchBroadcasts().contains(action)) {
+                if (DEBUG_BACKGROUND_CHECK) {
+                    Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
+                }
+                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            }
+
+            // TODO: b/329211459 - Remove this after background remote intent is fixed.
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                    && getWearRemoteIntentAction().equals(action)) {
+                final int callerProcState = callerApp != null
+                        ? callerApp.getCurProcState()
+                        : ActivityManager.PROCESS_STATE_NONEXISTENT;
+                if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
+                        > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                    return ActivityManager.START_CANCELED;
+                }
+            }
+
+            switch (action) {
+                case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
+                    UserManagerInternal umInternal = LocalServices.getService(
+                            UserManagerInternal.class);
+                    UserInfo userInfo = umInternal.getUserInfo(userId);
+                    if (userInfo != null && userInfo.isCloneProfile()) {
+                        userId = umInternal.getProfileParentId(userId);
+                    }
+                    break;
+                case Intent.ACTION_UID_REMOVED:
+                case Intent.ACTION_PACKAGE_REMOVED:
+                case Intent.ACTION_PACKAGE_CHANGED:
+                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    // Handle special intents: if this broadcast is from the package
+                    // manager about a package being removed, we need to remove all of
+                    // its activities from the history stack.
+                    if (checkComponentPermission(
+                            android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
+                            callingPid, callingUid, -1, true)
+                            != PackageManager.PERMISSION_GRANTED) {
+                        String msg = "Permission Denial: " + intent.getAction()
+                                + " broadcast from " + callerPackage + " (pid=" + callingPid
+                                + ", uid=" + callingUid + ")"
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                    switch (action) {
+                        case Intent.ACTION_UID_REMOVED:
+                            final int uid = getUidFromIntent(intent);
+                            if (uid >= 0) {
+                                mService.mBatteryStatsService.removeUid(uid);
+                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                                    mService.mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+                                } else {
+                                    mService.mAppOpsService.uidRemoved(uid);
+                                    mService.mServices.onUidRemovedLocked(uid);
+                                }
+                            }
+                            break;
+                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                            // If resources are unavailable just force stop all those packages
+                            // and flush the attribute cache as well.
+                            String[] list = intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                            if (list != null && list.length > 0) {
+                                for (int i = 0; i < list.length; i++) {
+                                    mService.forceStopPackageLocked(list[i], -1, false, true, true,
+                                            false, false, false, userId, "storage unmount");
+                                }
+                                mService.mAtmInternal.cleanupRecentTasksForUser(
+                                        UserHandle.USER_ALL);
+                                sendPackageBroadcastLocked(
+                                        ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
+                                        list, userId);
+                            }
+                            break;
+                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                            mService.mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
+                            break;
+                        case Intent.ACTION_PACKAGE_REMOVED:
+                        case Intent.ACTION_PACKAGE_CHANGED:
+                            Uri data = intent.getData();
+                            String ssp;
+                            if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                                boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
+                                final boolean replacing =
+                                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                                final boolean killProcess =
+                                        !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
+                                final boolean fullUninstall = removed && !replacing;
+
+                                if (removed) {
+                                    if (killProcess) {
+                                        mService.forceStopPackageLocked(ssp, UserHandle.getAppId(
+                                                        intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                false, true, true, false, fullUninstall, false,
+                                                userId, "pkg removed");
+                                        mService.getPackageManagerInternal()
+                                                .onPackageProcessKilledForUninstall(ssp);
+                                    } else {
+                                        // Kill any app zygotes always, since they can't fork new
+                                        // processes with references to the old code
+                                        mService.forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
+                                                        intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                userId);
+                                    }
+                                    final int cmd = killProcess
+                                            ? ApplicationThreadConstants.PACKAGE_REMOVED
+                                            : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
+                                    sendPackageBroadcastLocked(cmd,
+                                            new String[] {ssp}, userId);
+                                    if (fullUninstall) {
+                                        // Remove all permissions granted from/to this package
+                                        mService.mUgmInternal.removeUriPermissionsForPackage(ssp,
+                                                userId, true, false);
+
+                                        mService.mAtmInternal.removeRecentTasksByPackageName(ssp,
+                                                userId);
+
+                                        mService.mServices.forceStopPackageLocked(ssp, userId);
+                                        mService.mAtmInternal.onPackageUninstalled(ssp, userId);
+                                        mService.mBatteryStatsService.notePackageUninstalled(ssp);
+                                    }
+                                } else {
+                                    if (killProcess) {
+                                        int reason;
+                                        int subReason;
+                                        if (replacing) {
+                                            reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
+                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+                                        } else {
+                                            reason =
+                                                    ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
+                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+                                        }
+
+                                        final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
+                                                -1);
+                                        synchronized (mService.mProcLock) {
+                                            mService.mProcessList.killPackageProcessesLSP(ssp,
+                                                    UserHandle.getAppId(extraUid),
+                                                    userId, ProcessList.INVALID_ADJ,
+                                                    reason,
+                                                    subReason,
+                                                    "change " + ssp);
+                                        }
+                                    }
+                                    mService.cleanupDisabledPackageComponentsLocked(ssp, userId,
+                                            intent.getStringArrayExtra(
+                                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+                                    mService.mServices.schedulePendingServiceStartLocked(
+                                            ssp, userId);
+                                }
+                            }
+                            break;
+                        case Intent.ACTION_PACKAGES_SUSPENDED:
+                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                            final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
+                                    intent.getAction());
+                            final String[] packageNames = intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                            final int userIdExtra = intent.getIntExtra(
+                                    Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+                            mService.mAtmInternal.onPackagesSuspendedChanged(packageNames,
+                                    suspended, userIdExtra);
+
+                            final boolean quarantined = intent.getBooleanExtra(
+                                    Intent.EXTRA_QUARANTINED, false);
+                            if (suspended && quarantined && packageNames != null) {
+                                for (int i = 0; i < packageNames.length; i++) {
+                                    mService.forceStopPackage(packageNames[i], userId,
+                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
+                                }
+                            }
+
+                            break;
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_REPLACED: {
+                    final Uri data = intent.getData();
+                    final String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        ApplicationInfo aInfo = null;
+                        try {
+                            aInfo = AppGlobals.getPackageManager()
+                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
+                        } catch (RemoteException ignore) {
+                        }
+                        if (aInfo == null) {
+                            Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
+                                    + " ssp=" + ssp + " data=" + data);
+                            scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                                    userId, brOptions, callingUid, callerPackage);
+                            return ActivityManager.BROADCAST_SUCCESS;
+                        }
+                        mService.updateAssociationForApp(aInfo);
+                        mService.mAtmInternal.onPackageReplaced(aInfo);
+                        mService.mServices.updateServiceApplicationInfoLocked(aInfo);
+                        sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
+                                new String[] {ssp}, userId);
+                    }
+                    break;
+                }
+                case Intent.ACTION_PACKAGE_ADDED: {
+                    // Special case for adding a package: by default turn on compatibility mode.
+                    Uri data = intent.getData();
+                    String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        final boolean replacing =
+                                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        mService.mAtmInternal.onPackageAdded(ssp, replacing);
+
+                        try {
+                            ApplicationInfo ai = AppGlobals.getPackageManager()
+                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
+                            mService.mBatteryStatsService.notePackageInstalled(ssp,
+                                    ai != null ? ai.longVersionCode : 0);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                    break;
+                }
+                case Intent.ACTION_PACKAGE_DATA_CLEARED: {
+                    Uri data = intent.getData();
+                    String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        mService.mAtmInternal.onPackageDataCleared(ssp, userId);
+                    }
+                    break;
+                }
+                case Intent.ACTION_TIMEZONE_CHANGED:
+                    // If this is the time zone changed action, queue up a message that will reset
+                    // the timezone of all currently running processes. This message will get
+                    // queued up before the broadcast happens.
+                    mService.mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
+                    break;
+                case Intent.ACTION_TIME_CHANGED:
+                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+                    // the tri-state value it may contain and "unknown".
+                    // For convenience we re-use the Intent extra values.
+                    final int NO_EXTRA_VALUE_FOUND = -1;
+                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
+                    // Only send a message if the time preference is available.
+                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+                        Message updateTimePreferenceMsg =
+                                mService.mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+                                        timeFormatPreferenceMsgValue, 0);
+                        mService.mHandler.sendMessage(updateTimePreferenceMsg);
+                    }
+                    mService.mBatteryStatsService.noteCurrentTimeChanged();
+                    break;
+                case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
+                    mService.mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
+                    break;
+                case Proxy.PROXY_CHANGE_ACTION:
+                    mService.mHandler.sendMessage(mService.mHandler.obtainMessage(
+                            UPDATE_HTTP_PROXY_MSG));
+                    break;
+                case android.hardware.Camera.ACTION_NEW_PICTURE:
+                case android.hardware.Camera.ACTION_NEW_VIDEO:
+                    // In N we just turned these off; in O we are turing them back on partly,
+                    // only for registered receivers.  This will still address the main problem
+                    // (a spam of apps waking up when a picture is taken putting significant
+                    // memory pressure on the system at a bad point), while still allowing apps
+                    // that are already actively running to know about this happening.
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    break;
+                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
+                    mService.mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
+                    break;
+                case "com.android.launcher.action.INSTALL_SHORTCUT":
+                    // As of O, we no longer support this broadcasts, even for pre-O apps.
+                    // Apps should now be using ShortcutManager.pinRequestShortcut().
+                    Log.w(TAG, "Broadcast " + action
+                            + " no longer supported. It will not be delivered.");
+                    scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                            userId, brOptions, callingUid, callerPackage);
+                    return ActivityManager.BROADCAST_SUCCESS;
+                case Intent.ACTION_PRE_BOOT_COMPLETED:
+                    timeoutExempt = true;
+                    break;
+                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+                    if (!mService.mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+                            callerPackage)) {
+                        scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                                userId, brOptions, callingUid, callerPackage);
+                        // Returning success seems to be the pattern here
+                        return ActivityManager.BROADCAST_SUCCESS;
+                    }
+                    break;
+            }
+
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                final int uid = getUidFromIntent(intent);
+                if (uid != -1) {
+                    final UidRecord uidRec = mService.mProcessList.getUidRecordLOSP(uid);
+                    if (uidRec != null) {
+                        uidRec.updateHasInternetPermission();
+                    }
+                }
+            }
+        }
+
+        // Add to the sticky list if requested.
+        if (sticky) {
+            if (mService.checkPermission(android.Manifest.permission.BROADCAST_STICKY,
+                    callingPid, callingUid)
+                    != PackageManager.PERMISSION_GRANTED) {
+                String msg =
+                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+                                + " pid="
+                                + callingPid
+                                + ", uid="
+                                + callingUid
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_STICKY;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            if (requiredPermissions != null && requiredPermissions.length > 0) {
+                Slog.w(TAG, "Can't broadcast sticky intent " + intent
+                        + " and enforce permissions " + Arrays.toString(requiredPermissions));
+                scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                        userId, brOptions, callingUid, callerPackage);
+                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+            }
+            if (intent.getComponent() != null) {
+                throw new SecurityException(
+                        "Sticky broadcasts can't target a specific component");
+            }
+            synchronized (mStickyBroadcasts) {
+                // We use userId directly here, since the "all" target is maintained
+                // as a separate set of sticky broadcasts.
+                if (userId != UserHandle.USER_ALL) {
+                    // But first, if this is not a broadcast to all users, then
+                    // make sure it doesn't conflict with an existing broadcast to
+                    // all users.
+                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
+                            UserHandle.USER_ALL);
+                    if (stickies != null) {
+                        ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                        if (list != null) {
+                            int N = list.size();
+                            int i;
+                            for (i = 0; i < N; i++) {
+                                if (intent.filterEquals(list.get(i).intent)) {
+                                    throw new IllegalArgumentException("Sticky broadcast " + intent
+                                            + " for user " + userId
+                                            + " conflicts with existing global broadcast");
+                                }
+                            }
+                        }
+                    }
+                }
+                ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                        mStickyBroadcasts.get(userId);
+                if (stickies == null) {
+                    stickies = new ArrayMap<>();
+                    mStickyBroadcasts.put(userId, stickies);
+                }
+                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                if (list == null) {
+                    list = new ArrayList<>();
+                    stickies.put(intent.getAction(), list);
+                }
+                final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
+                        callingUid, brOptions, resultTo, ordered,
+                        BroadcastRecord.calculateUrgent(intent, brOptions));
+                final int stickiesCount = list.size();
+                int i;
+                for (i = 0; i < stickiesCount; i++) {
+                    if (intent.filterEquals(list.get(i).intent)) {
+                        // This sticky already exists, replace it.
+                        list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
+                                callingUid, callerAppProcessState, resolvedType));
+                        break;
+                    }
+                }
+                if (i >= stickiesCount) {
+                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
+                            callingUid, callerAppProcessState, resolvedType));
+                }
+            }
+        }
+
+        int[] users;
+        if (userId == UserHandle.USER_ALL) {
+            // Caller wants broadcast to go to all started users.
+            users = mService.mUserController.getStartedUserArray();
+        } else {
+            // Caller wants broadcast to go to one specific user.
+            users = new int[] {userId};
+        }
+
+        var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
+                true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
+        args.platformCompat = mService.mPlatformCompat;
+
+        // Figure out who all will receive this broadcast.
+        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
+        List receivers = null;
+        List<BroadcastFilter> registeredReceivers = null;
+        // Need to resolve the intent to interested receivers...
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+            receivers = collectReceiverComponents(
+                    intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
+        }
+        if (intent.getComponent() == null) {
+            final PackageDataSnapshot snapshot = mService.getPackageManagerInternal().snapshot();
+            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
+                // Query one target user at a time, excluding shell-restricted users
+                for (int i = 0; i < users.length; i++) {
+                    if (mService.mUserController.hasUserRestriction(
+                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
+                        continue;
+                    }
+                    List<BroadcastFilter> registeredReceiversForUser =
+                            mReceiverResolver.queryIntent(snapshot, intent,
+                                    resolvedType, false /*defaultOnly*/, users[i]);
+                    if (registeredReceivers == null) {
+                        registeredReceivers = registeredReceiversForUser;
+                    } else if (registeredReceiversForUser != null) {
+                        registeredReceivers.addAll(registeredReceiversForUser);
+                    }
+                }
+            } else {
+                registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
+                        resolvedType, false /*defaultOnly*/, userId);
+            }
+            if (registeredReceivers != null) {
+                SaferIntentUtils.blockNullAction(args, registeredReceivers);
+            }
+        }
+        BroadcastQueue.traceEnd(cookie);
+
+        final boolean replacePending =
+                (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+
+        if (DEBUG_BROADCAST) {
+            Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
+                    + " replacePending=" + replacePending);
+        }
+        if (registeredReceivers != null && broadcastAllowList != null) {
+            // if a uid whitelist was provided, remove anything in the application space that wasn't
+            // in it.
+            for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
+                final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
+                if (owningAppId >= Process.FIRST_APPLICATION_UID
+                        && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
+                    registeredReceivers.remove(i);
+                }
+            }
+        }
+
+        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
+
+        // Merge into one list.
+        int ir = 0;
+        if (receivers != null) {
+            // A special case for PACKAGE_ADDED: do not allow the package
+            // being added to see this broadcast.  This prevents them from
+            // using this as a back door to get run as soon as they are
+            // installed.  Maybe in the future we want to have a special install
+            // broadcast or such for apps, but we'd like to deliberately make
+            // this decision.
+            String[] skipPackages = null;
+            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
+                Uri data = intent.getData();
+                if (data != null) {
+                    String pkgName = data.getSchemeSpecificPart();
+                    if (pkgName != null) {
+                        skipPackages = new String[] { pkgName };
+                    }
+                }
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
+                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+            }
+            if (skipPackages != null && (skipPackages.length > 0)) {
+                for (String skipPackage : skipPackages) {
+                    if (skipPackage != null) {
+                        int NT = receivers.size();
+                        for (int it = 0; it < NT; it++) {
+                            ResolveInfo curt = (ResolveInfo) receivers.get(it);
+                            if (curt.activityInfo.packageName.equals(skipPackage)) {
+                                receivers.remove(it);
+                                it--;
+                                NT--;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int NT = receivers != null ? receivers.size() : 0;
+            int it = 0;
+            ResolveInfo curt = null;
+            BroadcastFilter curr = null;
+            while (it < NT && ir < NR) {
+                if (curt == null) {
+                    curt = (ResolveInfo) receivers.get(it);
+                }
+                if (curr == null) {
+                    curr = registeredReceivers.get(ir);
+                }
+                if (curr.getPriority() >= curt.priority) {
+                    // Insert this broadcast record into the final list.
+                    receivers.add(it, curr);
+                    ir++;
+                    curr = null;
+                    it++;
+                    NT++;
+                } else {
+                    // Skip to the next ResolveInfo in the final list.
+                    it++;
+                    curt = null;
+                }
+            }
+        }
+        while (ir < NR) {
+            if (receivers == null) {
+                receivers = new ArrayList();
+            }
+            receivers.add(registeredReceivers.get(ir));
+            ir++;
+        }
+
+        if (isCallerSystem) {
+            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
+                    isProtectedBroadcast, receivers);
+        }
+
+        if ((receivers != null && receivers.size() > 0)
+                || resultTo != null) {
+            BroadcastQueue queue = mBroadcastQueue;
+            SaferIntentUtils.filterNonExportedComponents(args, receivers);
+            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
+                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
+                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
+                    receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
+                    ordered, sticky, false, userId,
+                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+                    callerAppProcessState);
+            broadcastSentEventRecord.setBroadcastRecord(r);
+
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
+            queue.enqueueBroadcastLocked(r);
+        } else {
+            // There was nobody interested in the broadcast, but we still want to record
+            // that it happened.
+            if (intent.getComponent() == null && intent.getPackage() == null
+                    && (intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                // This was an implicit broadcast... let's record it for posterity.
+                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
+            }
+        }
+
+        return ActivityManager.BROADCAST_SUCCESS;
+    }
+
+    @GuardedBy("mService")
+    private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
+            Intent intent, int userId, BroadcastOptions options, int callingUid,
+            String callingPackage) {
+        if (resultTo == null) {
+            return;
+        }
+        final ProcessRecord app = resultToApp;
+        final IApplicationThread thread  = (app != null) ? app.getOnewayThread() : null;
+        if (thread != null) {
+            try {
+                final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
+                thread.scheduleRegisteredReceiver(
+                        resultTo, intent, Activity.RESULT_CANCELED, null, null,
+                        false, false, true, userId, app.mState.getReportedProcState(),
+                        shareIdentity ? callingUid : Process.INVALID_UID,
+                        shareIdentity ? callingPackage : null);
+            } catch (RemoteException e) {
+                final String msg = "Failed to schedule result of " + intent + " via "
+                        + app + ": " + e;
+                app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
+                Slog.d(TAG, msg);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    private int getRealProcessStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            return app.mState.getCurProcState();
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
+    @GuardedBy("mService")
+    private int getRealUidStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            final UidRecord uidRecord = app.getUidRecord();
+            if (uidRecord != null) {
+                return uidRecord.getCurProcState();
+            }
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
+    @VisibleForTesting
+    ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
+        synchronized (mStickyBroadcasts) {
+            final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
+                    mStickyBroadcasts.get(userId);
+            if (stickyBroadcasts == null) {
+                return null;
+            }
+            return stickyBroadcasts.get(action);
+        }
+    }
+
+    void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, true, ALLOW_NON_FULL,
+                "removeStickyBroadcast", null);
+
+        if (mService.checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: unbroadcastIntent() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (mStickyBroadcasts) {
+            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
+            if (stickies != null) {
+                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                if (list != null) {
+                    int N = list.size();
+                    int i;
+                    for (i = 0; i < N; i++) {
+                        if (intent.filterEquals(list.get(i).intent)) {
+                            list.remove(i);
+                            break;
+                        }
+                    }
+                    if (list.size() <= 0) {
+                        stickies.remove(intent.getAction());
+                    }
+                }
+                if (stickies.size() <= 0) {
+                    mStickyBroadcasts.remove(userId);
+                }
+            }
+        }
+    }
+
+    void finishReceiver(IBinder caller, int resultCode, String resultData,
+            Bundle resultExtras, boolean resultAbort, int flags) {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+
+        // Refuse possible leaked file descriptors
+        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Bundle");
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+                if (callerApp == null) {
+                    Slog.w(TAG, "finishReceiver: no app for " + caller);
+                    return;
+                }
+
+                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
+                        resultData, resultExtras, resultAbort, true);
+                // updateOomAdjLocked() will be done here
+                mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
+     */
+    private int getUidFromIntent(Intent intent) {
+        if (intent == null) {
+            return -1;
+        }
+        final Bundle intentExtras = intent.getExtras();
+        return intent.hasExtra(Intent.EXTRA_UID)
+                ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
+    }
+
+    final void rotateBroadcastStatsIfNeededLocked() {
+        final long now = SystemClock.elapsedRealtime();
+        if (mCurBroadcastStats == null
+                || (mCurBroadcastStats.mStartRealtime + (24 * 60 * 60 * 1000) < now)) {
+            mLastBroadcastStats = mCurBroadcastStats;
+            if (mLastBroadcastStats != null) {
+                mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
+                mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
+            }
+            mCurBroadcastStats = new BroadcastStats();
+        }
+    }
+
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
+    }
+
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+    }
+
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+    }
+
+    final Intent verifyBroadcastLocked(Intent intent) {
+        if (intent != null) {
+            intent.prepareToEnterSystemServer();
+        }
+
+        int flags = intent.getFlags();
+
+        if (!mService.mProcessesReady) {
+            // if the caller really truly claims to know what they're doing, go
+            // ahead and allow the broadcast without launching any receivers
+            if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
+                // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
+            } else if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
+                        + " before boot completion");
+                throw new IllegalStateException("Cannot broadcast before boot completed");
+            }
+        }
+
+        if ((flags & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+            throw new IllegalArgumentException(
+                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+        }
+
+        if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+            switch (Binder.getCallingUid()) {
+                case ROOT_UID:
+                case SHELL_UID:
+                    break;
+                default:
+                    Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
+                            + Binder.getCallingUid());
+                    intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
+                    break;
+            }
+        }
+
+        return intent;
+    }
+
+    private ArraySet<String> getBackgroundLaunchBroadcasts() {
+        if (mBackgroundLaunchBroadcasts == null) {
+            mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
+        }
+        return mBackgroundLaunchBroadcasts;
+    }
+
+    private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
+        if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
+            return false;
+        }
+        // Easy case -- we have the app's ProcessRecord.
+        if (record != null) {
+            return record.info.isInstantApp();
+        }
+        // Otherwise check with PackageManager.
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            if (callerPackage == null) {
+                final String[] packageNames = pm.getPackagesForUid(uid);
+                if (packageNames == null || packageNames.length == 0) {
+                    throw new IllegalArgumentException("Unable to determine caller package name");
+                }
+                // Instant Apps can't use shared uids, so its safe to only check the first package.
+                callerPackage = packageNames[0];
+            }
+            mService.mAppOpsService.checkPackage(uid, callerPackage);
+            return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
+            return true;
+        }
+    }
+
+    private String getWearRemoteIntentAction() {
+        return mContext.getResources().getString(
+                com.android.internal.R.string.config_wearRemoteIntentAction);
+    }
+
+    private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+        mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
+    }private List<ResolveInfo> collectReceiverComponents(
+            Intent intent, String resolvedType, int callingUid, int callingPid,
+            int[] users, int[] broadcastAllowList) {
+        // TODO: come back and remove this assumption to triage all broadcasts
+        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
+
+        List<ResolveInfo> receivers = null;
+        HashSet<ComponentName> singleUserReceivers = null;
+        boolean scannedFirstReceivers = false;
+        for (int user : users) {
+            // Skip users that have Shell restrictions
+            if (callingUid == SHELL_UID
+                    && mService.mUserController.hasUserRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+                continue;
+            }
+            List<ResolveInfo> newReceivers = mService.mPackageManagerInt.queryIntentReceivers(
+                    intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
+            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+                // If this is not the system user, we need to check for
+                // any receivers that should be filtered out.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+                        newReceivers.remove(i);
+                        i--;
+                    }
+                }
+            }
+            // Replace the alias receivers with their targets.
+            if (newReceivers != null) {
+                for (int i = newReceivers.size() - 1; i >= 0; i--) {
+                    final ResolveInfo ri = newReceivers.get(i);
+                    final ComponentAliasResolver.Resolution<ResolveInfo> resolution =
+                            mService.mComponentAliasResolver.resolveReceiver(intent, ri,
+                                    resolvedType, pmFlags, user, callingUid, callingPid);
+                    if (resolution == null) {
+                        // It was an alias, but the target was not found.
+                        newReceivers.remove(i);
+                        continue;
+                    }
+                    if (resolution.isAlias()) {
+                        newReceivers.set(i, resolution.getTarget());
+                    }
+                }
+            }
+            if (newReceivers != null && newReceivers.size() == 0) {
+                newReceivers = null;
+            }
+
+            if (receivers == null) {
+                receivers = newReceivers;
+            } else if (newReceivers != null) {
+                // We need to concatenate the additional receivers
+                // found with what we have do far.  This would be easy,
+                // but we also need to de-dup any receivers that are
+                // singleUser.
+                if (!scannedFirstReceivers) {
+                    // Collect any single user receivers we had already retrieved.
+                    scannedFirstReceivers = true;
+                    for (int i = 0; i < receivers.size(); i++) {
+                        ResolveInfo ri = receivers.get(i);
+                        if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                            ComponentName cn = new ComponentName(
+                                    ri.activityInfo.packageName, ri.activityInfo.name);
+                            if (singleUserReceivers == null) {
+                                singleUserReceivers = new HashSet<ComponentName>();
+                            }
+                            singleUserReceivers.add(cn);
+                        }
+                    }
+                }
+                // Add the new results to the existing results, tracking
+                // and de-dupping single user receivers.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                        ComponentName cn = new ComponentName(
+                                ri.activityInfo.packageName, ri.activityInfo.name);
+                        if (singleUserReceivers == null) {
+                            singleUserReceivers = new HashSet<ComponentName>();
+                        }
+                        if (!singleUserReceivers.contains(cn)) {
+                            singleUserReceivers.add(cn);
+                            receivers.add(ri);
+                        }
+                    } else {
+                        receivers.add(ri);
+                    }
+                }
+            }
+        }
+        if (receivers != null && broadcastAllowList != null) {
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                final int receiverAppId = UserHandle.getAppId(
+                        receivers.get(i).activityInfo.applicationInfo.uid);
+                if (receiverAppId >= Process.FIRST_APPLICATION_UID
+                        && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
+                    receivers.remove(i);
+                }
+            }
+        }
+        return receivers;
+    }
+
+    private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
+            String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+            // Don't yell about broadcasts sent via shell
+            return;
+        }
+
+        final String action = intent.getAction();
+        if (isProtectedBroadcast
+                || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+                || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
+                || Intent.ACTION_MEDIA_BUTTON.equals(action)
+                || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
+                || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
+                || Intent.ACTION_MASTER_CLEAR.equals(action)
+                || Intent.ACTION_FACTORY_RESET.equals(action)
+                || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
+                || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
+                || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
+                || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
+                || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
+            // Broadcast is either protected, or it's a public action that
+            // we've relaxed, so it's fine for system internals to send.
+            return;
+        }
+
+        // This broadcast may be a problem...  but there are often system components that
+        // want to send an internal broadcast to themselves, which is annoying to have to
+        // explicitly list each action as a protected broadcast, so we will check for that
+        // one safe case and allow it: an explicit broadcast, only being received by something
+        // that has protected itself.
+        if (intent.getPackage() != null || intent.getComponent() != null) {
+            if (receivers == null || receivers.size() == 0) {
+                // Intent is explicit and there's no receivers.
+                // This happens, e.g. , when a system component sends a broadcast to
+                // its own runtime receiver, and there's no manifest receivers for it,
+                // because this method is called twice for each broadcast,
+                // for runtime receivers and manifest receivers and the later check would find
+                // no receivers.
+                return;
+            }
+            boolean allProtected = true;
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                Object target = receivers.get(i);
+                if (target instanceof ResolveInfo) {
+                    ResolveInfo ri = (ResolveInfo) target;
+                    if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
+                        allProtected = false;
+                        break;
+                    }
+                } else {
+                    BroadcastFilter bf = (BroadcastFilter) target;
+                    if (bf.exported && bf.requiredPermission == null) {
+                        allProtected = false;
+                        break;
+                    }
+                }
+            }
+            if (allProtected) {
+                // All safe!
+                return;
+            }
+        }
+
+        // The vast majority of broadcasts sent from system internals
+        // should be protected to avoid security holes, so yell loudly
+        // to ensure we examine these cases.
+        if (callerApp != null) {
+            Log.wtf(TAG, "Sending non-protected broadcast " + action
+                            + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
+                    new Throwable());
+        } else {
+            Log.wtf(TAG, "Sending non-protected broadcast " + action
+                            + " from system uid " + UserHandle.formatUid(callingUid)
+                            + " pkg " + callerPackage,
+                    new Throwable());
+        }
+    }
+
+    // Apply permission policy around the use of specific broadcast options
+    void enforceBroadcastOptionPermissionsInternal(
+            @Nullable Bundle options, int callingUid) {
+        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+                callingUid);
+    }
+
+    private void enforceBroadcastOptionPermissionsInternal(
+            @Nullable BroadcastOptions options, int callingUid) {
+        if (options != null && callingUid != Process.SYSTEM_UID) {
+            if (options.isAlarmBroadcast()) {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    Slog.w(TAG, "Non-system caller " + callingUid
+                            + " may not flag broadcast as alarm");
+                }
+                throw new SecurityException(
+                        "Non-system callers may not flag broadcasts as alarm");
+            }
+            if (options.isInteractive()) {
+                mService.enforceCallingPermission(
+                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
+                        "setInteractive");
+            }
+        }
+    }
+
+    void startBroadcastObservers() {
+        mBroadcastQueue.start(mContext.getContentResolver());
+    }
+
+    void removeStickyBroadcasts(int userId) {
+        synchronized (mStickyBroadcasts) {
+            mStickyBroadcasts.remove(userId);
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean dumpConstants = true;
+        boolean dumpHistory = true;
+        boolean needSep = false;
+        boolean onlyHistory = false;
+        boolean printedAnything = false;
+        boolean onlyReceivers = false;
+        int filteredUid = Process.INVALID_UID;
+
+        if ("history".equals(dumpPackage)) {
+            if (opti < args.length && "-s".equals(args[opti])) {
+                dumpAll = false;
+            }
+            onlyHistory = true;
+            dumpPackage = null;
+        }
+        if ("receivers".equals(dumpPackage)) {
+            onlyReceivers = true;
+            dumpPackage = null;
+            if (opti + 2 <= args.length) {
+                for (int i = opti; i < args.length; i++) {
+                    String arg = args[i];
+                    switch (arg) {
+                        case "--uid":
+                            filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
+                            if (filteredUid == Process.INVALID_UID) {
+                                return;
+                            }
+                            break;
+                        default:
+                            pw.printf("Invalid argument at index %d: %s\n", i, arg);
+                            return;
+                    }
+                }
+            }
+        }
+        if (DEBUG_BROADCAST) {
+            Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
+                            + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory,
+                    onlyReceivers, filteredUid);
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
+        if (!onlyHistory && dumpAll) {
+            if (mRegisteredReceivers.size() > 0) {
+                boolean printed = false;
+                Iterator it = mRegisteredReceivers.values().iterator();
+                while (it.hasNext()) {
+                    ReceiverList r = (ReceiverList) it.next();
+                    if (dumpPackage != null && (r.app == null
+                            || !dumpPackage.equals(r.app.info.packageName))) {
+                        continue;
+                    }
+                    if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
+                        if (DEBUG_BROADCAST) {
+                            Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
+                                    + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
+                        }
+                        continue;
+                    }
+                    if (!printed) {
+                        pw.println("  Registered Receivers:");
+                        needSep = true;
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("  * "); pw.println(r);
+                    r.dump(pw, "    ");
+                }
+            } else {
+                if (onlyReceivers) {
+                    pw.println("  (no registered receivers)");
+                }
+            }
+
+            if (!onlyReceivers) {
+                if (mReceiverResolver.dump(pw, needSep
+                                ? "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
+                        "    ", dumpPackage, false, false)) {
+                    needSep = true;
+                    printedAnything = true;
+                }
+            }
+        }
+
+        if (!onlyReceivers) {
+            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+            printedAnything |= needSep;
+        }
+
+        needSep = true;
+
+        synchronized (mStickyBroadcasts) {
+            if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
+                    && dumpPackage == null) {
+                for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printedAnything = true;
+                    pw.print("  Sticky broadcasts for user ");
+                    pw.print(mStickyBroadcasts.keyAt(user));
+                    pw.println(":");
+                    StringBuilder sb = new StringBuilder(128);
+                    for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+                            : mStickyBroadcasts.valueAt(user).entrySet()) {
+                        pw.print("  * Sticky action ");
+                        pw.print(ent.getKey());
+                        if (dumpAll) {
+                            pw.println(":");
+                            ArrayList<StickyBroadcast> broadcasts = ent.getValue();
+                            final int N = broadcasts.size();
+                            for (int i = 0; i < N; i++) {
+                                final Intent intent = broadcasts.get(i).intent;
+                                final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
+                                sb.setLength(0);
+                                sb.append("    Intent: ");
+                                intent.toShortString(sb, false, true, false, false);
+                                pw.print(sb);
+                                if (deferUntilActive) {
+                                    pw.print(" [D]");
+                                }
+                                pw.println();
+                                pw.print("      originalCallingUid: ");
+                                pw.println(broadcasts.get(i).originalCallingUid);
+                                pw.println();
+                                Bundle bundle = intent.getExtras();
+                                if (bundle != null) {
+                                    pw.print("      extras: ");
+                                    pw.println(bundle);
+                                }
+                            }
+                        } else {
+                            pw.println("");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!onlyHistory && !onlyReceivers && dumpAll) {
+            pw.println();
+            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
+                    + mBroadcastQueue.describeStateLocked());
+            pw.println("  mHandler:");
+            mService.mHandler.dump(new PrintWriterPrinter(pw), "    ");
+            needSep = true;
+            printedAnything = true;
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    /**
+     * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
+     * message on {@code pw} when it cannot be parsed.
+     *
+     * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
+     */
+    private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
+        if (index > args.length) {
+            pw.println("Missing argument");
+            return invalidValue;
+        }
+        String arg = args[index];
+        try {
+            return Integer.parseInt(arg);
+        } catch (Exception e) {
+            pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
+            return invalidValue;
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        if (mCurBroadcastStats == null) {
+            return;
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
+        final long now = SystemClock.elapsedRealtime();
+        if (mLastBroadcastStats != null) {
+            pw.print("  Last stats (from ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
+            pw.print(" to ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
+            pw.print(", ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
+                    - mLastBroadcastStats.mStartUptime, pw);
+            pw.println(" uptime):");
+            if (!mLastBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
+                pw.println("    (nothing)");
+            }
+            pw.println();
+        }
+        pw.print("  Current stats (from ");
+        TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
+        pw.print(" to now, ");
+        TimeUtils.formatDuration(SystemClock.uptimeMillis()
+                - mCurBroadcastStats.mStartUptime, pw);
+        pw.println(" uptime):");
+        if (!mCurBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
+            pw.println("    (nothing)");
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean fullCheckin, String dumpPackage) {
+        if (mCurBroadcastStats == null) {
+            return;
+        }
+
+        if (mLastBroadcastStats != null) {
+            mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+            if (fullCheckin) {
+                mLastBroadcastStats = null;
+                return;
+            }
+        }
+        mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+        if (fullCheckin) {
+            mCurBroadcastStats = null;
+        }
+    }
+
+    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+        if (mRegisteredReceivers.size() > 0) {
+            Iterator it = mRegisteredReceivers.values().iterator();
+            while (it.hasNext()) {
+                ReceiverList r = (ReceiverList) it.next();
+                r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
+            }
+        }
+        mReceiverResolver.dumpDebug(proto,
+                ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
+        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+        synchronized (mStickyBroadcasts) {
+            for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+                long token = proto.start(
+                        ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
+                proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+                        : mStickyBroadcasts.valueAt(user).entrySet()) {
+                    long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+                    proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+                    for (StickyBroadcast broadcast : ent.getValue()) {
+                        broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
+                                false, true, true, false);
+                    }
+                    proto.end(actionToken);
+                }
+                proto.end(token);
+            }
+        }
+
+        long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
+        proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER,
+                mService.mHandler.toString());
+        mService.mHandler.getLooper().dumpDebug(proto,
+                ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
+        proto.end(handlerToken);
+    }
+
+    @VisibleForTesting
+    static final class StickyBroadcast {
+        public Intent intent;
+        public boolean deferUntilActive;
+        public int originalCallingUid;
+        /** The snapshot process state of the app who sent this broadcast */
+        public int originalCallingAppProcessState;
+        public String resolvedDataType;
+
+        public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
+                int originalCallingUid, int originalCallingAppProcessState,
+                String resolvedDataType) {
+            final StickyBroadcast b = new StickyBroadcast();
+            b.intent = intent;
+            b.deferUntilActive = deferUntilActive;
+            b.originalCallingUid = originalCallingUid;
+            b.originalCallingAppProcessState = originalCallingAppProcessState;
+            b.resolvedDataType = resolvedDataType;
+            return b;
+        }
+
+        @Override
+        public String toString() {
+            return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
+                    + originalCallingUid + ", originalCallingAppProcessState="
+                    + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 7d26004..5488446 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -150,6 +150,13 @@
     }
 
     /**
+     * Flush the brightness update that has been made to the persistent data store.
+     */
+    public void saveIfNeeded() {
+        mPersistentDataStore.saveIfNeeded();
+    }
+
+    /**
      * @return The brightness for the default display in nits. Used when the underlying display
      * device has changed but we want to persist the nit value.
      */
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55a6ce7..187caba 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1181,7 +1181,10 @@
 
     private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
             frameRateOverrides, DisplayInfo info, int callingUid) {
+        // Start with the display frame rate
         float frameRateHz = info.renderFrameRate;
+
+        // If the app has a specific override, use that instead
         for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
             if (frameRateOverride.uid == callingUid) {
                 frameRateHz = frameRateOverride.frameRateHz;
@@ -1200,18 +1203,21 @@
                                 DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
 
         // Override the refresh rate only if it is a divisor of the current
-        // refresh rate. This calculation needs to be in sync with the native code
+        // vsync rate. This calculation needs to be in sync with the native code
         // in RefreshRateSelector::getFrameRateDivisor
         Display.Mode currentMode = info.getMode();
-        float numPeriods = currentMode.getRefreshRate() / frameRateHz;
+        float vsyncRate = currentMode.getVsyncRate();
+        float numPeriods = vsyncRate / frameRateHz;
         float numPeriodsRound = Math.round(numPeriods);
         if (Math.abs(numPeriods - numPeriodsRound) > THRESHOLD_FOR_REFRESH_RATES_DIVISORS) {
             return info;
         }
-        frameRateHz = currentMode.getRefreshRate() / numPeriodsRound;
+        frameRateHz = vsyncRate / numPeriodsRound;
 
         DisplayInfo overriddenInfo = new DisplayInfo();
         overriddenInfo.copyFrom(info);
+
+        // If there is a mode that matches the override, use that one
         for (Display.Mode mode : info.supportedModes) {
             if (!mode.equalsExceptRefreshRate(currentMode)) {
                 continue;
@@ -1231,8 +1237,9 @@
                 return overriddenInfo;
             }
         }
-
         overriddenInfo.refreshRateOverride = frameRateHz;
+
+        // Create a fake mode for app compat
         if (!displayModeReturnsPhysicalRefreshRate) {
             overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
                     info.supportedModes.length + 1);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ab79713..a887f6d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2484,6 +2484,11 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
         mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
                 == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
+            // In manual mode, all brightness changes should be saved immediately.
+            mDisplayBrightnessController.saveBrightnessIfNeeded();
+        }
     }
 
 
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index e157b05..72a91d5 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -339,6 +339,13 @@
     }
 
     /**
+     * Flush the brightness update that has been made to the persistent data store.
+     */
+    public void saveBrightnessIfNeeded() {
+        mBrightnessSetting.saveIfNeeded();
+    }
+
+    /**
      * Sets the current screen brightness, and notifies the BrightnessSetting about the change.
      */
     public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f34b4e9..7ce9ee6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3037,6 +3037,7 @@
                 intent.putExtra("input_method_id", id);
                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
             }
+            bindingController.unbindCurrentMethod();
             unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ee0159d..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3065,10 +3065,9 @@
             case DumpState.DUMP_PREFERRED_XML:
             {
                 pw.flush();
-                FileOutputStream fout = new FileOutputStream(fd);
-                BufferedOutputStream str = new BufferedOutputStream(fout);
                 TypedXmlSerializer serializer = Xml.newFastSerializer();
-                try {
+                try (BufferedOutputStream str =
+                             new BufferedOutputStream(new FileOutputStream(fd))) {
                     serializer.setOutput(str, StandardCharsets.UTF_8.name());
                     serializer.startDocument(null, true);
                     serializer.setFeature(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5105fd3..ada6659b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2231,8 +2231,9 @@
                 //  by apexd to be more accurate.
                 installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced(
                         deletedPkgSetting, allUsers);
-                installRequest.setScannedPackageSettingLastUpdateTime(
-                        System.currentTimeMillis());
+                long currentTime = System.currentTimeMillis();
+                installRequest.setScannedPackageSettingLastUpdateTime(currentTime);
+                installRequest.setScannedPackageSettingFirstInstallTime(currentTime);
 
                 installRequest.getRemovedInfo().mBroadcastAllowList =
                         mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index dd2583a0d..ae7749b 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -58,6 +58,7 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -867,6 +868,14 @@
         mScanResult.mPkgSetting.setLastUpdateTime(lastUpdateTim);
     }
 
+    public void setScannedPackageSettingFirstInstallTime(long firstInstallTime) {
+        assertScanResultExists();
+        PackageUserStateInternal userState = mScanResult.mPkgSetting.getUserStates().get(mUserId);
+        if (userState != null && userState.getFirstInstallTimeMillis() == 0) {
+            mScanResult.mPkgSetting.setFirstInstallTime(firstInstallTime, mUserId);
+        }
+    }
+
     public void setRemovedAppId(int appId) {
         if (mRemovedInfo != null) {
             mRemovedInfo.mUid = appId;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 61fddba..0802e9e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -443,17 +443,16 @@
 
         // Take care of first install / last update times.
         final long scanFileTime = getLastModifiedTime(parsedPackage);
-        final long existingFirstInstallTime = userId == UserHandle.USER_ALL
-                ? PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates())
-                : pkgSetting.readUserState(userId).getFirstInstallTimeMillis();
+        final long earliestFirstInstallTime =
+                PackageStateUtils.getEarliestFirstInstallTime((pkgSetting.getUserStates()));
         if (currentTime != 0) {
-            if (existingFirstInstallTime == 0) {
+            if (earliestFirstInstallTime == 0) {
                 pkgSetting.setFirstInstallTime(currentTime, userId)
                         .setLastUpdateTime(currentTime);
             } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
                 pkgSetting.setLastUpdateTime(currentTime);
             }
-        } else if (existingFirstInstallTime == 0) {
+        } else if (earliestFirstInstallTime == 0) {
             // We need *something*.  Take time stamp of the file.
             pkgSetting.setFirstInstallTime(scanFileTime, userId)
                     .setLastUpdateTime(scanFileTime);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 00582bf..045d4db 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -282,6 +282,12 @@
             for (int j = 0; j < idSize; j++) {
                 ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
             }
+            if (ShortcutService.DEBUG_REBOOT) {
+                Slog.d(TAG, "Persist shortcut ids pinned by "
+                    + getPackageName() + " from "
+                    + up.userId + "@" + up.packageName + " ids=["
+                    + String.join(", ", ids) + "]");
+            }
             out.endTag(null, TAG_PACKAGE);
         }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 84674b2..60056eb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1850,9 +1850,17 @@
             }
             getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
 
+            if (ShortcutService.DEBUG_REBOOT) {
+                Slog.d(TAG, "Persisting shortcuts from "
+                    + getOwnerUserId() + "@" + getPackageName());
+            }
             for (int j = 0; j < size; j++) {
+                final ShortcutInfo si = mShortcuts.valueAt(j);
                 saveShortcut(
-                        out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
+                        out, si, forBackup, getPackageInfo().isBackupAllowed());
+                if (ShortcutService.DEBUG_REBOOT) {
+                    Slog.d(TAG, si.toSimpleString());
+                }
             }
 
             if (!forBackup) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 021f7aa..25468fa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
-    static final boolean DEBUG_REBOOT = true;
+    static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
 
     @VisibleForTesting
     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2b639fa..4a2d367 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1372,6 +1372,10 @@
         }
 
         if (isHeadlessSystemUserMode()) {
+            if (mContext.getResources()
+                    .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
+                return UserHandle.USER_SYSTEM;
+            }
             // Return the previous foreground user, if there is one.
             final int previousUser = getPreviousFullUserToEnterForeground();
             if (previousUser != UserHandle.USER_NULL) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 530c03f..9d91d3d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8567,7 +8567,8 @@
                 resolvedConfig,
                 mOptOutEdgeToEdge,
                 hasFixedRotationTransform(),
-                getCompatDisplayInsets() != null);
+                getCompatDisplayInsets() != null,
+                task);
         mResolveConfigHint.resetTmpOverrides();
 
         logAppCompatState();
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 9be3f43..670a61d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -195,12 +195,14 @@
      * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
      * All overrides to those fields should be in this method.
      *
+     * Task is only needed for split-screen to apply an offset special handling.
+     *
      * TODO: Consider integrate this with computeConfigByResolveHint()
      */
     static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
             Configuration newParentConfiguration, Configuration inOutConfig,
             boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform,
-            boolean hasCompatDisplayInsets) {
+            boolean hasCompatDisplayInsets, Task task) {
         if (displayContent == null) {
             return;
         }
@@ -223,13 +225,16 @@
         }
         if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig
                 || hasCompatDisplayInsets
-                || isFloating
                 || rotation == ROTATION_UNDEFINED)) {
             // If the insets configuration decoupled logic is not enabled for the app, or the app
             // already has a compat override, or the context doesn't contain enough info to
             // calculate the override, skip the override.
             return;
         }
+        if (isFloating) {
+            // Floating window won't have any insets affect configuration. Skip the override.
+            return;
+        }
         // Make sure the orientation related fields will be updated by the override insets, because
         // fixed rotation has assigned the fields from display's configuration.
         if (hasFixedRotationTransform) {
@@ -255,17 +260,17 @@
             inOutConfig.windowConfiguration.setAppBounds(
                     newParentConfiguration.windowConfiguration.getBounds());
             outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                final DisplayPolicy.DecorInsets.Info decor =
-                        displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
-                if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) {
-                    outAppBounds.intersect(decor.mOverrideNonDecorFrame);
+            if (task != null) {
+                task = task.getCreatedByOrganizerTask();
+                if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
+                    outAppBounds.offset(task.mOffsetXForInsets, task.mOffsetYForInsets);
                 }
-            } else {
-                // TODO(b/358509380): Handle other windowing mode like split screen and freeform
-                //  cases correctly.
-                outAppBounds.inset(displayContent.getDisplayPolicy()
-                        .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+            }
+            final DisplayPolicy.DecorInsets.Info decor =
+                    displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+            outAppBounds.intersectUnchecked(decor.mOverrideNonDecorFrame);
+            if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
+                outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
             }
         }
         float density = inOutConfig.densityDpi;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 92813a8..08b1e37 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -499,6 +499,13 @@
      */
     boolean mIsTrimmableFromRecents;
 
+    /**
+     * Bounds offset should be applied when calculating compatible configuration for apps targeting
+     * SDK level 34 or before.
+     */
+    int mOffsetXForInsets;
+    int mOffsetYForInsets;
+
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -5874,6 +5881,10 @@
         super.dumpInner(prefix, pw, dumpAll, dumpPackage);
         if (mCreatedByOrganizer) {
             pw.println(prefix + "  mCreatedByOrganizer=true");
+            if (mOffsetXForInsets != 0 || mOffsetYForInsets != 0) {
+                pw.println(prefix + "  mOffsetXForInsets=" + mOffsetXForInsets
+                        + " mOffsetYForInsets=" + mOffsetYForInsets);
+            }
         }
         if (mLastNonFullscreenBounds != null) {
             pw.print(prefix); pw.print("  mLastNonFullscreenBounds=");
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9ae881b..a980b77 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -2997,12 +2998,18 @@
         // Make sure display isn't a part of the transition already - needed for legacy transitions.
         if (mDisplayContent.inTransition()) return false;
 
-        if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
-            // Screenshots are turned off when PiP is undergoing changes.
-            return !inPinnedWindowingMode() && getParent() != null
-                    && !getParent().inPinnedWindowingMode();
-        }
-        return true;
+        // Screenshots are turned off when PiP is undergoing changes.
+        return ActivityTaskManagerService.isPip2ExperimentEnabled() || !isPipChange();
+    }
+
+    /** Returns true if WC is pinned and undergoing changes. */
+    private boolean isPipChange() {
+        final boolean isExitingPip = this.asTaskFragment() != null
+                && mTransitionController.getWindowingModeAtStart(this) == WINDOWING_MODE_PINNED
+                && !inPinnedWindowingMode();
+
+        return isExitingPip || inPinnedWindowingMode()
+                || (getParent() != null && getParent().inPinnedWindowingMode());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9d40b16..e1e64ee 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -18,9 +18,12 @@
 
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.isStartResultSuccessful;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
@@ -818,6 +821,31 @@
                 final Configuration c =
                         new Configuration(container.getRequestedOverrideConfiguration());
                 c.setTo(change.getConfiguration(), configMask, windowMask);
+                if (container.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+                        && (change.getConfigSetMask() & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+                    // Special handling for split screen window got offset. The insets calculation
+                    // for configuration should be stable regardless of the offset. Set offset to
+                    // the task level to be applied when calculate compat override for apps
+                    // targeting SDK level 34 or before.
+                    final Task task = container.asTask();
+                    if (task != null) {
+                        if (c.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+                                && c.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+                            final Rect oldBounds = container.getRequestedOverrideBounds();
+                            final Rect newBounds =
+                                    change.getConfiguration().windowConfiguration.getBounds();
+                            if (oldBounds.width() == newBounds.width()
+                                    && oldBounds.height() == newBounds.height()) {
+                                task.mOffsetXForInsets = oldBounds.left - newBounds.left;
+                                task.mOffsetYForInsets = oldBounds.top - newBounds.top;
+                            } else {
+                                task.mOffsetXForInsets = task.mOffsetYForInsets = 0;
+                            }
+                        } else {
+                            task.mOffsetXForInsets = task.mOffsetYForInsets = 0;
+                        }
+                    }
+                }
                 container.onRequestedOverrideConfigurationChanged(c);
             }
             effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2bae0a8..d2aebdee 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1689,7 +1689,8 @@
                 resolvedConfig,
                 false /* optsOutEdgeToEdge */,
                 false /* hasFixedRotationTransform */,
-                false /* hasCompatDisplayInsets */);
+                false /* hasCompatDisplayInsets */,
+                null /* task */);
     }
 
     void dispatchConfiguration(@NonNull Configuration config) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4568f2e..eed0cf7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3002,7 +3002,8 @@
                 resolvedConfig,
                 (mAttrs.privateFlags & PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE) != 0,
                 false /* hasFixedRotationTransform */,
-                false /* hasCompatDisplayInsets */);
+                false /* hasCompatDisplayInsets */,
+                null /* task */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 6984f0d..2c5a453 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -33,6 +33,7 @@
 import android.util.proto.ProtoInputStream;
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
@@ -76,15 +77,15 @@
     private static final String TAG = "WindowTracingDataSource";
 
     @NonNull
-    private final Consumer<Config> mOnStartCallback;
+    private final WeakReference<Consumer<Config>> mOnStartCallback;
     @NonNull
-    private final Consumer<Config> mOnStopCallback;
+    private final WeakReference<Consumer<Config>> mOnStopCallback;
 
     public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
             @NonNull Consumer<Config> onStop) {
         super(DATA_SOURCE_NAME);
-        mOnStartCallback = onStart;
-        mOnStopCallback = onStop;
+        mOnStartCallback = new WeakReference(onStart);
+        mOnStopCallback = new WeakReference(onStop);
 
         Producer.init(InitArguments.DEFAULTS);
         DataSourceParams params =
@@ -102,12 +103,18 @@
         return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
             @Override
             protected void onStart(StartCallbackArguments args) {
-                mOnStartCallback.accept(mConfig);
+                Consumer<Config> callback = mOnStartCallback.get();
+                if (callback != null) {
+                    callback.accept(mConfig);
+                }
             }
 
             @Override
             protected void onStop(StopCallbackArguments args) {
-                mOnStopCallback.accept(mConfig);
+                Consumer<Config> callback = mOnStopCallback.get();
+                if (callback != null) {
+                    callback.accept(mConfig);
+                }
             }
         };
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5013517..1290fb7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23501,6 +23501,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS);
 
         // These permissions may grant access to user data and therefore must be protected with
         // MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls.
@@ -24136,15 +24138,13 @@
 
     @Override
     public @ContentProtectionPolicy int getContentProtectionPolicy(
-            ComponentName who, String callerPackageName) {
+            ComponentName who, String callerPackageName, int userId) {
         if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
             return CONTENT_PROTECTION_DISABLED;
         }
 
         CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-        int userId = caller.getUserId();
         enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, callerPackageName, userId);
-
         Integer policy =
                 mDevicePolicyEngine.getResolvedPolicy(PolicyDefinition.CONTENT_PROTECTION, userId);
         if (policy == null) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index bbf2ecb..026fcc4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2080,6 +2080,31 @@
     }
 
     /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate even if it not
+     * a divisor of the peak refresh rate.
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRateNonPeakDivisor() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{120f}, new float[]{240f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(120f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 80f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(80f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
      * Tests that the mode reflects the render frame rate is in compat mode
      */
     @Test
@@ -3348,13 +3373,26 @@
     }
 
     private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-
                                                       float[] refreshRates) {
         return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN);
     }
 
     private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
                                                       float[] refreshRates,
+                                                      float[] vsyncRates) {
+        return createFakeDisplayDevice(displayManager, refreshRates, vsyncRates,
+                Display.TYPE_UNKNOWN);
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+            float[] refreshRates,
+            int displayType) {
+        return createFakeDisplayDevice(displayManager, refreshRates, refreshRates, displayType);
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+                                                      float[] refreshRates,
+                                                      float[] vsyncRates,
                                                       int displayType) {
         FakeDisplayDevice displayDevice = new FakeDisplayDevice();
         DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
@@ -3363,7 +3401,8 @@
         displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
         for (int i = 0; i < refreshRates.length; i++) {
             displayDeviceInfo.supportedModes[i] =
-                    new Display.Mode(i + 1, width, height, refreshRates[i]);
+                    new Display.Mode(i + 1, width, height, refreshRates[i], vsyncRates[i],
+                            new float[0], new int[0]);
         }
         displayDeviceInfo.modeId = 1;
         displayDeviceInfo.type = displayType;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5840cb9..2166cb7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2213,6 +2213,20 @@
                 /* ignoreAnimationLimits= */ anyBoolean());
     }
 
+    @Test
+    public void testManualBrightnessModeSavesBrightness() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Initialize
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        advanceTime(1);
+
+        verify(mHolder.brightnessSetting).saveIfNeeded();
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e610a32..809e13c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,8 +28,8 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.UserHandle.USER_ALL;
 import static android.util.DebugUtils.valueToString;
 
@@ -68,11 +68,9 @@
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -84,7 +82,6 @@
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.ForegroundServiceDelegationOptions;
-import android.app.IApplicationThread;
 import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -129,7 +126,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.sdksandbox.flags.Flags;
 import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.StickyBroadcast;
+import com.android.server.am.BroadcastController.StickyBroadcast;
 import com.android.server.am.ProcessList.IsolatedUidRange;
 import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
 import com.android.server.am.UidObserverController.ChangeRecord;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 37d87c4e..1cba3c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -52,6 +52,7 @@
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.multiuser.Flags;
 import android.os.PowerManager;
 import android.os.ServiceSpecificException;
@@ -152,6 +153,7 @@
     private File mTestDir;
 
     private Context mSpiedContext;
+    private Resources mSpyResources;
 
     private @Mock PackageManagerService mMockPms;
     private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -193,6 +195,13 @@
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
         mockIsLowRamDevice(false);
 
+        // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+        mSpyResources = spy(mSpiedContext.getResources());
+        when(mSpiedContext.getResources()).thenReturn(mSpyResources);
+        doReturn(false)
+                .when(mSpyResources)
+                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
         // Must construct UserManagerService in the UiThread
         mTestDir = new File(mRealContext.getDataDir(), "umstest");
         mTestDir.mkdirs();
@@ -849,6 +858,16 @@
                         USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
     }
 
+    @Test
+    public void testGetBootUser_enableBootToHeadlessSystemUser() {
+        setSystemUserHeadless(true);
+        doReturn(true)
+                .when(mSpyResources)
+                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
+        assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 8811e00..753cb1f 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -16,12 +16,17 @@
 
 package com.android.server.wm.flicker.helpers
 
+import android.content.Context
+import android.graphics.Insets
 import android.graphics.Rect
+import android.graphics.Region
 import android.platform.uiautomator_helpers.DeviceHelpers
 import android.tools.device.apphelpers.IStandardAppHelper
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.parsers.WindowManagerStateHelper
 import android.tools.traces.wm.WindowingMode
+import android.view.WindowInsets
+import android.view.WindowManager
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.UiDevice
@@ -70,9 +75,7 @@
         // Start dragging a little under the top to prevent dragging the notification shade.
         val startY = 10
 
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
+        val displayRect = getDisplayRect(wmHelper)
 
         // The position we want to drag to
         val endY = displayRect.centerY() / 2
@@ -81,18 +84,61 @@
         device.drag(startX, startY, startX, endY, 100)
     }
 
+    private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+        return caption
+            ?.children
+            ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
+            ?.children
+            ?.get(0)
+            ?: error("Unable to find resource $MAXIMIZE_BUTTON_VIEW\n")
+    }
+
     /** Click maximise button on the app header for the given app. */
     fun maximiseDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val caption = getCaptionForTheApp(wmHelper, device)
-        val maximizeButton =
-            caption
-                ?.children
-                ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
-                ?.children
-                ?.get(0)
-        maximizeButton?.click()
+        val maximizeButton = getMaximizeButtonForTheApp(caption)
+        maximizeButton.click()
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
+
+    /** Open maximize menu and click snap resize button on the app header for the given app. */
+    fun snapResizeDesktopApp(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+        context: Context,
+        toLeft: Boolean
+    ) {
+        val caption = getCaptionForTheApp(wmHelper, device)
+        val maximizeButton = getMaximizeButtonForTheApp(caption)
+        maximizeButton?.longClick()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+        val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
+        val maximizeMenu = getDesktopAppViewByRes(MAXIMIZE_MENU)
+
+        val snapResizeButton =
+            maximizeMenu
+                ?.wait(Until.findObject(By.res(SYSTEMUI_PACKAGE, buttonResId)), TIMEOUT.toMillis())
+                ?: error("Unable to find object with resource id $buttonResId")
+        snapResizeButton.click()
+
+        val displayRect = getDisplayRect(wmHelper)
+        val insets = getWindowInsets(
+            context, WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
+        displayRect.inset(insets)
+
+        val expectedWidth = displayRect.width() / 2
+        val expectedRect = Rect(displayRect).apply {
+            if (toLeft) right -= expectedWidth else left += expectedWidth
+        }
+
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withSurfaceVisibleRegion(this, Region(expectedRect))
+            .waitForAndVerify()
+    }
+
     /** Click close button on the app header for the given app. */
     fun closeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val caption = getCaptionForTheApp(wmHelper, device)
@@ -112,8 +158,7 @@
         if (
             wmHelper.getWindow(innerHelper)?.windowingMode !=
             WindowingMode.WINDOWING_MODE_FREEFORM.value
-        )
-            error("expected a freeform window with caption but window is not in freeform mode")
+        ) error("expected a freeform window with caption but window is not in freeform mode")
         val captions =
             device.wait(Until.findObjects(caption), TIMEOUT.toMillis())
                 ?: error("Unable to find view $caption\n")
@@ -156,6 +201,30 @@
             .waitForAndVerify()
     }
 
+    /** Drag a window to a snap resize region, found at the left and right edges of the screen. */
+    fun dragToSnapResizeRegion(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+        isLeft: Boolean,
+    ) {
+        val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+        // Set start x-coordinate as center of app header.
+        val startX = windowRect.centerX()
+        val startY = windowRect.top
+
+        val displayRect = getDisplayRect(wmHelper)
+
+        val endX = if (isLeft) displayRect.left else displayRect.right
+        val endY = displayRect.centerY() / 2
+
+        // drag the window to snap resize
+        device.drag(startX, startY, endX, endY, /* steps= */ 100)
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .waitForAndVerify()
+    }
+
     private fun getStartCoordinatesForCornerResize(
         windowRect: Rect,
         corner: Corners
@@ -179,9 +248,7 @@
 
     private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
+        val displayRect = getDisplayRect(wmHelper)
 
         val startX = windowRect.centerX()
         val endX = displayRect.centerX()
@@ -194,7 +261,8 @@
 
     fun enterDesktopModeFromAppHandleMenu(
         wmHelper: WindowManagerStateHelper,
-        device: UiDevice) {
+        device: UiDevice
+    ) {
         val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
         val startX = windowRect.centerX()
         // Click a little under the top to prevent opening the notification shade.
@@ -204,7 +272,7 @@
         device.click(startX, startY)
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
 
-        val pill = getAppHandlePillForWindow()
+        val pill = getDesktopAppViewByRes(PILL_CONTAINER)
         val desktopModeButton =
             pill
                 ?.children
@@ -214,10 +282,13 @@
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
 
-    private fun getAppHandlePillForWindow(): UiObject2? {
-        val pillContainer: BySelector = By.res(SYSTEMUI_PACKAGE, PILL_CONTAINER)
-        return DeviceHelpers.waitForObj(pillContainer, TIMEOUT)
-    }
+    private fun getDesktopAppViewByRes(viewResId: String): UiObject2? =
+        DeviceHelpers.waitForObj(By.res(SYSTEMUI_PACKAGE, viewResId), TIMEOUT)
+
+    private fun getDisplayRect(wmHelper: WindowManagerStateHelper): Rect =
+        wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+            ?: throw IllegalStateException("Default display is null")
+
 
     /** Wait for transition to full screen to finish. */
     private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
@@ -228,13 +299,23 @@
             .waitForAndVerify()
     }
 
+    private fun getWindowInsets(context: Context, typeMask: Int): Insets {
+        val wm: WindowManager = context.getSystemService(WindowManager::class.java)
+            ?: error("Unable to connect to WindowManager service")
+        val metricInsets = wm.currentWindowMetrics.windowInsets
+        return metricInsets.getInsetsIgnoringVisibility(typeMask)
+    }
+
     private companion object {
-        val TIMEOUT = Duration.ofSeconds(3)
-        val CAPTION = "desktop_mode_caption"
-        val MAXIMIZE_BUTTON_VIEW = "maximize_button_view"
-        val CLOSE_BUTTON = "close_window"
-        val PILL_CONTAINER = "windowing_pill"
-        val DESKTOP_MODE_BUTTON = "desktop_button"
+        val TIMEOUT: Duration = Duration.ofSeconds(3)
+        const val CAPTION: String = "desktop_mode_caption"
+        const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view"
+        const val MAXIMIZE_MENU: String = "maximize_menu"
+        const val CLOSE_BUTTON: String = "close_window"
+        const val PILL_CONTAINER: String = "windowing_pill"
+        const val DESKTOP_MODE_BUTTON: String = "desktop_button"
+        const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
+        const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
         val caption: BySelector
             get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
     }
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
new file mode 100644
index 0000000..9d56a92
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link ProtoLog}. */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogTest {
+
+    @Test
+    public void canRunProtoLogInitMultipleTimes() {
+        ProtoLog.init(TEST_GROUP_1);
+        ProtoLog.init(TEST_GROUP_1);
+        ProtoLog.init(TEST_GROUP_2);
+        ProtoLog.init(TEST_GROUP_1, TEST_GROUP_2);
+
+        final var instance = ProtoLog.getSingleInstance();
+        Truth.assertThat(instance.getRegisteredGroups())
+                .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+    }
+
+    private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
+    private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+
+    private static class ProtoLogGroup implements IProtoLogGroup {
+        private final boolean mEnabled;
+        private volatile boolean mLogToProto;
+        private volatile boolean mLogToLogcat;
+        private final String mTag;
+        private final int mId;
+
+        ProtoLogGroup(String tag, int id) {
+            this(true, true, false, tag, id);
+        }
+
+        ProtoLogGroup(
+                boolean enabled, boolean logToProto, boolean logToLogcat, String tag, int id) {
+            this.mEnabled = enabled;
+            this.mLogToProto = logToProto;
+            this.mLogToLogcat = logToLogcat;
+            this.mTag = tag;
+            this.mId = id;
+        }
+
+        @Override
+        public String name() {
+            return mTag;
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
+        @Override
+        public boolean isLogToProto() {
+            return mLogToProto;
+        }
+
+        @Override
+        public boolean isLogToLogcat() {
+            return mLogToLogcat;
+        }
+
+        @Override
+        public boolean isLogToAny() {
+            return mLogToLogcat || mLogToProto;
+        }
+
+        @Override
+        public String getTag() {
+            return mTag;
+        }
+
+        @Override
+        public void setLogToProto(boolean logToProto) {
+            this.mLogToProto = logToProto;
+        }
+
+        @Override
+        public void setLogToLogcat(boolean logToLogcat) {
+            this.mLogToLogcat = logToLogcat;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+}