Merge "Update Goldens for WMShell Tests" into main
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 7e4f95b..01b20f4 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <android/bitmap.h>
+#include <android/gui/DisplayCaptureArgs.h>
+#include <binder/ProcessState.h>
 #include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
@@ -33,7 +36,6 @@
 
 #include <ftl/concat.h>
 #include <ftl/optional.h>
-#include <gui/DisplayCaptureArgs.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
diff --git a/core/api/current.txt b/core/api/current.txt
index c1366a1..c06b814 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8724,6 +8724,13 @@
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
   }
 
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
+    ctor public AppFunctionService();
+    method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+    field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+  }
+
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionRequest implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.os.Bundle getExtras();
@@ -8741,6 +8748,31 @@
     method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
   }
 
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getErrorMessage();
+    method @NonNull public android.os.Bundle getExtras();
+    method public int getResultCode();
+    method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
+    method public boolean isSuccess();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
+    field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+    field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+    field public static final int RESULT_DENIED = 1; // 0x1
+    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_TIMED_OUT = 5; // 0x5
+  }
+
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final class ExecuteAppFunctionResponse.Builder {
+    ctor public ExecuteAppFunctionResponse.Builder(@NonNull android.app.appsearch.GenericDocument);
+    ctor public ExecuteAppFunctionResponse.Builder(int, @NonNull String);
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse build();
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse.Builder setExtras(@NonNull android.os.Bundle);
+  }
+
 }
 
 package android.app.assist {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 69b5222..3d1a785 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -393,7 +393,7 @@
      * changes.
      *
      * <p>This broadcast is only sent to registered receivers and receivers in packages that have
-     * been granted Do Not Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+     * been granted Notification Policy access (see {@link #isNotificationPolicyAccessGranted()}).
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -1627,7 +1627,7 @@
     }
 
     /**
-     * Checks the ability to modify notification do not disturb policy for the calling package.
+     * Checks the ability to modify Notification Policy for the calling package.
      *
      * <p>
      * Returns true if the calling package can modify notification policy.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 114a2c4..cb38cf2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -475,7 +475,7 @@
                 if (service == null
                         && ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
                         && android.server.Flags.allowRemovingVpnService()) {
-                    throw new ServiceNotFoundException(Context.VPN_MANAGEMENT_SERVICE);
+                    return null;
                 }
                 return new VpnManager(ctx, service);
             }});
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
new file mode 100644
index 0000000..fca465f
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -0,0 +1,126 @@
+/*
+ * 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 static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class to provide app functions to the system.
+ *
+ * <p>Include the following in the manifest:
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourService"
+ *       android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
+ *    <intent-filter>
+ *      <action android:name="android.app.appfunctions.AppFunctionService" />
+ *    </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @see AppFunctionManager
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public abstract class AppFunctionService extends Service {
+    /**
+     * The {@link Intent} that must be declared as handled by the service. To be supported, the
+     * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other
+     * applications can not abuse it.
+     */
+    @NonNull
+    public static final String SERVICE_INTERFACE =
+            "android.app.appfunctions.AppFunctionService";
+
+    private final Binder mBinder =
+            new IAppFunctionService.Stub() {
+                @Override
+                public void executeAppFunction(
+                        @NonNull ExecuteAppFunctionRequest request,
+                        @NonNull IExecuteAppFunctionCallback callback) {
+                    if (AppFunctionService.this.checkCallingPermission(
+                            BIND_APP_FUNCTION_SERVICE) == PERMISSION_DENIED) {
+                        throw new SecurityException("Can only be called by the system server.");
+                    }
+                    SafeOneTimeExecuteAppFunctionCallback safeCallback =
+                            new SafeOneTimeExecuteAppFunctionCallback(callback);
+                    try {
+                        AppFunctionService.this.onExecuteFunction(
+                                request,
+                                safeCallback::onResult);
+                    } catch (Exception ex) {
+                        // Apps should handle exceptions. But if they don't, report the error on
+                        // behalf of them.
+                        safeCallback.onResult(
+                                new ExecuteAppFunctionResponse.Builder(
+                                        getResultCode(ex), ex.getMessage()).build());
+                    }
+                }
+            };
+
+    private static int getResultCode(@NonNull Throwable t) {
+        if (t instanceof IllegalArgumentException) {
+            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+        }
+        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+    }
+
+    @NonNull
+    @Override
+    public final IBinder onBind(@Nullable Intent intent) {
+        return mBinder;
+    }
+
+    /**
+     * Called by the system to execute a specific app function.
+     *
+     * <p>This method is triggered when the system requests your AppFunctionService to handle a
+     * particular function you have registered and made available.
+     *
+     * <p>To ensure proper routing of function requests, assign a unique identifier to each
+     * function. This identifier doesn't need to be globally unique, but it must be unique within
+     * your app. For example, a function to order food could be identified as "orderFood". In most
+     * cases this identifier should come from the ID automatically generated by the AppFunctions
+     * SDK. You can determine the specific function to invoke by calling {@link
+     * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+     *
+     * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+     * thread and dispatch the result with the given callback. You should always report back the
+     * result using the callback, no matter if the execution was successful or not.
+     *
+     * @param request The function execution request.
+     * @param callback A callback to report back the result.
+     */
+    @MainThread
+    public abstract void onExecuteFunction(
+            @NonNull ExecuteAppFunctionRequest request,
+            @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl
new file mode 100644
index 0000000..5194e7a
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.appfunctions.ExecuteAppFunctionResponse;
+
+parcelable ExecuteAppFunctionResponse;
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..72205d9
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -0,0 +1,294 @@
+/*
+ * 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 static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The response to an app function execution.
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class ExecuteAppFunctionResponse implements Parcelable {
+    @NonNull
+    public static final Creator<ExecuteAppFunctionResponse> CREATOR =
+            new Creator<ExecuteAppFunctionResponse>() {
+                @Override
+                public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
+                    GenericDocument result =
+                            Objects.requireNonNull(GenericDocument.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);
+                }
+
+                @Override
+                public ExecuteAppFunctionResponse[] newArray(int size) {
+                    return new ExecuteAppFunctionResponse[size];
+                }
+            };
+    /**
+     * The name of the property that stores the function return value within the
+     * {@code resultDocument}.
+     *
+     * <p>See {@link GenericDocument#getProperty(String)} for more information.
+     *
+     * <p>If the function returns {@code void} or throws an error, the {@code resultDocument}
+     * will be empty {@link GenericDocument}.
+     *
+     * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+     * return {@code null}.
+     *
+     * <p>See {@link #getResultDocument} for more information on extracting the return value.
+     */
+    public static final String PROPERTY_RETURN_VALUE = "returnValue";
+
+    /**
+     * The call was successful.
+     */
+    public static final int RESULT_OK = 0;
+
+    /**
+     * The caller does not have the permission to execute an app function.
+     */
+    public static final int RESULT_DENIED = 1;
+
+    /**
+     * An unknown error occurred while processing the call in the AppFunctionService.
+     */
+    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
+
+    /**
+     * An internal error occurred within AppFunctionManagerService.
+     *
+     * <p>This error may be considered similar to {@link IllegalStateException}
+     */
+    public static final int RESULT_INTERNAL_ERROR = 3;
+
+    /**
+     * The caller supplied invalid arguments to the call.
+     *
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     */
+    public static final int RESULT_INVALID_ARGUMENT = 4;
+
+    /**
+     * The operation was timed out.
+     */
+    public static final int RESULT_TIMED_OUT = 5;
+
+    /**
+     * The result code of the app function execution.
+     */
+    @ResultCode
+    private final int mResultCode;
+
+    /**
+     * The error message associated with the result, if any. This is {@code null} if the result code
+     * is {@link #RESULT_OK}.
+     */
+    @Nullable
+    private final String mErrorMessage;
+
+    /**
+     * Returns the return value of the executed function.
+     *
+     * <p>The return value is stored in a {@link GenericDocument} with the key
+     * {@link #PROPERTY_RETURN_VALUE}.
+     *
+     * <p>See {@link #getResultDocument} for more information on extracting the return value.
+     */
+    @NonNull
+    private final GenericDocument mResultDocument;
+
+    /**
+     * Returns the additional metadata data relevant to this function execution response.
+     */
+    @NonNull
+    private final Bundle mExtras;
+
+    private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+                                       @NonNull Bundle extras,
+                                       @ResultCode int resultCode,
+                                       @Nullable String errorMessage) {
+        mResultDocument = Objects.requireNonNull(resultDocument);
+        mExtras = Objects.requireNonNull(extras);
+        mResultCode = resultCode;
+        mErrorMessage = errorMessage;
+    }
+
+    /**
+     * 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>
+     *
+     * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
+     * function does not produce a return value.
+     *
+     * <p>Sample code for extracting the return value:
+     * <pre>
+     *     GenericDocument resultDocument = response.getResultDocument();
+     *     Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+     *     if (returnValue != null) {
+     *       // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+     *       // {@link GenericDocument#getPropertyLong} etc.
+     *       // Do something with the returnValue
+     *     }
+     * </pre>
+     */
+    @NonNull
+    public GenericDocument getResultDocument() {
+        return mResultDocument;
+    }
+
+    /**
+     * Returns the extras of the app function execution.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Returns {@code true} if {@link #getResultCode} equals
+     * {@link ExecuteAppFunctionResponse#RESULT_OK}.
+     */
+    public boolean isSuccess() {
+        return getResultCode() == RESULT_OK;
+    }
+
+    /**
+     * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
+     */
+    @ResultCode
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Returns the error message associated with this result.
+     *
+     * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
+     */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mResultDocument.writeToParcel(dest, flags);
+        dest.writeBundle(mExtras);
+        dest.writeInt(mResultCode);
+        dest.writeString8(mErrorMessage);
+    }
+
+    /**
+     * Result codes.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"RESULT_"},
+            value = {
+                    RESULT_OK,
+                    RESULT_DENIED,
+                    RESULT_APP_UNKNOWN_ERROR,
+                    RESULT_INTERNAL_ERROR,
+                    RESULT_INVALID_ARGUMENT,
+                    RESULT_TIMED_OUT,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {
+    }
+
+    /**
+     * 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();
+        @NonNull
+        private Bundle mExtras = Bundle.EMPTY;
+        private int mResultCode;
+        @Nullable
+        private String mErrorMessage;
+
+        /**
+         * Creates a new builder for {@link ExecuteAppFunctionResponse}.
+         */
+        private Builder() {
+        }
+
+        /**
+         * Creates a new builder for {@link ExecuteAppFunctionResponse} to build a success response
+         * with a result code of {@link #RESULT_OK} and a resultDocument.
+         */
+        public Builder(@NonNull GenericDocument resultDocument) {
+            mResultDocument = Objects.requireNonNull(resultDocument);
+            mResultCode = RESULT_OK;
+        }
+
+        /**
+         * Creates a new builder for {@link ExecuteAppFunctionResponse} to build an error response
+         * with a result code and an error message.
+         */
+        public Builder(@ResultCode int resultCode,
+                       @NonNull String errorMessage) {
+            mResultCode = resultCode;
+            mErrorMessage = Objects.requireNonNull(errorMessage);
+        }
+
+        /**
+         * Sets the extras of the app function execution.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = Objects.requireNonNull(extras);
+            return this;
+        }
+
+        /**
+         * Builds the {@link ExecuteAppFunctionResponse} instance.
+         */
+        @NonNull
+        public ExecuteAppFunctionResponse build() {
+            return new ExecuteAppFunctionResponse(
+                    mResultDocument, mExtras, mResultCode, mErrorMessage);
+        }
+    }
+}
diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl
new file mode 100644
index 0000000..12b5c55
--- /dev/null
+++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.os.Bundle;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.ExecuteAppFunctionRequest;
+
+
+ /** {@hide} */
+oneway interface IAppFunctionService {
+    /**
+     * Called by the system to execute a specific app function.
+     *
+     * @param request  the function execution request.
+     * @param callback a callback to report back the result.
+     */
+    void executeAppFunction(
+        in ExecuteAppFunctionRequest request,
+        in IExecuteAppFunctionCallback callback
+    );
+}
diff --git a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
new file mode 100644
index 0000000..5323f9b
--- /dev/null
+++ b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+
+/** {@hide} */
+oneway interface IExecuteAppFunctionCallback {
+    void onResult(in ExecuteAppFunctionResponse result);
+}
diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
new file mode 100644
index 0000000..86fc369
--- /dev/null
+++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
@@ -0,0 +1,75 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper of IExecuteAppFunctionCallback which swallows the {@link RemoteException}. This
+ * callback is intended for one-time use only. Subsequent calls to onResult() will be ignored.
+ *
+ * @hide
+ */
+public class SafeOneTimeExecuteAppFunctionCallback {
+    private static final String TAG = "SafeOneTimeExecuteApp";
+
+    private final AtomicBoolean mOnResultCalled = new AtomicBoolean(false);
+
+    @NonNull private final IExecuteAppFunctionCallback mCallback;
+
+    @Nullable private final Consumer<ExecuteAppFunctionResponse> mOnDispatchCallback;
+
+    public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) {
+        this(callback, /* onDispatchCallback= */ null);
+    }
+
+    /**
+     * @param callback The callback to wrap.
+     * @param onDispatchCallback An optional callback invoked after the wrapped callback has been
+     *     dispatched with a result. This callback receives the result that has been dispatched.
+     */
+    public SafeOneTimeExecuteAppFunctionCallback(
+            @NonNull IExecuteAppFunctionCallback callback,
+            @Nullable Consumer<ExecuteAppFunctionResponse> onDispatchCallback) {
+        mCallback = Objects.requireNonNull(callback);
+        mOnDispatchCallback = onDispatchCallback;
+    }
+
+    /** Invoke wrapped callback with the result. */
+    public void onResult(@NonNull ExecuteAppFunctionResponse result) {
+        if (!mOnResultCalled.compareAndSet(false, true)) {
+            Log.w(TAG, "Ignore subsequent calls to onResult()");
+            return;
+        }
+        try {
+            mCallback.onResult(result);
+        } catch (RemoteException ex) {
+            // Failed to notify the other end. Ignore.
+            Log.w(TAG, "Failed to invoke the callback", ex);
+        }
+        if (mOnDispatchCallback != null) {
+            mOnDispatchCallback.accept(result);
+        }
+    }
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index f751a23..f05c24f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -21,6 +21,13 @@
 }
 
 flag {
+  name: "modes_ui_test"
+  namespace: "systemui"
+  description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui"
+  bug: "360862012"
+}
+
+flag {
   name: "api_tvextender"
   is_exported: true
   namespace: "systemui"
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index c7716e5..ffadd1e 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -112,6 +112,39 @@
                     "exclude-annotation":"org.junit.Ignore"
                 }
             ]
+        },
+        {
+            "name": "CtsPackageInstallerCUJInstallationTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
+        },
+        {
+            "name": "CtsPackageInstallerCUJUninstallationTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
+        },
+        {
+            "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
         }
     ],
     "presubmit-large":[
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 28f2c25..536eca6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -969,9 +969,7 @@
 
     /**
      * Specifies if a user is disallowed from adding new users. This can only be set by device
-     * owners or profile owners on the primary user. The default value is <code>false</code>.
-     * <p>This restriction has no effect on secondary users and managed profiles since only the
-     * primary user can add other users.
+     * owners or profile owners on the main user. The default value is <code>false</code>.
      * <p> When the device is an organization-owned device provisioned with a managed profile,
      * this restriction will be set as a base restriction which cannot be removed by any admin.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 184bac4..85d2325 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1972,10 +1972,10 @@
             "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
 
     /**
-     * Activity Action: Show Do Not Disturb access settings.
+     * Activity Action: Show Notification Policy access settings.
      * <p>
-     * Users can grant and deny access to Do Not Disturb configuration from here. Managed
-     * profiles cannot grant Do Not Disturb access.
+     * Users can grant and deny access to Notification Policy (DND / Priority Modes) configuration
+     * from here. Managed profiles cannot grant Notification Policy access.
      * See {@link android.app.NotificationManager#isNotificationPolicyAccessGranted()} for more
      * details.
      * <p>
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 1c3d738..40070c7 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -259,6 +259,16 @@
 }
 
 flag {
+  name: "dont_break_email_in_nobreak_tag"
+  namespace: "text"
+  description: "Prevent line break inside email."
+  bug: "350691716"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "handwriting_gesture_with_transformation"
   namespace: "text"
   description: "Fix handwriting gesture is not working when view has transformation."
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 017e004..67a207e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3462,6 +3462,15 @@
         public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
 
         /**
+         * Indicates that the window should receive key events including Action/Meta key.
+         * They will not be intercepted as usual and instead will be passed to the window with other
+         * key events.
+         * TODO(b/358569822) Remove this once we have nicer API for listening to shortcuts
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS = 1 << 23;
+
+        /**
          * Flag to indicate that the window is color space agnostic, and the color can be
          * interpreted to any color space.
          * @hide
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index eb35817..1922327 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,6 +83,7 @@
 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;
@@ -6246,6 +6247,18 @@
 
     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
@@ -6384,7 +6397,7 @@
 
         private View mResult;
         private ViewTree mTree;
-        private Action[] mActions;
+        private List<Action> mActions;
         private Exception mError;
 
         private AsyncApplyTask(
@@ -6411,11 +6424,20 @@
 
                 if (mRV.mActions != null) {
                     int count = mRV.mActions.size();
-                    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);
+                    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();
                     }
                 } else {
                     mActions = null;
@@ -6437,14 +6459,7 @@
 
                 try {
                     if (mActions != null) {
-
-                        ActionApplyParams applyParams = mApplyParams.clone();
-                        if (applyParams.handler == null) {
-                            applyParams.handler = DEFAULT_INTERACTION_HANDLER;
-                        }
-                        for (Action a : mActions) {
-                            a.apply(viewTree.mRoot, mParent, applyParams);
-                        }
+                        mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
                     }
                     // If the parent of the view is has is a root, resolve the recycling.
                     if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6620,6 +6635,11 @@
     }
 
     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;
@@ -6630,8 +6650,15 @@
         }
         if (mActions != null) {
             final int count = mActions.size();
-            for (int i = 0; i < count; i++) {
-                mActions.get(i).apply(v, parent, params);
+            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();
             }
         }
     }
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 253337b..fe936f7 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -115,8 +115,11 @@
     @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
     public static final class DisplayChange implements Parcelable {
         private final int mDisplayId;
+
+        // If non-null, these bounds changes should ignore any potential rotation changes.
         @Nullable private Rect mStartAbsBounds = null;
         @Nullable private Rect mEndAbsBounds = null;
+
         private int mStartRotation = WindowConfiguration.ROTATION_UNDEFINED;
         private int mEndRotation = WindowConfiguration.ROTATION_UNDEFINED;
         private boolean mPhysicalDisplayChanged = false;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
index 987fd41..ec5ff4c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
@@ -174,7 +174,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -298,9 +298,9 @@
     }
 
     @DataClass.Generated(
-            time = 1642560323360L,
+            time = 1723882842941L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java",
             inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 7d12d49..78b5cfe 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -317,7 +317,11 @@
         }
 
         if (mViewerConfigReader != null) {
-            mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0]));
+            // Load in background to avoid delay in boot process.
+            // The caveat is that any log message that is also logged to logcat will not be
+            // successfully decoded until this completes.
+            mBackgroundLoggingService.execute(() -> mViewerConfigReader
+                    .loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0])));
         }
     }
 
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 1031542..b1a2cea 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -19,6 +19,7 @@
 
 #include <android/gui/BnScreenCaptureListener.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <gui/AidlStatusUtil.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
@@ -141,12 +142,13 @@
 };
 
 static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) {
-    captureArgs.pixelFormat = static_cast<ui::PixelFormat>(
+    captureArgs.pixelFormat = static_cast<int32_t>(
             env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat));
-    captureArgs.sourceCrop =
+    const auto sourceCrop =
             JNICommon::rectFromObj(env,
                                    env->GetObjectField(captureArgsObject,
                                                        gCaptureArgsClassInfo.sourceCrop));
+    captureArgs.sourceCrop = gui::aidl_utils::toARect(sourceCrop);
     captureArgs.frameScaleX =
             env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
     captureArgs.frameScaleY =
@@ -172,7 +174,7 @@
                 jniThrowNullPointerException(env, "Exclude layer is null");
                 return;
             }
-            captureArgs.excludeHandles.emplace(excludeObject->getHandle());
+            captureArgs.excludeHandles.emplace_back(excludeObject->getHandle());
         }
     }
     captureArgs.hintForSeamlessTransition =
@@ -182,18 +184,18 @@
 
 static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
                                                        jobject displayCaptureArgsObject) {
-    DisplayCaptureArgs captureArgs;
-    getCaptureArgs(env, displayCaptureArgsObject, captureArgs);
+    DisplayCaptureArgs displayCaptureArgs;
+    getCaptureArgs(env, displayCaptureArgsObject, displayCaptureArgs.captureArgs);
 
-    captureArgs.displayToken =
+    displayCaptureArgs.displayToken =
             ibinderForJavaObject(env,
                                  env->GetObjectField(displayCaptureArgsObject,
                                                      gDisplayCaptureArgsClassInfo.displayToken));
-    captureArgs.width =
+    displayCaptureArgs.width =
             env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
-    captureArgs.height =
+    displayCaptureArgs.height =
             env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
-    return captureArgs;
+    return displayCaptureArgs;
 }
 
 static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
@@ -212,8 +214,8 @@
 
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
                                 jlong screenCaptureListenerObject, jboolean sync) {
-    LayerCaptureArgs captureArgs;
-    getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
+    LayerCaptureArgs layerCaptureArgs;
+    getCaptureArgs(env, layerCaptureArgsObject, layerCaptureArgs.captureArgs);
 
     SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
             env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer));
@@ -221,13 +223,13 @@
         return BAD_VALUE;
     }
 
-    captureArgs.layerHandle = layer->getHandle();
-    captureArgs.childrenOnly =
+    layerCaptureArgs.layerHandle = layer->getHandle();
+    layerCaptureArgs.childrenOnly =
             env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly);
 
     sp<gui::IScreenCaptureListener> captureListener =
             reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
-    return ScreenshotClient::captureLayers(captureArgs, captureListener, sync);
+    return ScreenshotClient::captureLayers(layerCaptureArgs, captureListener, sync);
 }
 
 static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 383033d..118acac 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -282,6 +282,11 @@
     <string name="config_satellite_emergency_handover_intent_action" translatable="false"></string>
     <java-symbol type="string" name="config_satellite_emergency_handover_intent_action" />
 
+    <!-- The action of the intent that hidden menu sends to the app to launch demo mode for sos
+     emergency messaging via satellite. -->
+    <string name="config_satellite_demo_mode_sos_intent_action" translatable="false"></string>
+    <java-symbol type="string" name="config_satellite_demo_mode_sos_intent_action" />
+
     <!-- Whether outgoing satellite datagrams should be sent to modem in demo mode. When satellite
          is enabled for demo mode, if this config is enabled, outgoing datagrams will be sent to
          modem; otherwise, success results will be returned. If demo mode is disabled, outgoing
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index a115c65..38ea4ac 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -93,5 +93,6 @@
         <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
         <permission name="android.permission.CONTROL_UI_TRACING" />
         <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
+        <permission name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1fe6ca7..9a55b80 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -592,6 +592,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS" />
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.providers.tv">
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 53024ab..ae50da1 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -304,7 +304,7 @@
         <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
             NotoSansBengali-VF.ttf
         </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular" supportedAxes="wght">
             NotoSerifBengali-VF.ttf
         </font>
     </family>
@@ -354,7 +354,7 @@
         <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
             NotoSansSinhala-VF.ttf
         </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular" supportedAxes="wght">
             NotoSerifSinhala-VF.ttf
         </font>
     </family>
@@ -927,23 +927,23 @@
             NotoSansMedefaidrin-VF.ttf
         </font>
     </family>
-    <family lang="und-Soyo" supportedAxes="wght">
-        <font postScriptName="NotoSansSoyombo-Regular">
+    <family lang="und-Soyo">
+        <font postScriptName="NotoSansSoyombo-Regular" supportedAxes="wght">
             NotoSansSoyombo-VF.ttf
         </font>
     </family>
-    <family lang="und-Takr" supportedAxes="wght">
-        <font postScriptName="NotoSansTakri-Regular">
+    <family lang="und-Takr">
+        <font postScriptName="NotoSansTakri-Regular" supportedAxes="wght">
             NotoSansTakri-VF.ttf
         </font>
     </family>
-    <family lang="und-Hmnp" supportedAxes="wght">
-        <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+    <family lang="und-Hmnp">
+        <font postScriptName="NotoSerifHmongNyiakeng-Regular" supportedAxes="wght">
             NotoSerifNyiakengPuachueHmong-VF.ttf
         </font>
     </family>
-    <family lang="und-Yezi" supportedAxes="wght">
-        <font postScriptName="NotoSerifYezidi-Regular">
+    <family lang="und-Yezi">
+        <font postScriptName="NotoSerifYezidi-Regular" supportedAxes="wght">
             NotoSerifYezidi-VF.ttf
         </font>
     </family>
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index a4ee825..407d704 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -304,7 +304,7 @@
         <font postScriptName="NotoSansBengali-Regular" supportedAxes="wght">
             NotoSansBengali-VF.ttf
         </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular" supportedAxes="wght">
             NotoSerifBengali-VF.ttf
         </font>
     </family>
@@ -354,7 +354,7 @@
         <font postScriptName="NotoSansSinhala-Regular" supportedAxes="wght">
             NotoSansSinhala-VF.ttf
         </font>
-        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular" supportedAxes="wght">
             NotoSerifSinhala-VF.ttf
         </font>
     </family>
@@ -943,23 +943,23 @@
             NotoSansMedefaidrin-VF.ttf
         </font>
     </family>
-    <family lang="und-Soyo" supportedAxes="wght">
-        <font postScriptName="NotoSansSoyombo-Regular">
+    <family lang="und-Soyo">
+        <font postScriptName="NotoSansSoyombo-Regular" supportedAxes="wght">
             NotoSansSoyombo-VF.ttf
         </font>
     </family>
-    <family lang="und-Takr" supportedAxes="wght">
-        <font postScriptName="NotoSansTakri-Regular">
+    <family lang="und-Takr">
+        <font postScriptName="NotoSansTakri-Regular" supportedAxes="wght">
             NotoSansTakri-VF.ttf
         </font>
     </family>
-    <family lang="und-Hmnp" supportedAxes="wght">
-        <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+    <family lang="und-Hmnp">
+        <font postScriptName="NotoSerifHmongNyiakeng-Regular" supportedAxes="wght">
             NotoSerifNyiakengPuachueHmong-VF.ttf
         </font>
     </family>
-    <family lang="und-Yezi" supportedAxes="wght">
-        <font postScriptName="NotoSerifYezidi-Regular">
+    <family lang="und-Yezi">
+        <font postScriptName="NotoSerifYezidi-Regular" supportedAxes="wght">
             NotoSerifYezidi-VF.ttf
         </font>
     </family>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 53ab2d5..08a746f 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -92,8 +92,11 @@
     <dimen name="docked_divider_handle_width">16dp</dimen>
     <dimen name="docked_divider_handle_height">2dp</dimen>
 
-    <dimen name="split_divider_handle_region_width">96dp</dimen>
+    <dimen name="split_divider_handle_region_width">80dp</dimen>
     <dimen name="split_divider_handle_region_height">48dp</dimen>
+    <!-- The divider touch zone height is intentionally halved in portrait to avoid colliding
+         with the app handle.-->
+    <dimen name="desktop_mode_portrait_split_divider_handle_region_height">24dp</dimen>
 
     <!-- Divider handle size for split screen -->
     <dimen name="split_divider_handle_width">40dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dabfeeb..3dc33c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2217,7 +2217,6 @@
         // And since all children are removed, remove the summary.
         removeCallback.accept(-1);
 
-        // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
         mBubbleData.addSummaryToSuppress(summary.getStatusBarNotification().getGroupKey(),
                 summary.getKey());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index dcbc72a..7c51a69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.util.ArraySet;
+import android.util.Size;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -193,8 +194,8 @@
 
 
     /** Called when a display rotate requested. */
-    public void onDisplayRotateRequested(WindowContainerTransaction wct, int displayId,
-            int fromRotation, int toRotation) {
+    public void onDisplayChangeRequested(WindowContainerTransaction wct, int displayId,
+            Rect startAbsBounds, Rect endAbsBounds, int fromRotation, int toRotation) {
         synchronized (mDisplays) {
             final DisplayRecord dr = mDisplays.get(displayId);
             if (dr == null) {
@@ -203,6 +204,13 @@
             }
 
             if (dr.mDisplayLayout != null) {
+                if (endAbsBounds != null) {
+                    // If there is a change in the display dimensions update the layout as well;
+                    // note that endAbsBounds should ignore any potential rotation changes, so
+                    // we still need to rotate the layout after if needed.
+                    dr.mDisplayLayout.resizeTo(dr.mContext.getResources(),
+                            new Size(endAbsBounds.width(), endAbsBounds.height()));
+                }
                 dr.mDisplayLayout.rotateTo(dr.mContext.getResources(), toRotation);
             }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 84e32a2..b6a1686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -35,6 +35,7 @@
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
+import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -244,6 +245,16 @@
         recalcInsets(res);
     }
 
+    /**
+     * Update the dimensions of this layout.
+     */
+    public void resizeTo(Resources res, Size displaySize) {
+        mWidth = displaySize.getWidth();
+        mHeight = displaySize.getHeight();
+
+        recalcInsets(res);
+    }
+
     /** Get this layout's non-decor insets. */
     public Rect nonDecorInsets() {
         return mNonDecorInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 442036ff..e7848e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -58,6 +58,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 /**
  * Divider for multi window splits.
@@ -228,7 +229,9 @@
                 : R.dimen.split_divider_handle_region_width);
         mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
                 ? R.dimen.split_divider_handle_region_width
-                : R.dimen.split_divider_handle_region_height);
+                : DesktopModeStatus.canEnterDesktopMode(mContext)
+                        ? R.dimen.desktop_mode_portrait_split_divider_handle_region_height
+                        : R.dimen.split_divider_handle_region_height);
     }
 
     void onInsetsChanged(InsetsState insetsState, boolean animate) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b3beb4a..2de545a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -695,6 +695,16 @@
                 return;
             }
 
+            if (mSplitScreenOptional.isPresent()) {
+                // If pip activity will reparent to origin task case and if the origin task still
+                // under split root, apply exit split transaction to make it expand to fullscreen.
+                SplitScreenController split = mSplitScreenOptional.get();
+                if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+                    split.prepareExitSplitScreen(wct, split.getStageOfTask(
+                            mTaskInfo.lastParentTaskIdBeforePip),
+                            SplitScreenController.EXIT_REASON_APP_FINISHED);
+                }
+            }
             mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
             return;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index eb6caba..e9c4c14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -29,7 +29,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
@@ -200,17 +199,8 @@
         DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
         mPipDisplayLayoutState.setDisplayLayout(layout);
 
-        mDisplayController.addDisplayWindowListener(this);
         mDisplayController.addDisplayChangingController(this);
         mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
-                new DisplayInsetsController.OnInsetsChangedListener() {
-                    @Override
-                    public void insetsChanged(InsetsState insetsState) {
-                        setDisplayLayout(mDisplayController
-                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
-                    }
-                });
-        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
                 new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
                     @Override
                     public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -285,34 +275,37 @@
         setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
     }
 
-    @Override
-    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
-        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
-            return;
-        }
-        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
-    }
-
     /**
      * A callback for any observed transition that contains a display change in its
-     * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta.
+     * {@link android.window.TransitionRequestInfo},
      */
     @Override
     public void onDisplayChange(int displayId, int fromRotation, int toRotation,
             @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+        final float boundsScale = mPipBoundsState.getBoundsScale();
+
+        // Update the display layout caches even if we are not in PiP.
+        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
+
         if (!mPipTransitionState.isInPip()) {
             return;
         }
 
-        // Calculate the snap fraction pre-rotation.
-        float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+        mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
 
-        // Update the caches to reflect the new display layout and movement bounds.
-        mPipDisplayLayoutState.rotateTo(toRotation);
+        // Update the caches to reflect the new display layout in the movement bounds;
+        // temporarily update bounds to be at the top left for the movement bounds calculation.
+        Rect toBounds = new Rect(0, 0,
+                (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale),
+                (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale));
+        mPipBoundsState.setBounds(toBounds);
         mPipTouchHandler.updateMovementBounds();
 
-        // The policy is to keep PiP width, height and snap fraction invariant.
-        Rect toBounds = mPipBoundsState.getBounds();
+        // The policy is to keep PiP snap fraction invariant.
         mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
         mPipBoundsState.setBounds(toBounds);
         t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index b7b42c7..209fc39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -114,7 +114,6 @@
                 t.clear();
                 mMainExecutor.execute(() -> {
                     finishCallback.onTransitionFinished(wct);
-                    mRemote = null;
                 });
             }
         };
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 68217c0..f0d3668 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
@@ -1187,12 +1187,15 @@
             }
             if (request.getDisplayChange() != null) {
                 TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
-                if (change.getEndRotation() != change.getStartRotation()) {
-                    // Is a rotation, so dispatch to all displayChange listeners
+                if (change.getStartRotation() != change.getEndRotation()
+                        || (change.getStartAbsBounds() != null
+                        && !change.getStartAbsBounds().equals(change.getEndAbsBounds()))) {
+                    // Is a display change, so dispatch to all displayChange listeners
                     if (wct == null) {
                         wct = new WindowContainerTransaction();
                     }
-                    mDisplayController.onDisplayRotateRequested(wct, change.getDisplayId(),
+                    mDisplayController.onDisplayChangeRequested(wct, change.getDisplayId(),
+                            change.getStartAbsBounds(), change.getEndAbsBounds(),
                             change.getStartRotation(), change.getEndRotation());
                 }
             }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
new file mode 100644
index 0000000..6d52a11
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.platform.test.annotations.Postsubmit
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+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 DragAppWindowMultiWindowAndPip : DragAppWindowScenarioTestBase()
+{
+    private val imeAppHelper = ImeAppHelper(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val pipApp = PipAppHelper(instrumentation)
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+    private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+    private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        // Set string extra to ensure the app is on PiP mode at launch
+        pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
+        testApp.enterDesktopWithDrag(wmHelper, device)
+        mailApp.launchViaIntent(wmHelper)
+        newTasksApp.launchViaIntent(wmHelper)
+        imeApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    override fun dragAppWindow() {
+        val (startXIme, startYIme) = getWindowDragStartCoordinate(imeAppHelper)
+
+        imeApp.dragWindow(startXIme, startYIme,
+            endX = startXIme + 150, endY = startYIme + 150,
+            wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        pipApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
index 5bc975b..b6bca7a 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
@@ -83,5 +83,8 @@
     @After
     fun teardown() {
         testApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
new file mode 100644
index 0000000..285ea13
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+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.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppCornerMultiWindowAndPip
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+    val horizontalChange: Int = 50,
+    val verticalChange: Int = -50) {
+
+    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 pipApp = PipAppHelper(instrumentation)
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+    private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+    private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        // Set string extra to ensure the app is on PiP mode at launch
+        pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
+        testApp.enterDesktopWithDrag(wmHelper, device)
+        mailApp.launchViaIntent(wmHelper)
+        newTasksApp.launchViaIntent(wmHelper)
+        imeApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun resizeAppWithCornerResize() {
+        imeApp.cornerResize(wmHelper,
+            device,
+            DesktopModeAppHelper.Corners.RIGHT_TOP,
+            horizontalChange,
+            verticalChange)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        pipApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c36eda9..ca468fc 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1027,7 +1027,7 @@
      * <p>This method has no effect if the device implements a fixed volume policy
      * as indicated by {@link #isVolumeFixed()}.
      * <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
-     * unless the app has been granted Do Not Disturb Access.
+     * unless the app has been granted Notification Policy Access.
      * See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
      *
      * @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},
@@ -1379,7 +1379,7 @@
      * <p>This method has no effect if the device implements a fixed volume policy
      * as indicated by {@link #isVolumeFixed()}.
      * * <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
-     * unless the app has been granted Do Not Disturb Access.
+     * unless the app has been granted Notification Policy Access.
      * See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
      * @param ringerMode The ringer mode, one of {@link #RINGER_MODE_NORMAL},
      *            {@link #RINGER_MODE_SILENT}, or {@link #RINGER_MODE_VIBRATE}.
@@ -1403,7 +1403,7 @@
      * <p>This method has no effect if the device implements a fixed volume policy
      * as indicated by {@link #isVolumeFixed()}.
      * <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless
-     * the app has been granted Do Not Disturb Access.
+     * the app has been granted Notification Policy Access.
      * See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
      * @param streamType The stream whose volume index should be set.
      * @param index The volume index to set. See
@@ -8829,7 +8829,7 @@
      * <p>This method has no effect if the device implements a fixed volume policy
      * as indicated by {@link #isVolumeFixed()}.
      * <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
-     * unless the app has been granted Do Not Disturb Access.
+     * unless the app has been granted Notification Policy Access.
      * See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
      * <p>This API checks if the caller has the necessary permissions based on the provided
      * component name, uid, and pid values.
@@ -8870,7 +8870,7 @@
      * <p>This method has no effect if the device implements a fixed volume policy
      * as indicated by {@link #isVolumeFixed()}.
      * <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless
-     * the app has been granted Do Not Disturb Access.
+     * the app has been granted Notification Policy Access.
      * See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
      * <p>This API checks if the caller has the necessary permissions based on the provided
      * component name, uid, and pid values.
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index be93abb..87cb3e7 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -70,6 +70,7 @@
     void releaseSession(in IBinder sessionToken, int userId);
     int getClientPid(in String sessionId);
     int getClientPriority(int useCase, in String sessionId);
+    int getClientUserId(in String sessionId);
 
     void setMainSession(in IBinder sessionToken, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index aed3e60e..25b6bfa 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -760,6 +760,14 @@
      * @hide
      */
     public static final int UNKNOWN_CLIENT_PID = -1;
+    /**
+     * An unknown state of the client userId gets from the TvInputManager. Client gets this value
+     * when query through {@link #getClientUserId(String sessionId)} fails.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+    public static final int UNKNOWN_CLIENT_USER_ID = -1;
 
     /**
      * Broadcast intent action when the user blocked content ratings change. For use with the
@@ -2510,6 +2518,21 @@
     };
 
     /**
+     * Get a the client user id when creating the session with the session id provided.
+     *
+     * @param sessionId a String of session id that is used to query the client user id.
+     * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_USER_ID}
+     *         if the call fails.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+    public int getClientUserId(@NonNull String sessionId) {
+        return getClientUserIdInternal(sessionId);
+    }
+
+    /**
      * Returns a priority for the given use case type and the client's foreground or background
      * status.
      *
@@ -2599,6 +2622,18 @@
         return clientPid;
     }
 
+    @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+    private int getClientUserIdInternal(String sessionId) {
+        Preconditions.checkNotNull(sessionId);
+        int clientUserId = UNKNOWN_CLIENT_USER_ID;
+        try {
+            clientUserId = mService.getClientUserId(sessionId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return clientUserId;
+    }
+
     private int getClientPriorityInternal(int useCase, String sessionId) {
         try {
             return mService.getClientPriority(useCase, sessionId);
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 3196ba1..d6e9e4e 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -31,4 +31,12 @@
     namespace: "media_tv"
     description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
     bug: "332201346"
+}
+
+flag {
+    name: "kids_mode_tvdb_sharing"
+    is_exported: true
+    namespace: "media_tv"
+    description: "Performance and Storage Optimization in Google TV Kids Mode."
+    bug: "288383796"
 }
\ No newline at end of file
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index da6efee..717ec02 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -1,4 +1,39 @@
 {
+  "presubmit": [
+    {
+      "name": "CtsPackageInstallerCUJInstallationTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUninstallationTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    }
+  ],
   "postsubmit": [
     {
       "name": "CtsPackageInstallTestCases",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 398c915..2e98c1f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -376,6 +376,10 @@
     <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
     <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
 
+    <!-- To be able to intercept meta key events, might need to be removed once b/358569822
+         is ready -->
+    <uses-permission android:name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" />
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 047c097..476fd8b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -541,6 +541,16 @@
 }
 
 flag {
+    name: "clipboard_image_timeout"
+    namespace: "systemui"
+    description: "Wait for clipboard image to load before showing UI"
+    bug: "359864629"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "screenshot_action_dismiss_system_windows"
     namespace: "systemui"
     description: "Dismiss existing system windows when starting action from screenshot UI"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index d15bda0..853dc6f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -60,6 +60,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -631,7 +632,11 @@
                     modifier =
                         Modifier.weight(1f)
                             .fillMaxHeight()
-                            .padding(end = screenCornerRadius / 2f, bottom = navBarBottomHeight)
+                            .padding(
+                                end =
+                                    dimensionResource(R.dimen.notification_panel_margin_horizontal),
+                                bottom = navBarBottomHeight
+                            )
                             .then(brightnessMirrorShowingModifier)
                 )
             }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index 08ee602..a3f40d4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -18,10 +18,17 @@
 package com.android.systemui.shared.quickaffordance.shared.model
 
 object KeyguardPreviewConstants {
+    const val MESSAGE_ID_DEFAULT_PREVIEW = 707
     const val MESSAGE_ID_HIDE_SMART_SPACE = 1111
-    const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
+    const val MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED = 1988
     const val MESSAGE_ID_SLOT_SELECTED = 1337
-    const val KEY_SLOT_ID = "slot_id"
-    const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+    const val MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES = 214
+
+    const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
     const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
+    const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+    const val KEY_QUICK_AFFORDANCE_ID = "quick_affordance_id"
+    const val KEY_SLOT_ID = "slot_id"
+
+    const val KEYGUARD_QUICK_AFFORDANCE_ID_NONE = "none"
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 8e109b4..c85cd66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -308,6 +308,15 @@
             )
         }
 
+    @Test
+    fun getConfig() =
+        testScope.runTest {
+            assertThat(underTest.getConfig(FakeCustomizationProviderClient.AFFORDANCE_1))
+                .isEqualTo(config1)
+            assertThat(underTest.getConfig(FakeCustomizationProviderClient.AFFORDANCE_2))
+                .isEqualTo(config2)
+        }
+
     private fun assertSelections(
         observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
         expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 75c0d3b..ad07c1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -426,6 +426,64 @@
         }
 
     @Test
+    fun quickAffordanceAlwaysVisible_withNonNullOverrideKeyguardQuickAffordanceId() =
+        testScope.runTest {
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+
+            // The default case
+            val collectedValue =
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        KeyguardQuickAffordancePosition.BOTTOM_START,
+                    )
+                )
+            assertThat(collectedValue())
+                .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+            val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+            assertThat(visibleModel.configKey)
+                .isEqualTo(
+                    "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${homeControls.key}"
+                )
+            assertThat(visibleModel.icon).isEqualTo(ICON)
+            assertThat(visibleModel.icon.contentDescription)
+                .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+            assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
+
+            // With override
+            val collectedValueWithOverride =
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                        overrideQuickAffordanceId =
+                            BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+                    )
+                )
+            assertThat(collectedValueWithOverride())
+                .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+            val visibleModelWithOverride =
+                collectedValueWithOverride() as KeyguardQuickAffordanceModel.Visible
+            assertThat(visibleModelWithOverride.configKey)
+                .isEqualTo(
+                    "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${quickAccessWallet.key}"
+                )
+            assertThat(visibleModelWithOverride.icon).isEqualTo(ICON)
+            assertThat(visibleModelWithOverride.icon.contentDescription)
+                .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+            assertThat(visibleModelWithOverride.activationState).isEqualTo(ActivationState.Active)
+        }
+
+    @Test
     fun select() =
         testScope.runTest {
             overrideResource(
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 9792c28..1bc2e24 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
@@ -60,6 +60,7 @@
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -2135,6 +2136,14 @@
     @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccluded_communalKtfRefactor() =
         testScope.runTest {
+            // GIVEN device is not dreaming
+            powerInteractor.setAwakeForTest()
+            keyguardRepository.setDreaming(false)
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            advanceTimeBy(600.milliseconds)
+
             // GIVEN a prior transition has run to GLANCEABLE_HUB
             communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
@@ -2298,6 +2307,54 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    @BrokenWithSceneContainer(339465026)
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
+        testScope.runTest {
+            // GIVEN that we are dreaming and not dozing
+            powerInteractor.setAwakeForTest()
+            keyguardRepository.setDreaming(true)
+            keyguardRepository.setKeyguardOccluded(true)
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            advanceTimeBy(700.milliseconds)
+            clearInvocations(transitionRepository)
+
+            // GIVEN a prior transition has run to GLANCEABLE_HUB
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+            runCurrent()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                )
+            clearInvocations(transitionRepository)
+
+            // WHEN dream ends but we are still occluded
+            keyguardRepository.setDreaming(false)
+            runCurrent()
+            assertThat(transitionRepository).noTransitionsStarted()
+
+            // Simulate occlusion signal changing due to dream terminating and then occluding again
+            // due to a new activity starting a couple milliseconds later.
+            keyguardRepository.setKeyguardOccluded(false)
+            advanceTimeBy(10.milliseconds)
+            keyguardRepository.setKeyguardOccluded(true)
+            advanceTimeBy(200.milliseconds)
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.OCCLUDED,
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
     private suspend fun TestScope.runTransitionAndSetWakefulness(
         from: KeyguardState,
         to: KeyguardState
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
new file mode 100644
index 0000000..f6f58c9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/BaseActivatableTest.kt
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.lifecycle
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BaseActivatableTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val underTest = FakeActivatable()
+
+    @Test
+    fun activate() =
+        testScope.runTest {
+            assertThat(underTest.isActive).isFalse()
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+        }
+
+    @Test
+    fun activate_andCancel() =
+        testScope.runTest {
+            assertThat(underTest.isActive).isFalse()
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            val job = Job()
+            underTest.activateIn(testScope, context = job)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(1)
+        }
+
+    @Test
+    fun activate_afterCancellation() =
+        testScope.runTest {
+            assertThat(underTest.isActive).isFalse()
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            val job = Job()
+            underTest.activateIn(testScope, context = job)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(1)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(underTest.activationCount).isEqualTo(2)
+            assertThat(underTest.cancellationCount).isEqualTo(1)
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun activate_whileActive_throws() =
+        testScope.runTest {
+            assertThat(underTest.isActive).isFalse()
+            assertThat(underTest.activationCount).isEqualTo(0)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(underTest.activationCount).isEqualTo(1)
+            assertThat(underTest.cancellationCount).isEqualTo(0)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+        }
+
+    @Test
+    fun addChild_beforeActive_activatesChildrenOnceActivated() =
+        testScope.runTest {
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            assertThat(underTest.isActive).isFalse()
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.activateIn(this)
+            runCurrent()
+
+            assertThat(underTest.isActive).isTrue()
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+        }
+
+    @Test
+    fun addChild_whileActive_activatesChildrenImmediately() =
+        testScope.runTest {
+            underTest.activateIn(this)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            runCurrent()
+
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+        }
+
+    @Test
+    fun addChild_afterCancellation_doesNotActivateChildren() =
+        testScope.runTest {
+            val job = Job()
+            underTest.activateIn(this, context = job)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            runCurrent()
+
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+        }
+
+    @Test
+    fun activate_cancellation_cancelsCurrentChildren() =
+        testScope.runTest {
+            val job = Job()
+            underTest.activateIn(this, context = job)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            runCurrent()
+
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+        }
+
+    @Test
+    fun activate_afterCancellation_reactivatesCurrentChildren() =
+        testScope.runTest {
+            val job = Job()
+            underTest.activateIn(this, context = job)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            runCurrent()
+
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.activateIn(this)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+        }
+
+    @Test
+    fun removeChild_beforeActive_neverActivatesChild() =
+        testScope.runTest {
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            assertThat(underTest.isActive).isFalse()
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+        }
+
+    @Test
+    fun removeChild_whileActive_cancelsChild() =
+        testScope.runTest {
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            assertThat(underTest.isActive).isFalse()
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.activateIn(this)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+
+            underTest.removeChild(child1)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isTrue()
+        }
+
+    @Test
+    fun removeChild_afterCancellation_doesNotReactivateChildren() =
+        testScope.runTest {
+            val child1 = FakeActivatable()
+            val child2 = FakeActivatable()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            assertThat(underTest.isActive).isFalse()
+            underTest.addChild(child1)
+            underTest.addChild(child2)
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            val job = Job()
+            underTest.activateIn(this, context = job)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(child1.isActive).isTrue()
+            assertThat(child2.isActive).isTrue()
+
+            job.cancel()
+            runCurrent()
+            assertThat(underTest.isActive).isFalse()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isFalse()
+
+            underTest.removeChild(child1)
+            underTest.activateIn(this)
+            runCurrent()
+            assertThat(underTest.isActive).isTrue()
+            assertThat(child1.isActive).isFalse()
+            assertThat(child2.isActive).isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt
deleted file mode 100644
index 9484821..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.lifecycle
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SafeActivatableTest : SysuiTestCase() {
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private val underTest = FakeActivatable()
-
-    @Test
-    fun activate() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-        }
-
-    @Test
-    fun activate_andCancel() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            val job = Job()
-            underTest.activateIn(testScope, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(1)
-        }
-
-    @Test
-    fun activate_afterCancellation() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            val job = Job()
-            underTest.activateIn(testScope, context = job)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            job.cancel()
-            runCurrent()
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(1)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(2)
-            assertThat(underTest.cancellationCount).isEqualTo(1)
-        }
-
-    @Test(expected = IllegalStateException::class)
-    fun activate_whileActive_throws() =
-        testScope.runTest {
-            assertThat(underTest.isActive).isFalse()
-            assertThat(underTest.activationCount).isEqualTo(0)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-            assertThat(underTest.isActive).isTrue()
-            assertThat(underTest.activationCount).isEqualTo(1)
-            assertThat(underTest.cancellationCount).isEqualTo(0)
-
-            underTest.activateIn(testScope)
-            runCurrent()
-        }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 4d1dd1c..d9faa30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
@@ -45,6 +46,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@FlakyTest(bugId = 360351805)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DragAndDropTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index dfc004a..c9869bdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -280,9 +280,12 @@
         }
 
     @Test
-    fun isActiveFollowsPackageManagerAdapter() =
+    fun isActiveFollowsPackageManagerAdapter_user0() =
         with(kosmos) {
             testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(0)
+
+                underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
                 packageManagerAdapterFacade.setIsActive(false)
                 assertThat(underTest.isTileActive()).isFalse()
 
@@ -295,6 +298,7 @@
     fun isToggleableFollowsPackageManagerAdapter() =
         with(kosmos) {
             testScope.runTest {
+                underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
                 packageManagerAdapterFacade.setIsToggleable(false)
                 assertThat(underTest.isTileToggleable()).isFalse()
 
@@ -303,6 +307,66 @@
             }
         }
 
+    @Test
+    fun isActiveFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(10)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsActive(false)
+                assertThat(underTest.isTileActive()).isFalse()
+
+                packageManagerAdapterFacade.setIsActive(true)
+                assertThat(underTest.isTileActive()).isTrue()
+            }
+        }
+
+    @Test
+    fun isToggleableFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(10)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsToggleable(false)
+                assertThat(underTest.isTileToggleable()).isFalse()
+
+                packageManagerAdapterFacade.setIsToggleable(true)
+                assertThat(underTest.isTileToggleable()).isTrue()
+            }
+        }
+
+    @Test
+    fun isActiveDoesntFollowPackageManagerAdapter_user10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(0)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsActive(false)
+                assertThat(underTest.isTileActive()).isFalse()
+
+                packageManagerAdapterFacade.setIsActive(true)
+                assertThat(underTest.isTileActive()).isFalse()
+            }
+        }
+
+    @Test
+    fun isToggleableDoesntFollowPackageManagerAdapter_user10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(0)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsToggleable(false)
+                assertThat(underTest.isTileToggleable()).isFalse()
+
+                packageManagerAdapterFacade.setIsToggleable(true)
+                assertThat(underTest.isTileToggleable()).isFalse()
+            }
+        }
+
     private companion object {
 
         val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
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 bbb467f..64a13de 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
@@ -28,9 +28,7 @@
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.internal.policy.IKeyguardDismissCallback
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
@@ -357,6 +355,7 @@
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                 SuccessFingerprintAuthenticationStatus(0, true)
             )
+            runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
             assertThat(alternateBouncerVisible).isFalse()
@@ -507,6 +506,33 @@
         }
 
     @Test
+    fun hideAlternateBouncerAndNotifyDismissCancelledWhenDeviceSleeps() =
+        testScope.runTest {
+            val alternateBouncerVisible by
+                collectLastValue(bouncerRepository.alternateBouncerVisible)
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            prepareState(
+                isDeviceUnlocked = false,
+                initialSceneKey = Scenes.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
+            bouncerRepository.setAlternateVisible(true)
+            underTest.start()
+
+            // run all pending dismiss succeeded/cancelled calls from setup:
+            kosmos.fakeExecutor.runAllReady()
+
+            val dismissCallback: IKeyguardDismissCallback = mock()
+            kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
+            powerInteractor.setAsleepForTest()
+            runCurrent()
+            kosmos.fakeExecutor.runAllReady()
+
+            assertThat(alternateBouncerVisible).isFalse()
+            verify(dismissCallback).onDismissCancelled()
+        }
+
+    @Test
     fun switchToLockscreenWhenDeviceSleepsLocked() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -1644,19 +1670,27 @@
         }
 
     @Test
-    fun notifyKeyguardDismissCallbacks_whenUnlocking_onDismissSucceeded() =
+    fun notifyKeyguardDismissCallbacks_whenUnlockingFromBouncer_onDismissSucceeded() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            prepareState()
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            prepareState(
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+                initialSceneKey = Scenes.Bouncer,
+            )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
             underTest.start()
+
+            // run all pending dismiss succeeded/cancelled calls from setup:
+            kosmos.fakeExecutor.runAllReady()
+
             val dismissCallback: IKeyguardDismissCallback = mock()
             kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
 
-            // Switch to bouncer and unlock device:
-            sceneInteractor.changeScene(Scenes.Bouncer, "")
-            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
-            kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
-            assertThat(currentScene).isEqualTo(Scenes.Gone)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
             verify(dismissCallback).onDismissSucceeded()
@@ -1665,19 +1699,26 @@
     @Test
     fun notifyKeyguardDismissCallbacks_whenLeavingBouncer_onDismissCancelled() =
         testScope.runTest {
+            val isUnlocked by collectLastValue(kosmos.deviceEntryInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             prepareState()
             underTest.start()
+
+            // run all pending dismiss succeeded/cancelled calls from setup:
+            kosmos.fakeExecutor.runAllReady()
+
             val dismissCallback: IKeyguardDismissCallback = mock()
             kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
 
             // Switch to bouncer:
             sceneInteractor.changeScene(Scenes.Bouncer, "")
             assertThat(currentScene).isEqualTo(Scenes.Bouncer)
+            runCurrent()
 
-            // Return to lockscreen:
+            // Return to lockscreen when isUnlocked=false:
             sceneInteractor.changeScene(Scenes.Lockscreen, "")
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(isUnlocked).isFalse()
             runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 04c6fa9..040af90 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.systemui.Flags.clipboardImageTimeout;
 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;
@@ -32,7 +33,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_IMAGE_TIMEOUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
@@ -288,7 +288,7 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
-        if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+        if (clipboardImageTimeout()) {
             if (shouldAnimate) {
                 reset();
                 mClipboardLogger.setClipSource(mClipboardModel.getSource());
@@ -452,7 +452,7 @@
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
                     mIsMinimized = false;
                 }
-                if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+                if (clipboardImageTimeout()) {
                     setExpandedView(() -> animateIn());
                 } else {
                     setExpandedView();
@@ -522,7 +522,7 @@
                 mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
             @Override
             public void onInputEvent(InputEvent event) {
-                if ((!mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT) || mShowingUi)
+                if ((!clipboardImageTimeout() || mShowingUi)
                         && event instanceof MotionEvent) {
                     MotionEvent motionEvent = (MotionEvent) event;
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt b/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
new file mode 100644
index 0000000..7e09a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import com.android.systemui.Flags
+
+/** Helper for reading or using the new picker UI flag. */
+@Suppress("NOTHING_TO_INLINE")
+object NewPickerUiKeyguardPreview {
+
+    /** Is the new picker UI enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.newPickerUi()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 698328e..d49550e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -22,7 +22,6 @@
 import android.os.UserHandle
 import android.util.LayoutDirection
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +34,7 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -61,10 +61,13 @@
     private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
     private val userTracker: UserTracker,
     legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
-    private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+    configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
     dumpManager: DumpManager,
     userHandle: UserHandle,
 ) {
+    // Configs for all keyguard quick affordances, mapped by the quick affordance ID as key
+    private val configsByAffordanceId: Map<String, KeyguardQuickAffordanceConfig> =
+        configs.associateBy { it.key }
     private val userId: Flow<Int> =
         ConflatedCallbackFlow.conflatedCallbackFlow {
             val callback =
@@ -126,7 +129,7 @@
      */
     fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
         val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
-        return configs.filter { selections.contains(it.key) }
+        return configsByAffordanceId.values.filter { selections.contains(it.key) }
     }
 
     /**
@@ -159,7 +162,7 @@
      */
     suspend fun getAffordancePickerRepresentations():
         List<KeyguardQuickAffordancePickerRepresentation> {
-        return configs
+        return configsByAffordanceId.values
             .associateWith { config -> config.getPickerScreenState() }
             .filterNot { (_, pickerState) ->
                 pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
@@ -226,6 +229,11 @@
         }
     }
 
+    /** Get the config of a quick affordance. */
+    fun getConfig(quickAffordanceId: String): KeyguardQuickAffordanceConfig? {
+        return configsByAffordanceId[quickAffordanceId]
+    }
+
     private inner class Dumpster : Dumpable {
         override fun dump(pw: PrintWriter, args: Array<out String>) {
             val slotPickerRepresentations = getSlotPickerRepresentations()
@@ -246,7 +254,7 @@
                 pw.println("    $slotId$selectionText (capacity = $capacity)")
             }
             pw.println("Available affordances on device:")
-            configs.forEach { config ->
+            configsByAffordanceId.values.forEach { config ->
                 pw.println("    ${config.key} (\"${config.pickerName()}\")")
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 0aa50e0..199caa1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -37,16 +37,21 @@
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
+@OptIn(FlowPreview::class)
 @SysUISingleton
 class FromGlanceableHubTransitionInteractor
 @Inject
@@ -196,19 +201,26 @@
             }
         } else if (communalSceneKtfRefactor()) {
             scope.launch {
-                allOf(
+                combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        noneOf(
-                            // Dream is a special-case of occluded, so filter out the dreaming
-                            // case here.
-                            keyguardInteractor.isDreaming,
-                            // When launching activities from widgets on the hub, we have a
-                            // custom occlusion animation.
-                            communalSceneInteractor.isLaunchingWidget,
-                        ),
+                        keyguardInteractor.isAbleToDream
+                            // Debounce the dreaming signal since there is a race condition between
+                            // the occluded and dreaming signals. We therefore add a small delay
+                            // to give enough time for occluded to flip to false when the dream
+                            // ends, to avoid transitioning to OCCLUDED erroneously when exiting
+                            // the dream.
+                            .debounce(100.milliseconds),
+                        ::Pair
                     )
-                    .filterRelevantKeyguardStateAnd { isOccludedAndNotDreamingNorLaunchingWidget ->
-                        isOccludedAndNotDreamingNorLaunchingWidget
+                    .sampleFilter(
+                        // When launching activities from widgets on the hub, we have a
+                        // custom occlusion animation.
+                        communalSceneInteractor.isLaunchingWidget,
+                    ) { launchingWidget ->
+                        !launchingWidget
+                    }
+                    .filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) ->
+                        isOccluded && !isDreaming
                     }
                     .collect { _ ->
                         communalSceneInteractor.changeScene(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 1c445a7..7801c00 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -17,15 +17,16 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolver
 import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolver
@@ -61,6 +62,8 @@
     deviceEntryInteractor: DeviceEntryInteractor,
     quickSettingsSceneFamilyResolver: QuickSettingsSceneFamilyResolver,
     notifShadeSceneFamilyResolver: NotifShadeSceneFamilyResolver,
+    powerInteractor: PowerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -124,10 +127,12 @@
                     scene = Scenes.Bouncer,
                     stateWithoutSceneContainer = PRIMARY_BOUNCER
                 ),
-                transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER),
+                alternateBouncerInteractor.isVisible,
                 isOnShadeWhileUnlocked,
-            ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked ->
-                !isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked
+                powerInteractor.isAsleep,
+            ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked, isAsleep ->
+                (!isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked) ||
+                    isAsleep
             }
             .filter { it }
             .sampleFilter(dismissAction) { it !is DismissAction.None }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 0682d87..2af95f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -53,6 +53,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
@@ -142,14 +143,18 @@
      *
      * This is useful for experiences like the lock screen preview mode, where the affordances must
      * always be visible.
+     *
+     * @param overrideQuickAffordanceId If null, return the currently-set quick affordance;
+     *   otherwise, override and return the correspondent [KeyguardQuickAffordanceModel].
      */
     suspend fun quickAffordanceAlwaysVisible(
         position: KeyguardQuickAffordancePosition,
+        overrideQuickAffordanceId: String? = null,
     ): Flow<KeyguardQuickAffordanceModel> {
         return if (isFeatureDisabledByDevicePolicy()) {
             flowOf(KeyguardQuickAffordanceModel.Hidden)
         } else {
-            quickAffordanceInternal(position)
+            quickAffordanceInternal(position, overrideQuickAffordanceId)
         }
     }
 
@@ -299,12 +304,24 @@
     }
 
     private fun quickAffordanceInternal(
-        position: KeyguardQuickAffordancePosition
+        position: KeyguardQuickAffordancePosition,
+        overrideAffordanceId: String? = null,
     ): Flow<KeyguardQuickAffordanceModel> =
         repository
             .get()
             .selections
-            .map { it[position.toSlotId()] ?: emptyList() }
+            .map { selections ->
+                val overrideQuickAffordanceConfigs =
+                    overrideAffordanceId?.let {
+                        if (it == KEYGUARD_QUICK_AFFORDANCE_ID_NONE) {
+                            emptyList()
+                        } else {
+                            val config = repository.get().getConfig(it)
+                            listOfNotNull(config)
+                        }
+                    }
+                overrideQuickAffordanceConfigs ?: selections[position.toSlotId()] ?: emptyList()
+            }
             .flatMapLatest { configs -> combinedConfigs(position, configs) }
 
     private fun combinedConfigs(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 2581b59..1bbe843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.keyguard.shared.quickaffordance
 
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
 
 /** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
 enum class KeyguardQuickAffordancePosition {
@@ -25,8 +26,19 @@
 
     fun toSlotId(): String {
         return when (this) {
-            BOTTOM_START -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
-            BOTTOM_END -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+            BOTTOM_START -> SLOT_ID_BOTTOM_START
+            BOTTOM_END -> SLOT_ID_BOTTOM_END
         }
     }
+
+    companion object {
+
+        /** If the slot ID does not match any string, return null. */
+        fun parseKeyguardQuickAffordancePosition(slotId: String): KeyguardQuickAffordancePosition? =
+            when (slotId) {
+                SLOT_ID_BOTTOM_START -> BOTTOM_START
+                SLOT_ID_BOTTOM_END -> BOTTOM_END
+                else -> null
+            }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 6031ef6..51ce355 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -275,6 +275,15 @@
         }
     }
 
+    fun onStartCustomizingQuickAffordances(
+        initiallySelectedSlotId: String?,
+    ) {
+        quickAffordancesCombinedViewModel.enablePreviewMode(
+            initiallySelectedSlotId = initiallySelectedSlotId,
+            shouldHighlightSelectedAffordance = true,
+        )
+    }
+
     fun onSlotSelected(slotId: String) {
         if (KeyguardBottomAreaRefactor.isEnabled) {
             quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
@@ -283,6 +292,21 @@
         }
     }
 
+    fun onPreviewQuickAffordanceSelected(slotId: String, quickAffordanceId: String) {
+        quickAffordancesCombinedViewModel.onPreviewQuickAffordanceSelected(
+            slotId,
+            quickAffordanceId,
+        )
+    }
+
+    fun onDefaultPreview() {
+        quickAffordancesCombinedViewModel.onClearPreviewQuickAffordances()
+        quickAffordancesCombinedViewModel.enablePreviewMode(
+            initiallySelectedSlotId = null,
+            shouldHighlightSelectedAffordance = false,
+        )
+    }
+
     fun destroy() {
         isDestroyed = true
         lockscreenSmartspaceController.disconnect()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 0532ee2..a6108c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -31,7 +31,16 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES
 import com.android.systemui.util.kotlin.logD
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +68,7 @@
         return try {
             val renderer =
                 if (Flags.lockscreenPreviewRendererCreateOnMainThread()) {
-                    runBlocking ("$TAG#previewRendererFactory.create", mainDispatcher) {
+                    runBlocking("$TAG#previewRendererFactory.create", mainDispatcher) {
                         previewRendererFactory.create(request)
                     }
                 } else {
@@ -157,16 +166,35 @@
         }
 
         when (message.what) {
-            KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
-                message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+            MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES -> {
+                checkNotNull(renderer)
+                    .onStartCustomizingQuickAffordances(
+                        initiallySelectedSlotId =
+                            message.data.getString(KEY_INITIALLY_SELECTED_SLOT_ID)
+                                ?: SLOT_ID_BOTTOM_START
+                    )
+            }
+            MESSAGE_ID_SLOT_SELECTED -> {
+                message.data.getString(KEY_SLOT_ID)?.let { slotId ->
                     checkNotNull(renderer).onSlotSelected(slotId = slotId)
                 }
             }
-            KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
-                checkNotNull(renderer)
-                    .hideSmartspace(
-                        message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
-                    )
+            MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED -> {
+                val slotId = message.data.getString(KEY_SLOT_ID)
+                val quickAffordanceId = message.data.getString(KEY_QUICK_AFFORDANCE_ID)
+                if (slotId != null && quickAffordanceId != null) {
+                    checkNotNull(renderer)
+                        .onPreviewQuickAffordanceSelected(
+                            slotId = slotId,
+                            quickAffordanceId = quickAffordanceId,
+                        )
+                }
+            }
+            MESSAGE_ID_DEFAULT_PREVIEW -> {
+                checkNotNull(renderer).onDefaultPreview()
+            }
+            MESSAGE_ID_HIDE_SMART_SPACE -> {
+                checkNotNull(renderer).hideSmartspace(message.data.getBoolean(KEY_HIDE_SMART_SPACE))
             }
             else -> checkNotNull(onDestroy).invoke(this)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 2426f97..c885c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.FlowTracing.traceEmissionCount
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.NewPickerUiKeyguardPreview
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,6 +30,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -164,13 +166,36 @@
             .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
             .distinctUntilChanged()
 
+    private val previewAffordances =
+        MutableStateFlow<Map<KeyguardQuickAffordancePosition, String>>(emptyMap())
+
     /** An observable for the view-model of the "start button" quick affordance. */
     val startButton: Flow<KeyguardQuickAffordanceViewModel> =
-        button(KeyguardQuickAffordancePosition.BOTTOM_START)
+        if (NewPickerUiKeyguardPreview.isEnabled) {
+            previewAffordances.flatMapLatestConflated {
+                button(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    overrideQuickAffordanceId = it[KeyguardQuickAffordancePosition.BOTTOM_START],
+                )
+            }
+        } else {
+            button(
+                KeyguardQuickAffordancePosition.BOTTOM_START,
+            )
+        }
 
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
-        button(KeyguardQuickAffordancePosition.BOTTOM_END)
+        if (NewPickerUiKeyguardPreview.isEnabled) {
+            previewAffordances.flatMapLatestConflated {
+                button(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    overrideQuickAffordanceId = it[KeyguardQuickAffordancePosition.BOTTOM_END],
+                )
+            }
+        } else {
+            button(KeyguardQuickAffordancePosition.BOTTOM_END)
+        }
 
     /**
      * Notifies that a slot with the given ID has been selected in the preview experience that is
@@ -183,6 +208,28 @@
     }
 
     /**
+     * Notifies to preview an affordance at a given slot ID. This is ignored for the real lock
+     * screen experience.
+     */
+    fun onPreviewQuickAffordanceSelected(slotId: String, affordanceId: String) {
+        val position =
+            KeyguardQuickAffordancePosition.parseKeyguardQuickAffordancePosition(slotId) ?: return
+        previewAffordances.value =
+            previewAffordances.value.toMutableMap().let {
+                it[position] = affordanceId
+                HashMap(it)
+            }
+    }
+
+    /**
+     * Notifies to clear up the preview affordances map. This is ignored for the real lock screen
+     * experience.
+     */
+    fun onClearPreviewQuickAffordances() {
+        previewAffordances.value = emptyMap()
+    }
+
+    /**
      * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
      * the lock screen preview in wallpaper picker / settings and not the real experience on the
      * lock screen.
@@ -207,14 +254,16 @@
     }
 
     private fun button(
-        position: KeyguardQuickAffordancePosition
+        position: KeyguardQuickAffordancePosition,
+        overrideQuickAffordanceId: String? = null,
     ): Flow<KeyguardQuickAffordanceViewModel> {
         return previewMode
             .flatMapLatest { previewMode ->
                 combine(
                         if (previewMode.isInPreviewMode) {
                             quickAffordanceInteractor.quickAffordanceAlwaysVisible(
-                                position = position
+                                position = position,
+                                overrideQuickAffordanceId = overrideQuickAffordanceId,
                             )
                         } else {
                             quickAffordanceInteractor.quickAffordance(position = position)
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
new file mode 100644
index 0000000..03476ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/BaseActivatable.kt
@@ -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.systemui.lifecycle
+
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A base [Activatable] with the following characteristics:
+ * 1. **Can be concurrently activated by no more than one owner.** A previous call to [activate]
+ *    must be canceled before a new call to [activate] can be made. Trying to call [activate] while
+ *    already active will fail with an error
+ * 2. **Can manage child [Activatable]s**. See [addChild] and [removeChild]. Added children
+ *    automatically track the activation state of the parent such that when the parent is active,
+ *    the children are active and vice-versa. Children are also retained such that deactivating the
+ *    parent and reactivating it also cancels and reactivates the children.
+ */
+abstract class BaseActivatable : Activatable {
+
+    private val _isActive = AtomicBoolean(false)
+
+    var isActive: Boolean
+        get() = _isActive.get()
+        private set(value) {
+            _isActive.set(value)
+        }
+
+    final override suspend fun activate(): Nothing {
+        val allowed = _isActive.compareAndSet(false, true)
+        check(allowed) { "Cannot activate an already active activatable!" }
+
+        coroutineScope {
+            try {
+                launch { manageChildren() }
+                onActivated()
+            } finally {
+                isActive = false
+            }
+        }
+    }
+
+    /**
+     * Notifies that the [Activatable] has been activated.
+     *
+     * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+     * its state fresh and/or perform side-effects.
+     *
+     * The method suspends and doesn't return until all work required by the object is finished. In
+     * most cases, it's expected for the work to remain ongoing forever so this method will forever
+     * suspend its caller until the coroutine that called it is canceled.
+     *
+     * Implementations could follow this pattern:
+     * ```kotlin
+     * override suspend fun onActivated(): Nothing {
+     *     coroutineScope {
+     *         launch { ... }
+     *         launch { ... }
+     *         launch { ... }
+     *     }
+     * }
+     * ```
+     *
+     * @see activate
+     */
+    protected abstract suspend fun onActivated(): Nothing
+
+    private val newChildren = Channel<Activatable>(Channel.BUFFERED)
+    private val jobByChild: MutableMap<Activatable, Job> by lazy { mutableMapOf() }
+
+    private suspend fun manageChildren(): Nothing {
+        coroutineScope {
+            // Reactivate children that were added during a previous activation:
+            jobByChild.keys.forEach { child -> jobByChild[child] = launch { child.activate() } }
+
+            // Process requests to add more children:
+            newChildren.receiveAsFlow().collect { newChild ->
+                removeChildInternal(newChild)
+                jobByChild[newChild] = launch { newChild.activate() }
+            }
+
+            awaitCancellation()
+        }
+    }
+
+    fun addChild(child: Activatable) {
+        newChildren.trySend(child)
+    }
+
+    fun removeChild(child: Activatable) {
+        removeChildInternal(child)
+    }
+
+    private fun removeChildInternal(child: Activatable) {
+        jobByChild.remove(child)?.cancel()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt
deleted file mode 100644
index 4dd76f8..0000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.lifecycle
-
-import java.util.concurrent.atomic.AtomicBoolean
-
-/**
- * An [Activatable] that can be concurrently activated by no more than one owner.
- *
- * A previous call to [activate] must be canceled before a new call to [activate] can be made.
- * Trying to call [activate] while already active will fail with an error.
- */
-abstract class SafeActivatable : Activatable {
-
-    private val _isActive = AtomicBoolean(false)
-
-    var isActive: Boolean
-        get() = _isActive.get()
-        private set(value) {
-            _isActive.set(value)
-        }
-
-    final override suspend fun activate(): Nothing {
-        val allowed = _isActive.compareAndSet(false, true)
-        check(allowed) { "Cannot activate an already active activatable!" }
-
-        try {
-            onActivated()
-        } finally {
-            isActive = false
-        }
-    }
-
-    /**
-     * Notifies that the [Activatable] has been activated.
-     *
-     * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
-     * its state fresh and/or perform side-effects.
-     *
-     * The method suspends and doesn't return until all work required by the object is finished. In
-     * most cases, it's expected for the work to remain ongoing forever so this method will forever
-     * suspend its caller until the coroutine that called it is canceled.
-     *
-     * Implementations could follow this pattern:
-     * ```kotlin
-     * override suspend fun onActivated(): Nothing {
-     *     coroutineScope {
-     *         launch { ... }
-     *         launch { ... }
-     *         launch { ... }
-     *     }
-     * }
-     * ```
-     *
-     * @see activate
-     */
-    protected abstract suspend fun onActivated(): Nothing
-}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 2edde4a..104b076 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -23,7 +23,7 @@
 import kotlinx.coroutines.launch
 
 /** Base class for all System UI view-models. */
-abstract class SysUiViewModel : SafeActivatable() {
+abstract class SysUiViewModel : BaseActivatable() {
 
     override suspend fun onActivated(): Nothing {
         awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
index 6cf4441..28e4fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
@@ -26,6 +26,8 @@
 import android.content.pm.ServiceInfo;
 import android.os.RemoteException;
 
+import androidx.annotation.Nullable;
+
 import javax.inject.Inject;
 
 // Adapter that wraps calls to PackageManager or IPackageManager for {@link TileLifecycleManager}.
@@ -45,6 +47,7 @@
         mIPackageManager = AppGlobals.getPackageManager();
     }
 
+    @Nullable
     public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
             throws RemoteException {
         return mIPackageManager.getServiceInfo(className, flags, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 96df728..cbcf68c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -188,10 +188,10 @@
     public boolean isActiveTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
-                    META_DATA_QUERY_FLAGS);
-            return info.metaData != null
+                    META_DATA_QUERY_FLAGS, mUser.getIdentifier());
+            return info != null && info.metaData != null
                     && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (RemoteException e) {
             return false;
         }
     }
@@ -206,10 +206,10 @@
     public boolean isToggleableTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
-                    META_DATA_QUERY_FLAGS);
-            return info.metaData != null
+                    META_DATA_QUERY_FLAGS, mUser.getIdentifier());
+            return info != null && info.metaData != null
                     && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false);
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (RemoteException e) {
             return false;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index c932cee..0aaea8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.qs.tiles.impl.custom.data.repository
 
 import android.content.pm.PackageManager
-import android.content.pm.ServiceInfo
 import android.graphics.drawable.Icon
+import android.os.RemoteException
 import android.os.UserHandle
 import android.service.quicksettings.Tile
 import android.service.quicksettings.TileService
@@ -163,13 +163,14 @@
     override suspend fun isTileActive(): Boolean =
         withContext(backgroundContext) {
             try {
-                val info: ServiceInfo =
+                val info =
                     packageManagerAdapter.getServiceInfo(
                         tileSpec.componentName,
-                        META_DATA_QUERY_FLAGS
+                        META_DATA_QUERY_FLAGS,
+                        getCurrentTileWithUser()?.user?.identifier ?: UserHandle.USER_CURRENT,
                     )
-                info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
-            } catch (e: PackageManager.NameNotFoundException) {
+                info?.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+            } catch (e: RemoteException) {
                 false
             }
         }
@@ -177,13 +178,14 @@
     override suspend fun isTileToggleable(): Boolean =
         withContext(backgroundContext) {
             try {
-                val info: ServiceInfo =
+                val info =
                     packageManagerAdapter.getServiceInfo(
                         tileSpec.componentName,
-                        META_DATA_QUERY_FLAGS
+                        META_DATA_QUERY_FLAGS,
+                        getCurrentTileWithUser()?.user?.identifier ?: UserHandle.USER_CURRENT
                     )
-                info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
-            } catch (e: PackageManager.NameNotFoundException) {
+                info?.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+            } catch (e: RemoteException) {
                 false
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index e73664d..cc46216 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -152,7 +152,7 @@
             hydrateBackStack()
             resetShadeSessions()
             handleKeyguardEnabledness()
-            notifyKeyguardDismissCallbacks()
+            notifyKeyguardDismissCancelledCallbacks()
             refreshLockscreenEnabled()
         } else {
             sceneLogger.logFrameworkEnabled(
@@ -379,8 +379,10 @@
                     when {
                         isAlternateBouncerVisible -> {
                             // When the device becomes unlocked when the alternate bouncer is
-                            // showing, always hide the alternate bouncer...
+                            // showing, always hide the alternate bouncer and notify dismiss
+                            // succeeded
                             alternateBouncerInteractor.hide()
+                            dismissCallbackRegistry.notifyDismissSucceeded()
 
                             // ... and go to Gone or stay on the current scene
                             if (
@@ -394,9 +396,11 @@
                                 null
                             }
                         }
-                        isOnPrimaryBouncer ->
+                        isOnPrimaryBouncer -> {
                             // When the device becomes unlocked in primary Bouncer,
+                            // notify dismiss succeeded and
                             // go to previous scene or Gone.
+                            dismissCallbackRegistry.notifyDismissSucceeded()
                             if (
                                 previousScene.value == Scenes.Lockscreen ||
                                     !statusBarStateController.leaveOpenOnKeyguardHide()
@@ -410,6 +414,7 @@
                                     "device was unlocked with primary bouncer showing," +
                                         " from sceneKey=$prevScene"
                             }
+                        }
                         isOnLockscreen ->
                             // The lockscreen should be dismissed automatically in 2 scenarios:
                             // 1. When face auth bypass is enabled and authentication happens while
@@ -468,6 +473,9 @@
         applicationScope.launch {
             powerInteractor.isAsleep.collect { isAsleep ->
                 if (isAsleep) {
+                    alternateBouncerInteractor.hide()
+                    dismissCallbackRegistry.notifyDismissCancelled()
+
                     switchToScene(
                         targetSceneKey = Scenes.Lockscreen,
                         loggingReason = "device is starting to sleep",
@@ -771,15 +779,23 @@
         }
     }
 
-    private fun notifyKeyguardDismissCallbacks() {
+    private fun notifyKeyguardDismissCancelledCallbacks() {
         applicationScope.launch {
-            sceneInteractor.currentScene.pairwise().collect { (from, to) ->
-                when {
-                    from != Scenes.Bouncer -> Unit
-                    to == Scenes.Gone -> dismissCallbackRegistry.notifyDismissSucceeded()
-                    else -> dismissCallbackRegistry.notifyDismissCancelled()
+            combine(
+                    deviceEntryInteractor.isUnlocked,
+                    sceneInteractor.currentScene.pairwise(),
+                ) { isUnlocked, (from, to) ->
+                    when {
+                        from != Scenes.Bouncer -> false
+                        to != Scenes.Gone && !isUnlocked -> true
+                        else -> false
+                    }
                 }
-            }
+                .collect { notifyKeyguardDismissCancelled ->
+                    if (notifyKeyguardDismissCancelled) {
+                        dismissCallbackRegistry.notifyDismissCancelled()
+                    }
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 65a59f5..c023b83 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4536,6 +4536,13 @@
     private final class StatusBarStateListener implements StateListener {
         @Override
         public void onStateChanged(int statusBarState) {
+            onStateChanged(statusBarState, false);
+        }
+
+        private void onStateChanged(
+                int statusBarState,
+                boolean animatingUnlockedShadeToKeyguardBypass
+        ) {
             boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
             boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
             int oldState = mBarState;
@@ -4607,15 +4614,14 @@
                 //  - getting notified again about the current SHADE or KEYGUARD state
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
-                        && mScreenOffAnimationController.isKeyguardShowDelayed();
+                        && mScreenOffAnimationController.isKeyguardShowDelayed()
+                        //Bypasses animatingUnlockedShadeToKeyguard for b/337742708
+                        && !animatingUnlockedShadeToKeyguardBypass;
                 if (!animatingUnlockedShadeToKeyguard) {
                     // Only make the status bar visible if we're not animating the screen off, since
                     // we only want to be showing the clock/notifications during the animation.
-                    if (keyguardShowing) {
-                        mShadeLog.v("Updating keyguard status bar state to visible");
-                    } else {
-                        mShadeLog.v("Updating keyguard status bar state to invisible");
-                    }
+                    mShadeLog.logKeyguardStatudBarVisibiliy(keyguardShowing, isOnAod(),
+                            animatingUnlockedShadeToKeyguardBypass, oldState, statusBarState);
                     mKeyguardStatusBarViewController.updateViewState(
                             /* alpha= */ 1f,
                             keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -4692,7 +4698,8 @@
                     .addTagListener(QS.TAG, mQsController.getQsFragmentListener());
             if (!SceneContainerFlag.isEnabled()) {
                 mStatusBarStateController.addCallback(mStatusBarStateListener);
-                mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
+                // Bypass animatingUnlockedShadeToKeyguard in onStateChanged for b/337742708
+                mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState(), true);
             }
             mConfigurationController.addCallback(mConfigurationListener);
             // Theme might have changed between inflating this view and attaching it to the
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 66a310c..f1eaec8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -411,4 +411,29 @@
             }
         )
     }
+
+    fun logKeyguardStatudBarVisibiliy(
+        visibility: Boolean,
+        isOnAod: Boolean,
+        animatingUnlockedShadeToKeyguardBypass: Boolean,
+        oldShadeState: Int,
+        newShadeState: Int,
+    ) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                bool1 = visibility
+                bool2 = isOnAod
+                bool3 = animatingUnlockedShadeToKeyguardBypass
+                int1 = oldShadeState
+                int2 = newShadeState
+            },
+            {
+                "Setting keyguard status bar visibility to: $bool1, isOnAod: $bool2" +
+                    "oldShadeState: $int1, newShadeState: $int2," +
+                    "animatingUnlockedShadeToKeyguardBypass: $bool3"
+            }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index fc7d682..0d209d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -27,9 +27,6 @@
 /**
  * A notification collection that manages the list of {@link NotificationEntry}s that will be
  * rendered.
- *
- * TODO: (b/145659174) Once we fully migrate to {@link NotifPipeline}, we probably won't need this,
- * but having it for now makes it easy to switch between the two.
  */
 public interface CommonNotifCollection {
     /**
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 20b1fff..e802076 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
@@ -258,7 +258,7 @@
     private float mOverScrolledBottomPixels;
     private final ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>();
     private final ListenerSet<Runnable> mHeadsUpHeightChangedListeners = new ListenerSet<>();
-    private NotificationLogger.OnChildLocationsChangedListener mListener;
+    private NotificationLogger.OnChildLocationsChangedListener mLegacyLocationsChangedListener;
     private OnNotificationLocationsChangedListener mLocationsChangedListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -1281,7 +1281,7 @@
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
         NotificationsLiveDataStoreRefactor.assertInLegacyMode();
-        mListener = listener;
+        mLegacyLocationsChangedListener = listener;
     }
 
     private void setMaxLayoutHeight(int maxLayoutHeight) {
@@ -4433,8 +4433,8 @@
                 mLocationsChangedListener.onChildLocationsChanged(collectVisibleLocationsCallable);
             }
         } else {
-            if (mListener != null) {
-                mListener.onChildLocationsChanged();
+            if (mLegacyLocationsChangedListener != null) {
+                mLegacyLocationsChangedListener.onChildLocationsChanged();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index de4d14d..0f93ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -545,6 +545,7 @@
 
     @VisibleForTesting
     void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
+        SceneContainerFlag.assertInLegacyMode();
         hideAlternateBouncer(false);
     }
 
@@ -554,6 +555,7 @@
      */
     @VisibleForTesting
     void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
+        SceneContainerFlag.assertInLegacyMode();
         if (mAlternateBouncerInteractor.isVisibleState()) {
             hideAlternateBouncer(false);
         }
@@ -981,7 +983,7 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
             }
-            if (hideBouncerWhenShowing) {
+            if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) {
                 hideAlternateBouncer(true);
             }
             mKeyguardUpdateManager.sendKeyguardReset();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 4f7749b..e1dcc52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -21,7 +21,11 @@
 import android.util.Log
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.PlatformButton
@@ -88,7 +92,15 @@
     @Composable
     private fun ModesDialogContent(dialog: SystemUIDialog) {
         AlertDialogContent(
-            title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+            modifier = Modifier.semantics {
+                testTagsAsResourceId = true
+            },
+            title = {
+                Text(
+                    modifier = Modifier.testTag("modes_title"),
+                    text = stringResource(R.string.zen_modes_dialog_title)
+                )
+            },
             content = { ModeTileGrid(viewModel.get()) },
             neutralButton = {
                 PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 3b392c8..3fffd9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -32,6 +32,7 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import com.android.systemui.common.ui.compose.Icon
@@ -70,12 +71,12 @@
                     Text(
                         viewModel.text,
                         fontWeight = FontWeight.W500,
-                        modifier = Modifier.tileMarquee()
+                        modifier = Modifier.tileMarquee().testTag("name")
                     )
                     Text(
                         viewModel.subtext,
                         fontWeight = FontWeight.W400,
-                        modifier = Modifier.tileMarquee()
+                        modifier = Modifier.tileMarquee().testTag("state")
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
index ef9f8ff..ae0061b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -19,7 +19,7 @@
 import android.util.IndentingPrintWriter
 import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.lifecycle.SafeActivatable
+import com.android.systemui.lifecycle.BaseActivatable
 import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printCollection
@@ -189,7 +189,7 @@
 ) : SimpleFlowDumper(), ActivatableFlowDumper {
 
     private val registration =
-        object : SafeActivatable() {
+        object : BaseActivatable() {
             override suspend fun onActivated(): Nothing {
                 try {
                     dumpManager.registerCriticalDumpable(
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 45799b2..7385b82 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -115,7 +115,6 @@
     private final Executor mSysuiUiBgExecutor;
 
     private final Bubbles.SysuiProxy mSysuiProxy;
-    // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
     private final List<NotifCallback> mCallbacks = new ArrayList<>();
     private final StatusBarWindowCallback mStatusBarWindowCallback;
     private final Runnable mSensitiveStateChangedListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 32d059b..a0fe538b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -29,6 +30,10 @@
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setSceneTransition
@@ -82,6 +87,8 @@
                 deviceEntryInteractor = kosmos.deviceEntryInteractor,
                 quickSettingsSceneFamilyResolver = kosmos.quickSettingsSceneFamilyResolver,
                 notifShadeSceneFamilyResolver = kosmos.notifShadeSceneFamilyResolver,
+                powerInteractor = kosmos.powerInteractor,
+                alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
             )
     }
 
@@ -234,6 +241,32 @@
         }
 
     @Test
+    fun resetDismissAction_onBouncer_OnAsleep() =
+        testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+            keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = {},
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(resetDismissAction).isNull()
+            kosmos.fakePowerRepository.updateWakefulness(
+                rawState = WakefulnessState.ASLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.TIMEOUT,
+                powerButtonLaunchGestureTriggered = false,
+            )
+            assertThat(resetDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
     fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
         testScope.runTest {
             val dismissAction by collectLastValue(underTest.dismissAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 24bea2c..73b9f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -20,6 +20,7 @@
 import android.app.admin.DevicePolicyManager
 import android.content.Intent
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -402,6 +403,67 @@
         }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_NEW_PICKER_UI)
+    fun startButton_inPreviewMode_onPreviewQuickAffordanceSelected() =
+        testScope.runTest {
+            underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+            underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
+
+            repository.setKeyguardShowing(false)
+            val latest = collectLastValue(underTest.startButton)
+
+            val icon: Icon = mock()
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = null,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val defaultConfigKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            // Set up the quick access wallet config
+            val quickAccessWalletAffordanceConfigKey =
+                quickAccessWalletAffordanceConfig
+                    .apply {
+                        onTriggeredResult =
+                            KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+                                intent = Intent("action"),
+                                canShowWhileLocked = false,
+                            )
+                        setState(
+                            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                                icon = icon,
+                                activationState = ActivationState.Active,
+                            )
+                        )
+                    }
+                    .let {
+                        KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() +
+                            "::${quickAccessWalletAffordanceConfig.key}"
+                    }
+
+            // onPreviewQuickAffordanceSelected should trigger the override with the quick access
+            // wallet quick affordance
+            underTest.onPreviewQuickAffordanceSelected(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+            )
+            Truth.assertThat(latest()?.configKey).isEqualTo(quickAccessWalletAffordanceConfigKey)
+
+            // onClearPreviewQuickAffordances should make the default quick affordance shows again
+            underTest.onClearPreviewQuickAffordances()
+            Truth.assertThat(latest()?.configKey).isEqualTo(defaultConfigKey)
+        }
+
+    @Test
     fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
         testScope.runTest {
             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
@@ -445,7 +507,7 @@
         }
 
     @Test
-    fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
+    fun endButton_inHighlightedPreviewMode_dimmedWhenOtherIsSelected() =
         testScope.runTest {
             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
             underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 68307b1..c1cf91d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -23,6 +23,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -50,6 +52,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -183,6 +186,40 @@
                 .thenReturn(defaultPackageInfo);
     }
 
+    private void setPackageInstalledForUser(
+            boolean installed,
+            boolean active,
+            boolean toggleable,
+            int user
+    ) throws Exception {
+        ServiceInfo defaultServiceInfo = null;
+        if (installed) {
+            defaultServiceInfo = new ServiceInfo();
+            defaultServiceInfo.metaData = new Bundle();
+            defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, active);
+            defaultServiceInfo.metaData
+                    .putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, toggleable);
+            when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), eq(user)))
+                    .thenReturn(defaultServiceInfo);
+            if (user == 0) {
+                when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt()))
+                        .thenReturn(defaultServiceInfo);
+            }
+            PackageInfo defaultPackageInfo = new PackageInfo();
+            when(mMockPackageManagerAdapter.getPackageInfoAsUser(anyString(), anyInt(), eq(user)))
+                    .thenReturn(defaultPackageInfo);
+        } else {
+            when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), eq(user)))
+                    .thenReturn(null);
+            if (user == 0) {
+                when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt()))
+                        .thenThrow(new PackageManager.NameNotFoundException());
+            }
+            when(mMockPackageManagerAdapter.getPackageInfoAsUser(anyString(), anyInt(), eq(user)))
+                    .thenThrow(new PackageManager.NameNotFoundException());
+        }
+    }
+
     private void verifyBind(int times) {
         assertEquals(times > 0, mContext.isBound(mTileServiceComponentName));
     }
@@ -557,6 +594,100 @@
         verify(mockContext).unbindService(captor.getValue());
     }
 
+    @Test
+    public void testIsActive_user0_packageInstalled() throws Exception {
+        setPackageInstalledForUser(true, true, false, 0);
+        mUser = UserHandle.of(0);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isActiveTile()).isTrue();
+    }
+
+    @Test
+    public void testIsActive_user10_packageInstalled_notForUser0() throws Exception {
+        setPackageInstalledForUser(true, true, false, 10);
+        setPackageInstalledForUser(false, false, false, 0);
+        mUser = UserHandle.of(10);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isActiveTile()).isTrue();
+    }
+
+    @Test
+    public void testIsToggleable_user0_packageInstalled() throws Exception {
+        setPackageInstalledForUser(true, false, true, 0);
+        mUser = UserHandle.of(0);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isToggleableTile()).isTrue();
+    }
+
+    @Test
+    public void testIsToggleable_user10_packageInstalled_notForUser0() throws Exception {
+        setPackageInstalledForUser(true, false, true, 10);
+        setPackageInstalledForUser(false, false, false, 0);
+        mUser = UserHandle.of(10);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isToggleableTile()).isTrue();
+    }
+
+    @Test
+    public void testIsToggleableActive_installedForDifferentUser() throws Exception {
+        setPackageInstalledForUser(true, false, false, 10);
+        setPackageInstalledForUser(false, true, true, 0);
+        mUser = UserHandle.of(10);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isToggleableTile()).isFalse();
+        assertThat(manager.isActiveTile()).isFalse();
+    }
+
     private void mockChangeEnabled(long changeId, boolean enabled) {
         doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                 any(UserHandle.class)));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 957f092..27eadb1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
 import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
@@ -37,5 +39,7 @@
             deviceEntryInteractor = deviceEntryInteractor,
             quickSettingsSceneFamilyResolver = quickSettingsSceneFamilyResolver,
             notifShadeSceneFamilyResolver = notifShadeSceneFamilyResolver,
+            powerInteractor = powerInteractor,
+            alternateBouncerInteractor = alternateBouncerInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
index bcc7393..4c05939 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
@@ -21,7 +21,7 @@
 class FakeActivatable(
     private val onActivation: () -> Unit = {},
     private val onDeactivation: () -> Unit = {},
-) : SafeActivatable() {
+) : BaseActivatable() {
     var activationCount = 0
     var cancellationCount = 0
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index fa8d363..5ac7c39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -18,14 +18,19 @@
 
 import android.content.ComponentName
 import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
 import android.content.pm.ServiceInfo
 import android.os.Bundle
+import android.os.UserHandle
 import com.android.systemui.qs.external.PackageManagerAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.hamcrest.MockitoHamcrest.intThat
 
 /**
  * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
@@ -36,29 +41,26 @@
  * [com.android.systemui.qs.external.TileServiceManager.isToggleableTile] or
  * [com.android.systemui.qs.external.TileServiceManager.isActiveTile] when the real objects are
  * used.
+ *
+ * The user this is set up must be a real user (`user >= 0`) or [UserHandle.USER_ALL].
  */
 class FakePackageManagerAdapterFacade(
     val componentName: ComponentName,
     val packageManagerAdapter: PackageManagerAdapter = mock {},
+    user: Int = UserHandle.USER_ALL,
 ) {
 
     private var isToggleable: Boolean = false
     private var isActive: Boolean = false
 
     init {
-        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
-            createServiceInfo()
+        if (user == UserHandle.USER_ALL) {
+            setForAllUsers()
+        } else if (user >= 0) {
+            setExclusiveForUser(user)
+        } else {
+            throw IllegalArgumentException("User must be a real user or UserHandle.USER_ALL")
         }
-        whenever(
-                packageManagerAdapter.getPackageInfoAsUser(
-                    eq(componentName.packageName),
-                    anyInt(),
-                    anyInt()
-                )
-            )
-            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
-        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
-            .thenAnswer { createServiceInfo() }
     }
 
     private fun createServiceInfo(): ServiceInfo {
@@ -84,4 +86,67 @@
     fun setIsToggleable(isToggleable: Boolean) {
         this.isToggleable = isToggleable
     }
+
+    fun setExclusiveForUser(newUser: Int) {
+        check(newUser >= 0)
+        val notEqualMatcher = NotEqualMatcher(newUser)
+        if (newUser == 0) {
+            whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+                createServiceInfo()
+            }
+        }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    eq(newUser)
+                )
+            )
+            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    intThat(notEqualMatcher),
+                )
+            )
+            .thenThrow(PackageManager.NameNotFoundException())
+
+        whenever(
+                packageManagerAdapter.getServiceInfo(
+                    eq(componentName),
+                    anyInt(),
+                    intThat(notEqualMatcher)
+                )
+            )
+            .thenAnswer { null }
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), eq(newUser)))
+            .thenAnswer { createServiceInfo() }
+    }
+
+    fun setForAllUsers() {
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+            createServiceInfo()
+        }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    anyInt()
+                )
+            )
+            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
+            .thenAnswer { createServiceInfo() }
+    }
+}
+
+private class NotEqualMatcher(private val notEqualValue: Int) : BaseMatcher<Int>() {
+    override fun describeTo(description: Description?) {
+        description?.appendText("!= $notEqualValue")
+    }
+
+    override fun matches(item: Any?): Boolean {
+        return (item as? Int)?.equals(notEqualValue)?.not() ?: true
+    }
 }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 531fa45..09068d5 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -171,7 +171,7 @@
             EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
             EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
 
-    private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
+    private static HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
     private CameraManager mCameraManager;
 
     private static boolean checkForLatencyAPI() {
@@ -820,7 +820,7 @@
             mCameraManager = getSystemService(CameraManager.class);
 
             String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
-            if (cameraIds != null) {
+            if (cameraIds != null && mMetadataVendorIdMap.isEmpty()) {
                 for (String cameraId : cameraIds) {
                     CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
                     Object thisClass = CameraCharacteristics.Key.class;
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 6150343..58cd2e4 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -166,6 +166,14 @@
     jarjar_rules: ":ravenwood-services-jarjar-rules",
 }
 
+java_device_for_host {
+    name: "ravenwood-junit-impl-for-ravenizer",
+    libs: [
+        "ravenwood-junit-impl",
+    ],
+    visibility: [":__subpackages__"],
+}
+
 // Separated out from ravenwood-junit-impl since it needs to compile
 // against `module_current`
 java_library {
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java
similarity index 97%
rename from ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
rename to ravenwood/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java
index b477117..30abaa2 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood;
+package com.android.ravenwoodtest;
 
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
diff --git a/ravenwood/resapk_test/test/com/android/ravenwood/resapk_test/RavenwoodResApkTest.java b/ravenwood/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
similarity index 96%
rename from ravenwood/resapk_test/test/com/android/ravenwood/resapk_test/RavenwoodResApkTest.java
rename to ravenwood/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
index 1029ed2..e547114 100644
--- a/ravenwood/resapk_test/test/com/android/ravenwood/resapk_test/RavenwoodResApkTest.java
+++ b/ravenwood/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.resapk_test;
+package com.android.ravenwoodtest.resapk_test;
 
 
 import static junit.framework.TestCase.assertTrue;
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsConstantsTest.java b/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java
similarity index 99%
rename from ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsConstantsTest.java
rename to ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java
index 3332e24..633ed4e 100644
--- a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsConstantsTest.java
+++ b/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.runtimetest;
+package com.android.ravenwoodtest.runtimetest;
 
 // Copied from libcore/luni/src/test/java/libcore/android/system/OsConstantsTest.java
 
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java b/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
similarity index 99%
rename from ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
rename to ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index 05275b2..c2230c7 100644
--- a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
+++ b/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.runtimetest;
+package com.android.ravenwoodtest.runtimetest;
 
 import static android.system.OsConstants.S_ISBLK;
 import static android.system.OsConstants.S_ISCHR;
diff --git a/ravenwood/scripts/shrink-systemui-test b/ravenwood/scripts/shrink-systemui-test
new file mode 100755
index 0000000..8589c1d
--- /dev/null
+++ b/ravenwood/scripts/shrink-systemui-test
@@ -0,0 +1,131 @@
+#!/bin/bash
+# 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.
+
+set -e
+
+SCRIPT_NAME="${0##*/}"
+
+usage() {
+    cat <<"EOF"
+
+$SCRIPT_NAME: Shrink / unshrink SystemUiRavenTests.
+
+    SystemUiRavenTests has a lot of kotlin source files, so it's slow to build,
+    which is painful when you want to run it after updating ravenwood code
+    that SystemUiRavenTests depends on. (example: junit-src/)
+
+    This script basically removes the test files in SystemUI/multivalentTests
+    that don't have @EnabledOnRavenwood. But if we actaully remove them,
+    soong would re-generate the ninja file, which will take a long time,
+    so instead it'll truncate them.
+
+    This script will also tell git to ignore these files, so they won't shw up
+    in `git status`.
+    (Use `git ls-files -v | sed -ne "s/^[a-zS] //p"` to show ignored filse.)
+
+Usage:
+    $SCRIPT_NAME -s # Shrink the test files.
+
+    $SCRIPT_NAME -u # Undo it.
+
+EOF
+}
+
+TEST_PATH=${ANDROID_BUILD_TOP}/frameworks/base/packages/SystemUI/multivalentTests
+cd "$TEST_PATH"
+
+command=""
+case "$1" in
+    "-s") command=shrink ;;
+    "-u") command=unshrink ;;
+    *) usage ; exit 1 ;;
+esac
+
+
+echo "Listing test files...."
+files=( $(find . -name '*Test.kt' -o -name '*Test.java') )
+
+exemption='(BaseHeadsUpManagerTest)'
+
+shrink() {
+    local target=()
+    for file in ${files[@]}; do
+        # Check for exemption
+        if echo $file | egrep -q "$exemption"; then
+            echo "  Skip exempted file"
+            continue
+        fi
+
+        echo "Checking $file"
+        if ! [[ -f $file ]] ; then
+            echo "  Skip non regular file"
+            continue
+        fi
+
+        if ! [[ -s $file ]] ; then
+            echo "  Skip empty file"
+            continue
+        fi
+
+        if grep -q '@EnabledOnRavenwood' $file ; then
+            echo "  Skip ravenwood test file".
+            continue
+        fi
+
+        # It's a non ravenwood test file. Empty it.
+        : > $file
+
+        # Tell git to ignore the file
+
+        target+=($file)
+
+        echo "  Emptied"
+
+    done
+    if (( ${#target[@]} == 0 )) ; then
+        echo "No files emptied."
+        return 0
+    fi
+
+    git update-index --skip-worktree ${target[@]}
+
+    echo "Emptied ${#target[@]} files"
+    return 0
+}
+
+unshrink() {
+    local target=()
+
+    # Collect empty files
+    for file in ${files[@]}; do
+        if [[ -s $file ]] ; then
+            continue
+        fi
+
+        target+=($file)
+        : > $file
+    done
+    if (( ${#target[@]} == 0 )) ; then
+        echo "No files to restore."
+        return 0
+    fi
+    # Un-ignore the files, and check out the original files
+    echo "Restoring ${#target[@]} files..."
+    git update-index --no-skip-worktree ${target[@]}
+    git checkout goog/main ${target[@]}
+    return 0
+}
+
+$command
diff --git a/ravenwood/tools/ravenizer-fake/Android.bp b/ravenwood/tools/ravenizer-fake/Android.bp
deleted file mode 100644
index 7e2c407..0000000
--- a/ravenwood/tools/ravenizer-fake/Android.bp
+++ /dev/null
@@ -1,14 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-sh_binary_host {
-    name: "ravenizer",
-    src: "ravenizer",
-    visibility: ["//visibility:public"],
-}
diff --git a/ravenwood/tools/ravenizer-fake/ravenizer b/ravenwood/tools/ravenizer-fake/ravenizer
deleted file mode 100755
index 84b3c8e..0000000
--- a/ravenwood/tools/ravenizer-fake/ravenizer
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-# 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.
-
-# "Fake" ravenizer, which just copies the file.
-# We need it to add ravenizer support to Soong on AOSP,
-# when the actual ravenizer is not in AOSP yet.
-
-invalid_arg() {
-    echo "Ravenizer(fake): invalid args" 1>&2
-    exit 1
-}
-
-(( $# >= 4 )) || invalid_arg
-[[ "$1" == "--in-jar" ]] || invalid_arg
-[[ "$3" == "--out-jar" ]] || invalid_arg
-
-echo "Ravenizer(fake): copiyng $2 to $4"
-
-cp "$2" "$4"
diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp
new file mode 100644
index 0000000..2892d07
--- /dev/null
+++ b/ravenwood/tools/ravenizer/Android.bp
@@ -0,0 +1,25 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_binary_host {
+    name: "ravenizer",
+    main_class: "com.android.platform.test.ravenwood.ravenizer.RavenizerMain",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "hoststubgen-lib",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "ow2-asm-util",
+        "junit",
+        "ravenwood-junit-impl-for-ravenizer",
+    ],
+    visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
new file mode 100644
index 0000000..da9c7d9
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.zipEntryNameToClassName
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.util.CheckClassAdapter
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+import java.util.zip.ZipOutputStream
+
+/**
+ * Various stats on Ravenizer.
+ */
+data class RavenizerStats(
+    /** Total end-to-end time. */
+    var totalTime: Double = .0,
+
+    /** Time took to build [ClasNodes] */
+    var loadStructureTime: Double = .0,
+
+    /** Total real time spent for converting the jar file */
+    var totalProcessTime: Double = .0,
+
+    /** Total real time spent for converting class files (except for I/O time). */
+    var totalConversionTime: Double = .0,
+
+    /** Total real time spent for copying class files without modification. */
+    var totalCopyTime: Double = .0,
+
+    /** # of entries in the input jar file */
+    var totalEntiries: Int = 0,
+
+    /** # of *.class files in the input jar file */
+    var totalClasses: Int = 0,
+
+    /** # of *.class files that have been processed. */
+    var processedClasses: Int = 0,
+) {
+    override fun toString(): String {
+        return """
+            RavenizerStats{
+              totalTime=$totalTime,
+              loadStructureTime=$loadStructureTime,
+              totalProcessTime=$totalProcessTime,
+              totalConversionTime=$totalConversionTime,
+              totalCopyTime=$totalCopyTime,
+              totalEntiries=$totalEntiries,
+              totalClasses=$totalClasses,
+              processedClasses=$processedClasses,
+            }
+            """.trimIndent()
+    }
+}
+
+/**
+ * Main class.
+ */
+class Ravenizer(val options: RavenizerOptions) {
+    fun run() {
+        val stats = RavenizerStats()
+        stats.totalTime = log.nTime {
+            process(options.inJar.get, options.outJar.get, stats)
+        }
+        log.i(stats.toString())
+    }
+
+    private fun process(inJar: String, outJar: String, stats: RavenizerStats) {
+        var allClasses = ClassNodes.loadClassStructures(inJar) {
+            time -> stats.loadStructureTime = time
+        }
+
+        stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
+            ZipFile(inJar).use { inZip ->
+                val inEntries = inZip.entries()
+
+                stats.totalEntiries = inZip.size()
+
+                ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
+                    while (inEntries.hasMoreElements()) {
+                        val entry = inEntries.nextElement()
+
+                        if (entry.name.endsWith(".dex")) {
+                            // Seems like it's an ART jar file. We can't process it.
+                            // It's a fatal error.
+                            throw GeneralUserErrorException(
+                                "$inJar is not a desktop jar file. It contains a *.dex file."
+                            )
+                        }
+
+                        val className = zipEntryNameToClassName(entry.name)
+
+                        if (className != null) {
+                            stats.totalClasses += 1
+                        }
+
+                        if (className != null && shouldProcessClass(allClasses, className)) {
+                            stats.processedClasses += 1
+                            processSingleClass(inZip, entry, outZip, allClasses, stats)
+                        } else {
+                            // Too slow, let's use merge_zips to bring back the original classes.
+                            copyZipEntry(inZip, entry, outZip, stats)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Copy a single ZIP entry to the output.
+     */
+    private fun copyZipEntry(
+        inZip: ZipFile,
+        entry: ZipEntry,
+        out: ZipOutputStream,
+        stats: RavenizerStats,
+    ) {
+        stats.totalCopyTime += log.nTime {
+            inZip.getInputStream(entry).use { ins ->
+                // Copy unknown entries as is to the impl out. (but not to the stub out.)
+                val outEntry = ZipEntry(entry.name)
+                outEntry.method = 0
+                outEntry.size = entry.size
+                outEntry.crc = entry.crc
+                out.putNextEntry(outEntry)
+
+                ins.transferTo(out)
+
+                out.closeEntry()
+            }
+        }
+    }
+
+    private fun processSingleClass(
+        inZip: ZipFile,
+        entry: ZipEntry,
+        outZip: ZipOutputStream,
+        allClasses: ClassNodes,
+        stats: RavenizerStats,
+    ) {
+        val newEntry = ZipEntry(entry.name)
+        outZip.putNextEntry(newEntry)
+
+        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+            processSingleClass(entry, bis, outZip, allClasses, stats)
+        }
+        outZip.closeEntry()
+    }
+
+    /**
+     * Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
+     */
+    private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
+        return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
+    }
+
+    private fun processSingleClass(
+        entry: ZipEntry,
+        input: InputStream,
+        output: OutputStream,
+        allClasses: ClassNodes,
+        stats: RavenizerStats,
+    ) {
+        val cr = ClassReader(input)
+
+        lateinit var data: ByteArray
+        stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+            val flags = ClassWriter.COMPUTE_MAXS
+            val cw = ClassWriter(flags)
+            var outVisitor: ClassVisitor = cw
+
+            val enableChecker = false
+            if (enableChecker) {
+                outVisitor = CheckClassAdapter(outVisitor)
+            }
+
+            // This must be kept in sync with shouldProcessClass.
+            outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)
+
+            cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
+
+            data = cw.toByteArray()
+        }
+        output.write(data)
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
new file mode 100644
index 0000000..ff41818
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+@file:JvmName("RavenizerMain")
+
+package com.android.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.LogLevel
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.runMainWithBoilerplate
+
+/**
+ * Entry point.
+ */
+fun main(args: Array<String>) {
+    executableName = "Ravenizer"
+    log.setConsoleLogLevel(LogLevel.Info)
+
+    runMainWithBoilerplate {
+        val options = RavenizerOptions.parseArgs(args)
+
+        log.i("$executableName started")
+        log.v("Options: $options")
+
+        // Run.
+        Ravenizer(options).run()
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
new file mode 100644
index 0000000..e85e3be
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.ArgIterator
+import com.android.hoststubgen.ArgumentsException
+import com.android.hoststubgen.SetOnce
+import com.android.hoststubgen.ensureFileExists
+import com.android.hoststubgen.log
+
+class RavenizerOptions(
+    /** Input jar file*/
+    var inJar: SetOnce<String> = SetOnce(""),
+
+    /** Output jar file */
+    var outJar: SetOnce<String> = SetOnce(""),
+) {
+    companion object {
+        fun parseArgs(args: Array<String>): RavenizerOptions {
+            val ret = RavenizerOptions()
+            val ai = ArgIterator.withAtFiles(args)
+
+            while (true) {
+                val arg = ai.nextArgOptional()
+                if (arg == null) {
+                    break
+                }
+
+                fun nextArg(): String = ai.nextArgRequired(arg)
+
+                if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+                    continue
+                }
+                try {
+                    when (arg) {
+                        // TODO: Write help
+                        "-h", "--help" -> TODO("Help is not implemented yet")
+
+                        "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
+                        "--out-jar" -> ret.outJar.set(nextArg())
+
+                        else -> throw ArgumentsException("Unknown option: $arg")
+                    }
+                } catch (e: SetOnce.SetMoreThanOnceException) {
+                    throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+                }
+            }
+
+            if (!ret.inJar.isSet) {
+                throw ArgumentsException("Required option missing: --in-jar")
+            }
+            if (!ret.outJar.isSet) {
+                throw ArgumentsException("Required option missing: --out-jar")
+            }
+           return ret
+        }
+    }
+
+    override fun toString(): String {
+        return """
+            RavenizerOptions{
+              inJar=$inJar,
+              outJar=$outJar,
+            }
+            """.trimIndent()
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
new file mode 100644
index 0000000..0018648
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnyAnnotation
+import org.objectweb.asm.Type
+
+val junitTestMethodType = Type.getType(org.junit.Test::class.java)
+val junitRunWithType = Type.getType(org.junit.runner.RunWith::class.java)
+
+val junitTestMethodDescriptor = junitTestMethodType.descriptor
+val junitRunWithDescriptor = junitRunWithType.descriptor
+
+val junitTestMethodDescriptors = setOf<String>(junitTestMethodDescriptor)
+val junitRunWithDescriptors = setOf<String>(junitRunWithDescriptor)
+
+/**
+ * Returns true, if a test looks like it's a test class which needs to be processed.
+ */
+fun isTestLookingClass(classes: ClassNodes, className: String): Boolean {
+    // Similar to  com.android.tradefed.lite.HostUtils.testLoadClass(), except it's more lenient,
+    // and accept non-public and/or abstract classes.
+    // HostUtils also checks "Suppress" or "SuiteClasses" but this one doesn't.
+    // TODO: SuiteClasses may need to be supported.
+
+    val cn = classes.findClass(className) ?: return false
+
+    if (cn.findAnyAnnotation(junitRunWithDescriptors) != null) {
+        return true
+    }
+    cn.methods?.forEach { method ->
+        if (method.findAnyAnnotation(junitTestMethodDescriptors) != null) {
+            return true
+        }
+    }
+    if (cn.superName == null) {
+        return false
+    }
+    return isTestLookingClass(classes, cn.superName)
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
new file mode 100644
index 0000000..c539908
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer.adapter
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.visitors.OPCODE_VERSION
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
+import org.objectweb.asm.ClassVisitor
+
+/**
+ * Class visitor to rewrite the test runner for Ravenwood
+ *
+ * TODO: Implement it.
+ */
+class TestRunnerRewritingAdapter(
+    protected val classes: ClassNodes,
+    nextVisitor: ClassVisitor,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+   companion object {
+       /**
+        * Returns true if a target class is interesting to this adapter.
+        */
+       fun shouldProcess(classes: ClassNodes, className: String): Boolean {
+            return isTestLookingClass(classes, className)
+       }
+    }
+}
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index efa1397..3d7ad0b 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -35,7 +35,7 @@
         "androidx.annotation_annotation",
     ],
     static_libs: [
-        "a11ychecker-protos-java-proto-lite",
+        "accessibility_protos_lite",
         "com_android_server_accessibility_flags_lib",
         "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
     ],
@@ -71,17 +71,6 @@
     aconfig_declarations: "com_android_server_accessibility_flags",
 }
 
-java_library_static {
-    name: "a11ychecker-protos-java-proto-lite",
-    proto: {
-        type: "lite",
-        canonical_path_from_root: false,
-    },
-    srcs: [
-        "java/**/a11ychecker/proto/*.proto",
-    ],
-}
-
 genrule {
     name: "statslog-accessibility-java-gen",
     tools: ["stats-log-api-gen"],
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index f7a59a4b..83f57b2 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -30,7 +30,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.Flags;
-import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
 
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
@@ -58,7 +57,7 @@
     private final PackageManager mPackageManager;
     private final Set<AccessibilityHierarchyCheck> mHierarchyChecks;
     private final ATFHierarchyBuilder mATFHierarchyBuilder;
-    private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>();
+    private final Set<AndroidAccessibilityCheckerResult> mCachedResults = new HashSet<>();
 
     @VisibleForTesting
     final A11yCheckerTimer mTimer = new A11yCheckerTimer();
@@ -85,14 +84,14 @@
      * logging. Returns the check results for the given nodes.
      */
     @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
-    public Set<AccessibilityCheckResultReported> maybeRunA11yChecker(
+    public Set<AndroidAccessibilityCheckerResult> maybeRunA11yChecker(
             List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
             ComponentName a11yServiceComponentName, @UserIdInt int userId) {
         if (!shouldRunA11yChecker()) {
             return Set.of();
         }
 
-        Set<AccessibilityCheckResultReported> allResults = new HashSet<>();
+        Set<AndroidAccessibilityCheckerResult> allResults = new HashSet<>();
         String defaultBrowserName = mPackageManager.getDefaultBrowserPackageNameAsUser(userId);
 
         try {
@@ -104,7 +103,7 @@
                     continue;
                 }
                 List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
-                Set<AccessibilityCheckResultReported> filteredResults =
+                Set<AndroidAccessibilityCheckerResult> filteredResults =
                         AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
                                 sourceEventClassName, mPackageManager, a11yServiceComponentName);
                 allResults.addAll(filteredResults);
@@ -127,7 +126,7 @@
         return checkResults;
     }
 
-    public Set<AccessibilityCheckResultReported> getCachedResults() {
+    public Set<AndroidAccessibilityCheckerResult> getCachedResults() {
         return Collections.unmodifiableSet(mCachedResults);
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
index 1b3ec5a..fa0bb59 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerStatsdLogger.java
@@ -16,10 +16,9 @@
 
 package com.android.server.accessibility.a11ychecker;
 
+import android.text.TextUtils;
 import android.util.Slog;
 
-import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
-
 import java.util.Set;
 
 
@@ -35,11 +34,11 @@
     /**
      * Writes results to statsd.
      */
-    public static void logResults(Set<AccessibilityCheckResultReported> results) {
-        Slog.i(LOG_TAG, String.format("Writing %d AccessibilityCheckResultReported events",
+    public static void logResults(Set<AndroidAccessibilityCheckerResult> results) {
+        Slog.i(LOG_TAG, TextUtils.formatSimple("Writing %d AccessibilityCheckResultReported events",
                 results.size()));
 
-        for (AccessibilityCheckResultReported result : results) {
+        for (AndroidAccessibilityCheckerResult result : results) {
             AccessibilityCheckerStatsLog.write(ATOM_ID,
                     result.getPackageName(),
                     result.getAppVersionCode(),
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index fa0fed2..eb24b02 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -17,6 +17,8 @@
 package com.android.server.accessibility.a11ychecker;
 
 
+import android.accessibility.AccessibilityCheckClass;
+import android.accessibility.AccessibilityCheckResultType;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageInfo;
@@ -25,9 +27,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckClass;
-import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
-import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultType;
 
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
@@ -92,7 +91,7 @@
                             AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK));
     // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto)
 
-    static Set<AccessibilityCheckResultReported> processResults(
+    static Set<AndroidAccessibilityCheckerResult> processResults(
             AccessibilityNodeInfo nodeInfo,
             List<AccessibilityHierarchyCheckResult> checkResults,
             @Nullable String activityClassName,
@@ -103,16 +102,16 @@
         if (nodePath == null) {
             return Set.of();
         }
-        AccessibilityCheckResultReported.Builder builder;
+        AndroidAccessibilityCheckerResult.Builder commonBuilder;
         try {
-            builder = AccessibilityCheckResultReported.newBuilder()
+            commonBuilder = AndroidAccessibilityCheckerResult.newBuilder()
                     .setPackageName(appPackageName)
                     .setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
                     .setUiElementPath(nodePath)
                     .setActivityName(
                             getActivityName(packageManager, appPackageName, activityClassName))
                     .setWindowTitle(getWindowTitle(nodeInfo))
-                    .setSourceComponentName(a11yServiceComponentName.flattenToString())
+                    .setSourceComponentName(a11yServiceComponentName)
                     .setSourceVersionCode(
                             getAppVersionCode(packageManager,
                                     a11yServiceComponentName.getPackageName()));
@@ -126,7 +125,8 @@
                         == AccessibilityCheckResult.AccessibilityCheckResultType.ERROR
                         || checkResult.getType()
                         == AccessibilityCheckResult.AccessibilityCheckResultType.WARNING)
-                .map(checkResult -> builder.setResultCheckClass(
+                .map(checkResult -> new AndroidAccessibilityCheckerResult.Builder(
+                        commonBuilder).setResultCheckClass(
                         getCheckClass(checkResult)).setResultType(
                         getCheckResultType(checkResult)).setResultId(
                         checkResult.getResultId()).build())
@@ -188,9 +188,9 @@
     private static AccessibilityCheckResultType getCheckResultType(
             AccessibilityHierarchyCheckResult checkResult) {
         return switch (checkResult.getType()) {
-            case ERROR -> AccessibilityCheckResultType.ERROR;
-            case WARNING -> AccessibilityCheckResultType.WARNING;
-            default -> AccessibilityCheckResultType.UNKNOWN_RESULT_TYPE;
+            case ERROR -> AccessibilityCheckResultType.ERROR_CHECK_RESULT_TYPE;
+            case WARNING -> AccessibilityCheckResultType.WARNING_CHECK_RESULT_TYPE;
+            default -> AccessibilityCheckResultType.UNKNOWN_CHECK_RESULT_TYPE;
         };
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AndroidAccessibilityCheckerResult.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AndroidAccessibilityCheckerResult.java
new file mode 100644
index 0000000..c9cd9fe
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AndroidAccessibilityCheckerResult.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 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.accessibility.a11ychecker;
+
+import android.accessibility.AccessibilityCheckClass;
+import android.accessibility.AccessibilityCheckResultType;
+import android.content.ComponentName;
+import android.text.TextUtils;
+
+public class AndroidAccessibilityCheckerResult implements Cloneable {
+    // Package name of the app containing the checked View.
+    private String mPackageName;
+    // Version code of the app containing the checked View.
+    private long mAppVersionCode;
+    // The path of the View starting from the root element in the window. Each element is
+    // represented by the View's resource id, when available, or the View's class name.
+    private String mUiElementPath;
+    // Class name of the activity containing the checked View.
+    private String mActivityName;
+    // Title of the window containing the checked View.
+    private String mWindowTitle;
+    // The component name of the app running the AccessibilityService which provided the a11y node.
+    private String mSourceComponentName;
+    // Version code of the app running the AccessibilityService that provided the a11y node.
+    private long mSourceVersionCode;
+    // Class Name of the AccessibilityCheck that produced the result.
+    private AccessibilityCheckClass mResultCheckClass;
+    // Result type of the AccessibilityCheckResult.
+    private AccessibilityCheckResultType mResultType;
+    // Result ID of the AccessibilityCheckResult.
+    private int mResultId;
+
+    static final class Builder {
+        private final AndroidAccessibilityCheckerResult mInstance;
+
+        Builder() {
+            mInstance = new AndroidAccessibilityCheckerResult();
+        }
+
+        Builder(Builder otherBuilder) {
+            mInstance = otherBuilder.mInstance.clone();
+        }
+
+        public Builder setPackageName(String packageName) {
+            mInstance.mPackageName = packageName;
+            return this;
+        }
+
+        public Builder setAppVersionCode(long versionCode) {
+            mInstance.mAppVersionCode = versionCode;
+            return this;
+        }
+
+        public Builder setUiElementPath(String uiElementPath) {
+            mInstance.mUiElementPath = uiElementPath;
+            return this;
+        }
+
+        public Builder setActivityName(String activityName) {
+            mInstance.mActivityName = activityName;
+            return this;
+        }
+
+        public Builder setWindowTitle(String windowTitle) {
+            mInstance.mWindowTitle = windowTitle;
+            return this;
+        }
+
+        public Builder setSourceComponentName(ComponentName componentName) {
+            mInstance.mSourceComponentName = componentName.flattenToString();
+            return this;
+        }
+
+        public Builder setSourceVersionCode(long versionCode) {
+            mInstance.mSourceVersionCode = versionCode;
+            return this;
+        }
+
+        public Builder setResultCheckClass(AccessibilityCheckClass checkClass) {
+            mInstance.mResultCheckClass = checkClass;
+            return this;
+        }
+
+        public Builder setResultType(AccessibilityCheckResultType resultType) {
+            mInstance.mResultType = resultType;
+            return this;
+        }
+
+        public Builder setResultId(int resultId) {
+            mInstance.mResultId = resultId;
+            return this;
+        }
+
+        public AndroidAccessibilityCheckerResult build() {
+            // TODO: assert all fields are set, etc
+            return mInstance;
+        }
+    }
+
+    static Builder newBuilder() {
+        return new Builder();
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public long getAppVersionCode() {
+        return mAppVersionCode;
+    }
+
+    public String getUiElementPath() {
+        return mUiElementPath;
+    }
+
+    public String getActivityName() {
+        return mActivityName;
+    }
+
+    public String getWindowTitle() {
+        return mWindowTitle;
+    }
+
+    public String getSourceComponentName() {
+        return mSourceComponentName;
+    }
+
+    public long getSourceVersionCode() {
+        return mSourceVersionCode;
+    }
+
+    public AccessibilityCheckClass getResultCheckClass() {
+        return mResultCheckClass;
+    }
+
+    public AccessibilityCheckResultType getResultType() {
+        return mResultType;
+    }
+
+    public int getResultId() {
+        return mResultId;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof AndroidAccessibilityCheckerResult)) {
+            return false;
+        }
+        AndroidAccessibilityCheckerResult otherResult = (AndroidAccessibilityCheckerResult) other;
+        return mPackageName.equals(otherResult.mPackageName)
+                && mAppVersionCode == otherResult.mAppVersionCode
+                && mUiElementPath.equals(otherResult.mUiElementPath)
+                && mActivityName.equals(otherResult.mActivityName)
+                && mWindowTitle.equals(otherResult.mWindowTitle)
+                && mSourceComponentName.equals(otherResult.mSourceComponentName)
+                && mSourceVersionCode == otherResult.mSourceVersionCode
+                && mResultCheckClass.equals(otherResult.mResultCheckClass)
+                && mResultType.equals(otherResult.mResultType)
+                && mResultId == otherResult.mResultId;
+    }
+
+    @Override
+    public int hashCode() {
+        return toString().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return TextUtils.formatSimple("%s:%d:%s:%s:%s:%s:%d:%s:%s:%d", mPackageName,
+                mAppVersionCode, mUiElementPath, mActivityName, mWindowTitle, mSourceComponentName,
+                mSourceVersionCode, mResultCheckClass.name(), mResultType.name(), mResultId);
+    }
+
+    @Override
+    public AndroidAccessibilityCheckerResult clone() {
+        try {
+            return (AndroidAccessibilityCheckerResult) super.clone();
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
deleted file mode 100644
index 8beed4a..0000000
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-syntax = "proto2";
-package android.accessibility;
-
-option java_package = "com.android.server.accessibility.a11ychecker";
-option java_outer_classname = "A11yCheckerProto";
-
-// TODO(b/326385939): remove and replace usage with the atom extension proto, when submitted.
-/** Logs the result of an AccessibilityCheck. */
-message AccessibilityCheckResultReported {
-  // Package name of the app containing the checked View.
-  optional string package_name = 1;
-  // Version code of the app containing the checked View.
-  optional int64 app_version_code = 2;
-  // The path of the View starting from the root element in the window. Each element is
-  // represented by the View's resource id, when available, or the View's class name.
-  optional string ui_element_path = 3;
-  // Class name of the activity containing the checked View.
-  optional string activity_name = 4;
-  // Title of the window containing the checked View.
-  optional string window_title = 5;
-  // The flattened component name of the app running the AccessibilityService which provided the a11y node.
-  optional string source_component_name = 6;
-  // Version code of the app running the AccessibilityService that provided the a11y node.
-  optional int64 source_version_code = 7;
-  // Class Name of the AccessibilityCheck that produced the result.
-  optional AccessibilityCheckClass result_check_class = 8;
-  // Result type of the AccessibilityCheckResult.
-  optional AccessibilityCheckResultType result_type = 9;
-  // Result ID of the AccessibilityCheckResult.
-  optional int32 result_id = 10;
-}
-
-/** The AccessibilityCheck class. */
-// LINT.IfChange
-enum AccessibilityCheckClass {
-  UNKNOWN_CHECK = 0;
-  CLASS_NAME_CHECK = 1;
-  CLICKABLE_SPAN_CHECK = 2;
-  DUPLICATE_CLICKABLE_BOUNDS_CHECK = 3;
-  DUPLICATE_SPEAKABLE_TEXT_CHECK = 4;
-  EDITABLE_CONTENT_DESC_CHECK = 5;
-  IMAGE_CONTRAST_CHECK = 6;
-  LINK_PURPOSE_UNCLEAR_CHECK = 7;
-  REDUNDANT_DESCRIPTION_CHECK = 8;
-  SPEAKABLE_TEXT_PRESENT_CHECK = 9;
-  TEXT_CONTRAST_CHECK = 10;
-  TEXT_SIZE_CHECK = 11;
-  TOUCH_TARGET_SIZE_CHECK = 12;
-  TRAVERSAL_ORDER_CHECK = 13;
-}
-// LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java)
-
-/** The type of AccessibilityCheckResult */
-enum AccessibilityCheckResultType {
-  UNKNOWN_RESULT_TYPE = 0;
-  ERROR = 1;
-  WARNING = 2;
-}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 0815384..e84250d 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -309,13 +309,14 @@
      */
     public void registerHealthObserver(PackageHealthObserver observer) {
         synchronized (mLock) {
-            ObserverInternal internalObserver = mAllObservers.get(observer.getName());
+            ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
             if (internalObserver != null) {
                 internalObserver.registeredObserver = observer;
             } else {
-                internalObserver = new ObserverInternal(observer.getName(), new ArrayList<>());
+                internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
+                        new ArrayList<>());
                 internalObserver.registeredObserver = observer;
-                mAllObservers.put(observer.getName(), internalObserver);
+                mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
                 syncState("added new observer");
             }
         }
@@ -342,12 +343,12 @@
     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
             long durationMs) {
         if (packageNames.isEmpty()) {
-            Slog.wtf(TAG, "No packages to observe, " + observer.getName());
+            Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
             return;
         }
         if (durationMs < 1) {
             Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
-                    + observer.getName() + ". Not observing packages " + packageNames);
+                    + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
             durationMs = DEFAULT_OBSERVING_DURATION_MS;
         }
 
@@ -374,14 +375,14 @@
             syncState("observing new packages");
 
             synchronized (mLock) {
-                ObserverInternal oldObserver = mAllObservers.get(observer.getName());
+                ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
                 if (oldObserver == null) {
-                    Slog.d(TAG, observer.getName() + " started monitoring health "
+                    Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
                             + "of packages " + packageNames);
-                    mAllObservers.put(observer.getName(),
-                            new ObserverInternal(observer.getName(), packages));
+                    mAllObservers.put(observer.getUniqueIdentifier(),
+                            new ObserverInternal(observer.getUniqueIdentifier(), packages));
                 } else {
-                    Slog.d(TAG, observer.getName() + " added the following "
+                    Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
                             + "packages to monitor " + packageNames);
                     oldObserver.updatePackagesLocked(packages);
                 }
@@ -405,9 +406,9 @@
     public void unregisterHealthObserver(PackageHealthObserver observer) {
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
-                mAllObservers.remove(observer.getName());
+                mAllObservers.remove(observer.getUniqueIdentifier());
             }
-            syncState("unregistering observer: " + observer.getName());
+            syncState("unregistering observer: " + observer.getUniqueIdentifier());
         });
     }
 
@@ -781,7 +782,7 @@
          * Identifier for the observer, should not change across device updates otherwise the
          * watchdog may drop observing packages with the old name.
          */
-        String getName();
+        String getUniqueIdentifier();
 
         /**
          * An observer will not be pruned if this is set, even if the observer is not explicitly
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c2cb5e9..bba97fa 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -917,7 +917,7 @@
         }
 
         @Override
-        public String getName() {
+        public String getUniqueIdentifier() {
             return NAME;
         }
 
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 44c8d1c..28a0b28 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -231,7 +231,7 @@
         if (!isExternalDisplayAllowed()) {
             Slog.w(TAG, "handleExternalDisplayConnectedLocked: External display can not be used"
                                 + " because it is currently not allowed.");
-            mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
+            mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
             return;
         }
 
@@ -329,7 +329,7 @@
 
         if (!isExternalDisplayAllowed()) {
             Slog.w(TAG, "External display is currently not allowed and is getting disabled.");
-            mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
+            mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
         }
 
         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, /*enabled=*/ false);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 154710f..81c30dd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1225,11 +1225,15 @@
         boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message);
         // Set System Audio Mode according to TV's settings.
         // Handle <System Audio Mode Status> here only when
-        // SystemAudioAutoInitiationAction timeout
+        // SystemAudioAutoInitiationAction timeout.
+        // If AVR reports SAM on and it is in standby, the action SystemAudioActionFromTv
+        // triggers a <SAM Request> that will wake-up the AVR.
         HdmiDeviceInfo avr = getAvrDeviceInfo();
         if (avr == null) {
             setSystemAudioMode(false);
-        } else if (avrSystemAudioMode != tvSystemAudioMode) {
+        } else if (avrSystemAudioMode != tvSystemAudioMode
+                    || (avrSystemAudioMode && avr.getDevicePowerStatus()
+                        == HdmiControlManager.POWER_STATUS_STANDBY)) {
             addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(),
                     tvSystemAudioMode, null));
         } else {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
index 56e538b..028637b 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -16,7 +16,9 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
@@ -89,8 +91,13 @@
 
         // If System Audio Control feature is enabled, turn on system audio mode when new AVR is
         // detected. Otherwise, turn off system audio mode.
+        // If AVR reports SAM on and it is in standby, the action SystemAudioActionFromTv
+        // triggers a <SAM Request> that will wake-up the AVR.
         boolean targetSystemAudioMode = tv().isSystemAudioControlFeatureEnabled();
-        if (currentSystemAudioMode != targetSystemAudioMode) {
+        if (currentSystemAudioMode != targetSystemAudioMode
+                || (currentSystemAudioMode && tv().getAvrDeviceInfo() != null
+                && tv().getAvrDeviceInfo().getDevicePowerStatus()
+                == HdmiControlManager.POWER_STATUS_STANDBY)) {
             // Start System Audio Control feature actions only if necessary.
             addAndStartAction(
                     new SystemAudioActionFromTv(tv(), mAvrAddress, targetSystemAudioMode, null));
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 2c13bd0..18d2390 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -104,6 +104,54 @@
           "include-filter": "android.appsecurity.cts.EphemeralTest#testGetSearchableInfo"
         }
       ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJInstallationTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUninstallationTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
     }
   ],
   "presubmit-large":[
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index aa56e8d..934feb3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3352,6 +3352,12 @@
             mConsumedKeysForDevice.put(deviceId, consumedKeys);
         }
 
+        // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
+        if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode))
+                && shouldInterceptShortcuts(focusedToken)) {
+            return keyNotConsumed;
+        }
+
         if (interceptSystemKeysAndShortcuts(focusedToken, event)
                 && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
             consumedKeys.add(keyCode);
@@ -3416,7 +3422,7 @@
                 return handleHomeShortcuts(focusedToken, event);
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
-                    showRecentApps(false /* triggeredFromAltTab */);
+                    toggleRecentApps();
                     notifyKeyGestureCompleted(event,
                             KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                 }
@@ -3842,6 +3848,15 @@
         return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
+    private boolean shouldInterceptShortcuts(IBinder focusedToken) {
+        KeyInterceptionInfo info =
+                mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+        boolean hasInterceptWindowFlag = (info.layoutParamsPrivateFlags
+                & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
+        return hasInterceptWindowFlag && mButtonOverridePermissionChecker.canAppOverrideSystemKey(
+                mContext, info.windowOwnerUid);
+    }
+
     /**
      * In this function, we check whether a system key should be sent to the application. We also
      * detect the key gesture on this key, even if the key will be sent to the app. The gesture
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 1ca267e99..89fa9b6 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -304,12 +304,22 @@
     @Override
     public void onStart() {
         if (getPowerStatsHal().isInitialized()) {
-            mPowerStatsInternal = new LocalService();
-            publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
+            publishLocalService(PowerStatsInternal.class, getPowerStatsInternal());
         }
         publishBinderService(Context.POWER_STATS_SERVICE, mService);
     }
 
+    /**
+     * Returns the PowerStatsInternal associated with this service, maybe creating it if needed.
+     */
+    @VisibleForTesting
+    public PowerStatsInternal getPowerStatsInternal() {
+        if (mPowerStatsInternal == null) {
+            mPowerStatsInternal = new LocalService();
+        }
+        return mPowerStatsInternal;
+    }
+
     private void onSystemServicesReady() {
         mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
         mDeviceConfigListener.startListening();
@@ -456,7 +466,13 @@
 
     private void getEnergyConsumedAsync(CompletableFuture<EnergyConsumerResult[]> future,
             int[] energyConsumerIds) {
-        EnergyConsumerResult[] results = getPowerStatsHal().getEnergyConsumed(energyConsumerIds);
+        EnergyConsumerResult[] results;
+        try {
+            results = getPowerStatsHal().getEnergyConsumed(energyConsumerIds);
+        } catch (Exception e) {
+            future.completeExceptionally(e);
+            return;
+        }
 
         // STOPSHIP(253292374): Remove once missing EnergyConsumer results issue is resolved.
         EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
@@ -523,12 +539,20 @@
 
     private void getStateResidencyAsync(CompletableFuture<StateResidencyResult[]> future,
             int[] powerEntityIds) {
-        future.complete(getPowerStatsHal().getStateResidency(powerEntityIds));
+        try {
+            future.complete(getPowerStatsHal().getStateResidency(powerEntityIds));
+        } catch (Exception e) {
+            future.completeExceptionally(e);
+        }
     }
 
     private void readEnergyMeterAsync(CompletableFuture<EnergyMeasurement[]> future,
             int[] channelIds) {
-        future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
+        try {
+            future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
+        } catch (Exception e) {
+            future.completeExceptionally(e);
+        }
     }
 
     private static class PowerMonitorState {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 4f28e02..e91097c 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.rollback;
 
+import static android.content.pm.Flags.provideInfoOfApkInApex;
+
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -23,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
@@ -230,7 +233,7 @@
 
 
     @Override
-    public String getName() {
+    public String getUniqueIdentifier() {
         return NAME;
     }
 
@@ -486,19 +489,40 @@
      */
     @AnyThread
     private boolean isModule(String packageName) {
-        // Check if the package is an APK inside an APEX. If it is, use the parent APEX package when
-        // querying PackageManager.
-        String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
-                packageName);
-        if (apexPackageName != null) {
-            packageName = apexPackageName;
-        }
-
         PackageManager pm = mContext.getPackageManager();
-        try {
-            return pm.getModuleInfo(packageName, 0) != null;
-        } catch (PackageManager.NameNotFoundException ignore) {
-            return false;
+
+        if (Flags.refactorCrashrecovery() && provideInfoOfApkInApex()) {
+            // Check if the package is listed among the system modules.
+            boolean isApex = false;
+            try {
+                isApex = (pm.getModuleInfo(packageName, 0 /* flags */) != null);
+            } catch (PackageManager.NameNotFoundException e) {
+                //pass
+            }
+
+            // Check if the package is an APK inside an APEX.
+            boolean isApkInApex = false;
+            try {
+                final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+                isApkInApex = (pkg.getApexPackageName() != null);
+            } catch (PackageManager.NameNotFoundException e) {
+                // pass
+            }
+            return isApex || isApkInApex;
+        } else {
+            // Check if the package is an APK inside an APEX. If it is, use the parent APEX package
+            // when querying PackageManager.
+            String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
+                    packageName);
+            if (apexPackageName != null) {
+                packageName = apexPackageName;
+            }
+
+            try {
+                return pm.getModuleInfo(packageName, 0) != null;
+            } catch (PackageManager.NameNotFoundException ignore) {
+                return false;
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6721893..c543b6d 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
 import static android.media.AudioManager.DEVICE_NONE;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
+import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -805,6 +806,19 @@
         }
     }
 
+    private boolean isServiceSingleUser(ComponentName component) {
+        try {
+            ServiceInfo serviceInfo = getContext().getPackageManager()
+                    .getServiceInfo(component, 0);
+            // Check if the single-user flag is present
+            return (serviceInfo.flags & ServiceInfo.FLAG_SINGLE_USER) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Handle the case where the service is not found
+            Slog.e(TAG, "Service not found: " + component, e);
+            return false;
+        }
+    }
+
     @GuardedBy("mLock")
     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
             String inputId, int userId) {
@@ -2840,6 +2854,26 @@
         }
 
         @Override
+        public int getClientUserId(String sessionId) {
+            ensureTunerResourceAccessPermission();
+            int clientUserId = TvInputManager.UNKNOWN_CLIENT_USER_ID;
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        clientUserId = getClientUserIdLocked(sessionId);
+                    } catch (ClientUserIdNotFoundException e) {
+                        Slog.e(TAG, "error in getClientUserId", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return clientUserId;
+        }
+
+        @Override
         public int getClientPriority(int useCase, String sessionId) {
             ensureTunerResourceAccessPermission();
             final int callingPid = Binder.getCallingPid();
@@ -2924,6 +2958,16 @@
             return mSessionIdToSessionStateMap.get(sessionId).callingPid;
         }
 
+        @GuardedBy("mLock")
+        private int getClientUserIdLocked(String sessionId) throws ClientUserIdNotFoundException {
+            SessionState sessionState = mSessionIdToSessionStateMap.get(sessionId);
+            if (sessionState == null) {
+                throw new ClientUserIdNotFoundException(
+                        "Client UserId not found with sessionId " + sessionId);
+            }
+            return sessionState.userId;
+        }
+
         private void ensureTunerResourceAccessPermission() {
             if (mContext.checkCallingPermission(
                     android.Manifest.permission.TUNER_RESOURCE_ACCESS)
@@ -3495,11 +3539,15 @@
                     "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
                             + ")");
         }
+        int bindUserId = userId;
+        if (kidsModeTvdbSharing() && isServiceSingleUser(serviceState.component)) {
+            bindUserId = UserHandle.USER_SYSTEM;
+        }
         Intent i =
                 new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
         serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                new UserHandle(userId));
+                new UserHandle(bindUserId));
         if (!serviceState.bound) {
             Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
             mContext.unbindService(serviceState.connection);
@@ -4700,4 +4748,10 @@
             super(name);
         }
     }
+
+    private static class ClientUserIdNotFoundException extends IllegalArgumentException {
+        ClientUserIdNotFoundException(String name) {
+            super(name);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 37e4449..3dba57f 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -55,11 +55,10 @@
 
         Change() {}
 
-        void copyFrom(@NonNull Change other) {
+        Change(@NonNull Change other) {
             mAlpha = other.mAlpha;
             mBlurRadius = other.mBlurRadius;
             mDimmingContainer = other.mDimmingContainer;
-            mGeometryParent = other.mGeometryParent;
             mRelativeLayer = other.mRelativeLayer;
         }
 
@@ -84,8 +83,8 @@
         }
     }
 
-    private final Change mCurrentProperties = new Change();
-    private final Change mRequestedProperties = new Change();
+    private Change mCurrentProperties = new Change();
+    private Change mRequestedProperties = new Change();
     private AnimationSpec mAlphaAnimationSpec;
 
     private final AnimationAdapterFactory mAnimationAdapterFactory;
@@ -129,7 +128,7 @@
                     + "call adjustRelativeLayer?");
             return;
         }
-        if (mRequestedProperties.mDimmingContainer.getSurfaceControl() == null) {
+        if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
             Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
                     + "does not have a surface");
             dim.remove(t);
@@ -155,35 +154,35 @@
                         "%s skipping animation and directly setting alpha=%f, blur=%d",
                         dim, mRequestedProperties.mAlpha,
                         mRequestedProperties.mBlurRadius);
-                mCurrentProperties.copyFrom(mRequestedProperties);
-                setCurrentAlphaBlur(dim.mDimSurface, t);
+                setAlphaBlur(dim.mDimSurface, mRequestedProperties.mAlpha,
+                        mRequestedProperties.mBlurRadius, t);
                 dim.mSkipAnimation = false;
             } else {
-                Change startProperties = mCurrentProperties;
-                mCurrentProperties.copyFrom(mRequestedProperties);
-                startAnimation(t, dim, startProperties, mRequestedProperties);
+                startAnimation(t, dim);
             }
+
         } else if (!dim.isDimming()) {
             // We are not dimming, so we tried the exit animation but the alpha is already 0,
             // therefore, let's just remove this surface
             dim.remove(t);
         }
+        mCurrentProperties = new Change(mRequestedProperties);
     }
 
     private void startAnimation(
-            @NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim,
-            @NonNull Change from, @NonNull Change to) {
+            @NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim) {
         ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
-        mAlphaAnimationSpec = getRequestedAnimationSpec(from, to);
+        mAlphaAnimationSpec = getRequestedAnimationSpec();
         mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
                 dim.mHostContainer.mWmService.mSurfaceAnimationRunner);
 
-        float targetAlpha = to.mAlpha;
+        float targetAlpha = mRequestedProperties.mAlpha;
+        int targetBlur = mRequestedProperties.mBlurRadius;
 
         mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t,
                 ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> {
                     synchronized (dim.mHostContainer.mWmService.mGlobalLock) {
-                        setCurrentAlphaBlur(dim.mDimSurface, t);
+                        setAlphaBlur(dim.mDimSurface, targetAlpha, targetBlur, t);
                         if (targetAlpha == 0f && !dim.isDimming()) {
                             dim.remove(t);
                         }
@@ -208,15 +207,15 @@
     }
 
     @NonNull
-    private static AnimationSpec getRequestedAnimationSpec(Change from, Change to) {
-        final float startAlpha = Math.max(from.mAlpha, 0f);
-        final int startBlur = Math.max(from.mBlurRadius, 0);
-        long duration = (long) (getDimDuration(to.mDimmingContainer)
-                * Math.abs(to.mAlpha - startAlpha));
+    private AnimationSpec getRequestedAnimationSpec() {
+        final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+        final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+        long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+                * Math.abs(mRequestedProperties.mAlpha - startAlpha));
 
         final AnimationSpec spec =  new AnimationSpec(
-                new AnimationSpec.AnimationExtremes<>(startAlpha, to.mAlpha),
-                new AnimationSpec.AnimationExtremes<>(startBlur, to.mBlurRadius),
+                new AnimationSpec.AnimationExtremes<>(startAlpha, mRequestedProperties.mAlpha),
+                new AnimationSpec.AnimationExtremes<>(startBlur, mRequestedProperties.mBlurRadius),
                 duration
         );
         ProtoLog.v(WM_DEBUG_DIMMER, "Dim animation requested: %s", spec);
@@ -226,7 +225,7 @@
     /**
      * Change the geometry and relative parent of this dim layer
      */
-    static void reparent(@NonNull SurfaceControl dimLayer,
+    void reparent(@NonNull SurfaceControl dimLayer,
                   @Nullable SurfaceControl newGeometryParent,
                   @NonNull SurfaceControl relativeParent,
                   int relativePosition,
@@ -241,16 +240,17 @@
         }
     }
 
-    void setCurrentAlphaBlur(@NonNull SurfaceControl sc, @NonNull SurfaceControl.Transaction t) {
+    void setAlphaBlur(@NonNull SurfaceControl sc, float alpha, int blur,
+                      @NonNull SurfaceControl.Transaction t) {
         try {
-            t.setAlpha(sc, mCurrentProperties.mAlpha);
-            t.setBackgroundBlurRadius(sc, mCurrentProperties.mBlurRadius);
+            t.setAlpha(sc, alpha);
+            t.setBackgroundBlurRadius(sc, blur);
         } catch (NullPointerException e) {
             Log.w(TAG , "Tried to change look of dim " + sc + " after remove",  e);
         }
     }
 
-    private static long getDimDuration(@NonNull WindowContainer<?> container) {
+    private long getDimDuration(@NonNull WindowContainer<?> container) {
         // Use the same duration as the animation on the WindowContainer
         AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
         final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 648f6bd..129931e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -857,8 +857,8 @@
             return false;
         }
         if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
-                && !mImeLayeringTarget.isRequestedVisible(ime())
-                && !mImeLayeringTarget.isVisibleRequested()) {
+                && !(mImeLayeringTarget.isRequestedVisible(ime())
+                        && mImeLayeringTarget.isVisibleRequested())) {
             return false;
         }
 
@@ -3496,10 +3496,7 @@
      */
     void collectDisplayChange(@NonNull Transition transition) {
         if (!mLastHasContent) return;
-        if (!transition.isCollecting()) {
-            throw new IllegalArgumentException("Can only collect display change if transition"
-                    + " is collecting");
-        }
+        if (!transition.isCollecting()) return;
         if (!transition.mParticipants.contains(this)) {
             transition.collect(this);
             startAsyncRotationIfNeeded();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ef25eda..1d2b693 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -408,6 +408,10 @@
      */
     @Nullable
     Transition getCollectingTransition() {
+        if (mCollectingTransition != null && !mCollectingTransition.isCollecting()) {
+            Slog.wtfStack(TAG, "Collecting Transition (#" + mCollectingTransition.getSyncId()
+                    + ") is not collecting. state=" + mCollectingTransition.getState());
+        }
         return mCollectingTransition;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bdb1d43..87c0084 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -32,6 +32,7 @@
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PC;
@@ -9024,14 +9025,7 @@
         }
         clearPointerDownOutsideFocusRunnable();
 
-        // For embedded activity that is showing side-by-side with another activity, delay
-        // handling the touch-outside event to prevent focus rapid changes back-n-forth.
-        // Otherwise, handle the touch-outside event directly.
-        final WindowState w = t.getWindowState();
-        final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
-        if (mFocusedInputTarget != t && mFocusedInputTarget != null
-                && activity != null && activity.isEmbedded()
-                && activity.getTaskFragment().getAdjacentTaskFragment() != null) {
+        if (shouldDelayTouchOutside(t)) {
             mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
             mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
         } else if (!fromHandler) {
@@ -9044,6 +9038,33 @@
         }
     }
 
+    private boolean shouldDelayTouchOutside(InputTarget t) {
+        final WindowState w = t.getWindowState();
+        final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
+        final Task task = w != null ? w.getRootTask() : null;
+
+        final boolean isInputTargetNotFocused =
+                mFocusedInputTarget != t && mFocusedInputTarget != null;
+        if (!isInputTargetNotFocused) {
+            return false;
+        }
+
+        // For embedded activity that is showing side-by-side with another activity, delay
+        // handling the touch-outside event to prevent focus rapid changes back-n-forth.
+        final boolean shouldDelayTouchForEmbeddedActivity = activity != null
+                && activity.isEmbedded()
+                && activity.getTaskFragment().getAdjacentTaskFragment() != null;
+
+        // For cases when there are multiple freeform windows where non-top windows are blocking
+        // the gesture zones, delay handling the touch-outside event to prevent refocusing the
+        // the non-top windows during the gesture.
+        final boolean shouldDelayTouchForFreeform =
+                task != null && task.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+
+        // If non of the above cases are true, handle the touch-outside event directly.
+        return shouldDelayTouchForEmbeddedActivity || shouldDelayTouchForFreeform;
+    }
+
     private void handlePointerDownOutsideFocus(InputTarget t) {
         synchronized (mGlobalLock) {
             if (mPointerDownOutsideFocusRunnable != null
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index dab3978..8332b8b 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -46,12 +46,12 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.art.ArtManagerLocal;
+import com.android.server.profcollect.Utils;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.util.Arrays;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -280,11 +280,7 @@
             return;
         }
 
-        // Sample for a fraction of app launches.
-        int traceFrequency = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
-                "applaunch_trace_freq", 2);
-        int randomNum = ThreadLocalRandom.current().nextInt(100);
-        if (randomNum < traceFrequency) {
+        if (Utils.withFrequency("applaunch_trace_freq", 2)) {
             BackgroundThread.get().getThreadHandler().post(() -> {
                 try {
                     mIProfcollect.trace_system("applaunch");
@@ -318,12 +314,7 @@
         if (mIProfcollect == null) {
             return;
         }
-        // Sample for a fraction of dex2oat runs.
-        final int traceFrequency =
-            DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
-                "dex2oat_trace_freq", 25);
-        int randomNum = ThreadLocalRandom.current().nextInt(100);
-        if (randomNum < traceFrequency) {
+        if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
             // Dex2oat could take a while before it starts. Add a short delay before start tracing.
             BackgroundThread.get().getThreadHandler().postDelayed(() -> {
                 try {
@@ -393,27 +384,22 @@
                 if (Arrays.asList(cameraSkipPackages).contains(packageId)) {
                     return;
                 }
-                // Sample for a fraction of camera events.
-                final int traceFrequency =
-                        DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
-                        "camera_trace_freq", 10);
-                int randomNum = ThreadLocalRandom.current().nextInt(100);
-                if (randomNum >= traceFrequency) {
-                    return;
-                }
-                final int traceDuration = 5000;
-                final String traceTag = "camera";
-                BackgroundThread.get().getThreadHandler().post(() -> {
-                    if (mIProfcollect == null) {
-                        return;
-                    }
-                    try {
-                        mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
+                if (Utils.withFrequency("camera_trace_freq", 10)) {
+                    final int traceDuration = 5000;
+                    final String traceTag = "camera";
+                    BackgroundThread.get().getThreadHandler().post(() -> {
+                        if (mIProfcollect == null) {
+                            return;
+                        }
+                        try {
+                            mIProfcollect.trace_process(traceTag,
+                                "android.hardware.camera.provider",
                                 traceDuration);
-                    } catch (RemoteException e) {
-                        Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
-                    }
-                });
+                        } catch (RemoteException e) {
+                            Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
+                        }
+                    });
+                }
             }
         }, null);
     }
diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java
new file mode 100644
index 0000000..d5ef14c
--- /dev/null
+++ b/services/profcollect/src/com/android/server/profcollect/Utils.java
@@ -0,0 +1,32 @@
+/**
+ * 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.profcollect;
+
+import android.provider.DeviceConfig;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public final class Utils {
+
+  public static boolean withFrequency(String configName, int defaultFrequency) {
+        int threshold = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, configName, defaultFrequency);
+        int randomNum = ThreadLocalRandom.current().nextInt(100);
+        return randomNum < threshold;
+    }
+
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 82acaf8..f728168 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -258,6 +258,7 @@
 
         when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
         mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+        mHandler.flush();
         verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
         verify(mMockedDisplayNotificationManager, times(2))
                 .onHighTemperatureExternalDisplayNotAllowed();
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 53e3143..115cdf6 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -74,6 +74,8 @@
 import java.util.Arrays;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
 /**
@@ -221,6 +223,7 @@
     };
 
     public static final class TestPowerStatsHALWrapper implements IPowerStatsHALWrapper {
+        public RuntimeException exception;
         public EnergyConsumerResult[] energyConsumerResults;
         public EnergyMeasurement[] energyMeasurements;
 
@@ -243,6 +246,9 @@
 
         @Override
         public StateResidencyResult[] getStateResidency(int[] powerEntityIds) {
+            if (exception != null) {
+                throw exception;
+            }
             StateResidencyResult[] stateResidencyResultList =
                     new StateResidencyResult[POWER_ENTITY_COUNT];
             for (int i = 0; i < stateResidencyResultList.length; i++) {
@@ -294,6 +300,9 @@
 
         @Override
         public EnergyConsumerResult[] getEnergyConsumed(int[] energyConsumerIds) {
+            if (exception != null) {
+                throw exception;
+            }
             return energyConsumerResults;
         }
 
@@ -322,6 +331,9 @@
 
         @Override
         public EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
+            if (exception != null) {
+                throw exception;
+            }
             return energyMeasurements;
         }
 
@@ -1222,4 +1234,31 @@
         assertThrows(NullPointerException.class, () -> iPowerStatsService.getPowerMonitorReadings(
                 new int[] {0}, null));
     }
+
+    @Test
+    public void getEnergyConsumedAsync_halException() {
+        mPowerStatsHALWrapper.exception = new IllegalArgumentException();
+        CompletableFuture<EnergyConsumerResult[]> future =
+                mService.getPowerStatsInternal().getEnergyConsumedAsync(new int[]{1});
+        ExecutionException exception = assertThrows(ExecutionException.class, future::get);
+        assertThat(exception.getCause()).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void getStateResidencyAsync_halException() {
+        mPowerStatsHALWrapper.exception = new IllegalArgumentException();
+        CompletableFuture<StateResidencyResult[]> future =
+                mService.getPowerStatsInternal().getStateResidencyAsync(new int[]{1});
+        ExecutionException exception = assertThrows(ExecutionException.class, future::get);
+        assertThat(exception.getCause()).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void readEnergyMeterAsync_halException() {
+        mPowerStatsHALWrapper.exception = new IllegalArgumentException();
+        CompletableFuture<EnergyMeasurement[]> future =
+                mService.getPowerStatsInternal().readEnergyMeterAsync(new int[]{1});
+        ExecutionException exception = assertThrows(ExecutionException.class, future::get);
+        assertThat(exception.getCause()).isInstanceOf(IllegalArgumentException.class);
+    }
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 701c350..ace4b15 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -36,8 +36,8 @@
         "-Werror",
     ],
     static_libs: [
-        "a11ychecker-protos-java-proto-lite",
         "aatf",
+        "accessibility_protos_lite",
         "cts-input-lib",
         "frameworks-base-testutils",
         "services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
index c1b3929..5ee86ff 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
@@ -23,7 +23,7 @@
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER;
-import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
+import static com.android.server.accessibility.a11ychecker.TestUtils.createResult;
 import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -32,6 +32,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.accessibility.AccessibilityCheckClass;
+import android.accessibility.AccessibilityCheckResultType;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.DisableFlags;
@@ -112,19 +114,19 @@
                         .setViewIdResourceName("node2")
                         .build();
 
-        Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
+        Set<AndroidAccessibilityCheckerResult> results =
                 mAccessibilityCheckerManager.maybeRunA11yChecker(
                         List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
 
         assertThat(results).containsExactly(
-                createAtom(/*viewIdResourceName=*/ "node1", TEST_ACTIVITY_NAME,
-                        A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
-                        A11yCheckerProto.AccessibilityCheckResultType.ERROR, /*resultId=*/ 2),
-                createAtom(/*viewIdResourceName=*/ "node2", TEST_ACTIVITY_NAME,
-                        A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
-                        A11yCheckerProto.AccessibilityCheckResultType.ERROR, /*resultId=*/ 2)
+                createResult(/*viewIdResourceName=*/ "node1", TEST_ACTIVITY_NAME,
+                        AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+                        AccessibilityCheckResultType.ERROR_CHECK_RESULT_TYPE, /*resultId=*/ 2),
+                createResult(/*viewIdResourceName=*/ "node2", TEST_ACTIVITY_NAME,
+                        AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+                        AccessibilityCheckResultType.ERROR_CHECK_RESULT_TYPE, /*resultId=*/ 2)
         );
     }
 
@@ -137,7 +139,7 @@
                         .setViewIdResourceName("node1")
                         .build();
 
-        Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
+        Set<AndroidAccessibilityCheckerResult> results =
                 mAccessibilityCheckerManager.maybeRunA11yChecker(
                         List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
@@ -158,16 +160,17 @@
                         .setViewIdResourceName("node1")
                         .build();
 
-        Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
+        Set<AndroidAccessibilityCheckerResult> results =
                 mAccessibilityCheckerManager.maybeRunA11yChecker(
                         List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
 
         assertThat(results).containsExactly(
-                createAtom(/*viewIdResourceName=*/ "node1", TEST_ACTIVITY_NAME,
-                        A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
-                        A11yCheckerProto.AccessibilityCheckResultType.ERROR, /*resultId=*/ 2)
+                createResult(/*viewIdResourceName=*/ "node1", TEST_ACTIVITY_NAME,
+                        AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+                        AccessibilityCheckResultType.ERROR_CHECK_RESULT_TYPE, /*resultId=*/
+                        2)
         );
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 5b4e72e..4ec2fb9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -21,13 +21,15 @@
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
 import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
-import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
+import static com.android.server.accessibility.a11ychecker.TestUtils.createResult;
 import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.when;
 
+import android.accessibility.AccessibilityCheckClass;
+import android.accessibility.AccessibilityCheckResultType;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -87,7 +89,7 @@
                         AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5,
                         null);
 
-        Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+        Set<AndroidAccessibilityCheckerResult> results =
                 AccessibilityCheckerUtils.processResults(
                         mockNodeInfo,
                         List.of(result1, result2, result3, result4),
@@ -96,13 +98,13 @@
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME));
 
-        assertThat(atoms).containsExactly(
-                createAtom("TargetNode", "",
-                        A11yCheckerProto.AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK,
-                        A11yCheckerProto.AccessibilityCheckResultType.WARNING, 1),
-                createAtom("TargetNode", "",
-                        A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
-                        A11yCheckerProto.AccessibilityCheckResultType.ERROR, 2)
+        assertThat(results).containsExactly(
+                createResult("TargetNode", "",
+                        AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK,
+                        AccessibilityCheckResultType.WARNING_CHECK_RESULT_TYPE, 1),
+                createResult("TargetNode", "",
+                        AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+                        AccessibilityCheckResultType.ERROR_CHECK_RESULT_TYPE, 2)
         );
     }
 
@@ -126,7 +128,7 @@
                         TouchTargetSizeCheck.class,
                         AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
 
-        Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+        Set<AndroidAccessibilityCheckerResult> results =
                 AccessibilityCheckerUtils.processResults(
                         mockNodeInfo,
                         List.of(result1, result2),
@@ -135,7 +137,7 @@
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME));
 
-        assertThat(atoms).isEmpty();
+        assertThat(results).isEmpty();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
index acf64b6..8e0b2ed 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -20,6 +20,8 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.when;
 
+import android.accessibility.AccessibilityCheckClass;
+import android.accessibility.AccessibilityCheckResultType;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
@@ -90,20 +92,20 @@
         return accessibilityEvent;
     }
 
-    static A11yCheckerProto.AccessibilityCheckResultReported createAtom(
+    static AndroidAccessibilityCheckerResult createResult(
             String viewIdResourceName,
             String activityName,
-            A11yCheckerProto.AccessibilityCheckClass checkClass,
-            A11yCheckerProto.AccessibilityCheckResultType resultType,
+            AccessibilityCheckClass checkClass,
+            AccessibilityCheckResultType resultType,
             int resultId) {
-        return A11yCheckerProto.AccessibilityCheckResultReported.newBuilder()
+        return AndroidAccessibilityCheckerResult.newBuilder()
                 .setPackageName(TEST_APP_PACKAGE_NAME)
                 .setAppVersionCode(TEST_APP_VERSION_CODE)
                 .setUiElementPath(TEST_APP_PACKAGE_NAME + ":" + viewIdResourceName)
                 .setWindowTitle(TEST_WINDOW_TITLE)
                 .setActivityName(activityName)
                 .setSourceComponentName(new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
-                        TEST_A11Y_SERVICE_CLASS_NAME).flattenToString())
+                        TEST_A11Y_SERVICE_CLASS_NAME))
                 .setSourceVersionCode(TEST_A11Y_SERVICE_SOURCE_VERSION_CODE)
                 .setResultCheckClass(checkClass)
                 .setResultType(resultType)
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 2b93ccb..a7e8a00 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2148,6 +2148,40 @@
                 .hasSize(1);
     }
 
+
+    @Test
+    public void handleReportAudioStatus_SamOnAvrStandby_startSystemAudioActionFromTv() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
+                HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        HdmiCecMessage reportPowerStatus =
+                HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_AUDIO_SYSTEM, ADDR_TV,
+                        HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mNativeWrapper.onCecMessage(reportPowerStatus);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).hasSize(0);
+
+        HdmiCecFeatureAction systemAudioAutoInitiationAction =
+                new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM);
+        mHdmiCecLocalDeviceTv.addAndStartAction(systemAudioAutoInitiationAction);
+        HdmiCecMessage reportSystemAudioMode =
+                HdmiCecMessageBuilder.buildReportSystemAudioMode(
+                        ADDR_AUDIO_SYSTEM,
+                        mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
+                        true);
+        mHdmiControlService.handleCecCommand(reportSystemAudioMode);
+        mTestLooper.dispatchAll();
+
+        // SAM must be on; ARC must be off
+        assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).hasSize(1);
+    }
+
     protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
         MockTvDevice(HdmiControlService service) {
             super(service);
diff --git a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
new file mode 100644
index 0000000..b979335
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
@@ -0,0 +1,96 @@
+/*
+ * 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.policy;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.KeyInterceptionInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Testing {@link PhoneWindowManager} functionality of letting app intercepting key events
+ * containing META.
+ */
+@SmallTest
+public class MetaKeyEventsInterceptionTests extends ShortcutKeyTestBase {
+
+    private static final List<KeyEvent> META_KEY_EVENTS = Arrays.asList(
+            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
+            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
+            new KeyEvent(/* downTime= */ 0, /* eventTime= */
+                    0, /* action= */ 0, /* code= */ 0, /* repeat= */ 0,
+                    /* metaState= */ KeyEvent.META_META_ON));
+
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager();
+    }
+
+    @Test
+    public void doesntInterceptMetaKeyEvents_whenWindowAskedForIt() {
+        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
+        setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
+
+        META_KEY_EVENTS.forEach(keyEvent -> {
+            assertKeyInterceptionResult(keyEvent, /* intercepted= */ false);
+        });
+    }
+
+    @Test
+    public void interceptsMetaKeyEvents_whenWindowDoesntHaveFlagSet() {
+        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
+        setWindowKeyInterceptionWithPrivateFlags(0);
+
+        META_KEY_EVENTS.forEach(keyEvent -> {
+            assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
+        });
+    }
+
+    @Test
+    public void interceptsMetaKeyEvents_whenWindowDoesntHavePermission() {
+        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ false);
+        setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
+
+        META_KEY_EVENTS.forEach(keyEvent -> {
+            assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
+        });
+    }
+
+    private void setWindowKeyInterceptionWithPrivateFlags(int privateFlags) {
+        KeyInterceptionInfo info = new KeyInterceptionInfo(
+                WindowManager.LayoutParams.TYPE_APPLICATION, privateFlags, "title", 0);
+        mPhoneWindowManager.overrideWindowKeyInterceptionInfo(info);
+    }
+
+    private void assertKeyInterceptionResult(KeyEvent keyEvent, boolean intercepted) {
+        long result = mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent);
+        int expected = intercepted ? -1 : 0;
+        assertThat(result).isEqualTo(expected);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 43b065d..79c7ac1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -52,6 +52,7 @@
 import static org.mockito.Mockito.description;
 import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
@@ -613,6 +614,10 @@
                 .when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
     }
 
+    void overrideWindowKeyInterceptionInfo(KeyInterceptionInfo info) {
+        when(mWindowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info);
+    }
+
     void overrideKeyEventPolicyFlags(int flags) {
         mKeyEventPolicyFlags = flags;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ec5e51e..58e919d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2796,15 +2796,17 @@
         final WindowState imeAppTarget =
                 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
         mDisplayContent.setImeLayeringTarget(imeAppTarget);
-        spyOn(imeAppTarget);
-        doReturn(true).when(imeAppTarget).isRequestedVisible(ime());
+        imeAppTarget.setRequestedVisibleTypes(ime());
         assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
 
         // Verify imeMenuDialog doesn't be focused window if the next IME target is closing.
         final WindowState nextImeAppTarget =
                 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget");
         makeWindowVisibleAndDrawn(nextImeAppTarget);
-        nextImeAppTarget.mActivityRecord.commitVisibility(false, false);
+        // Even if the app still requests IME, the ime dialog should not gain focus if the target
+        // app is invisible.
+        nextImeAppTarget.setRequestedVisibleTypes(ime());
+        nextImeAppTarget.mActivityRecord.setVisibility(false);
         mDisplayContent.setImeLayeringTarget(nextImeAppTarget);
         assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
new file mode 100644
index 0000000..5e56f84
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
@@ -0,0 +1,132 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteModemEnableRequestAttributes is used to pack info needed by modem to allow carrier to
+ * roam to satellite.
+ *
+ * @hide
+ */
+public final class SatelliteModemEnableRequestAttributes implements Parcelable {
+
+    /** {@code true} to enable satellite and {@code false} to disable satellite */
+    private final boolean mIsEnabled;
+    /**
+     * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite,
+     * {@code mIsDemoMode} is always considered as {@code false} by Telephony.
+     */
+    private final boolean mIsDemoMode;
+    /**
+     * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When
+     * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by
+     * Telephony.
+     */
+    private final boolean mIsEmergencyMode;
+
+    /** The subscription related info */
+    @NonNull private final SatelliteSubscriptionInfo mSatelliteSubscriptionInfo;
+
+    public SatelliteModemEnableRequestAttributes(boolean isEnabled, boolean isDemoMode,
+            boolean isEmergencyMode, @NonNull SatelliteSubscriptionInfo satelliteSubscriptionInfo) {
+        mIsEnabled = isEnabled;
+        mIsDemoMode = isDemoMode;
+        mIsEmergencyMode = isEmergencyMode;
+        mSatelliteSubscriptionInfo = satelliteSubscriptionInfo;
+    }
+
+    private SatelliteModemEnableRequestAttributes(Parcel in) {
+        mIsEnabled = in.readBoolean();
+        mIsDemoMode = in.readBoolean();
+        mIsEmergencyMode = in.readBoolean();
+        mSatelliteSubscriptionInfo = in.readParcelable(
+                SatelliteSubscriptionInfo.class.getClassLoader(), SatelliteSubscriptionInfo.class);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mIsDemoMode);
+        dest.writeBoolean(mIsEmergencyMode);
+        mSatelliteSubscriptionInfo.writeToParcel(dest, flags);
+    }
+
+    public static final Creator<SatelliteModemEnableRequestAttributes> CREATOR = new Creator<>() {
+        @Override
+        public SatelliteModemEnableRequestAttributes createFromParcel(Parcel in) {
+            return new SatelliteModemEnableRequestAttributes(in);
+        }
+
+        @Override
+        public SatelliteModemEnableRequestAttributes[] newArray(int size) {
+            return new SatelliteModemEnableRequestAttributes[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return (new StringBuilder()).append("SatelliteModemEnableRequestAttributes{")
+                .append(", mIsEnabled=").append(mIsEnabled)
+                .append(", mIsDemoMode=").append(mIsDemoMode)
+                .append(", mIsDemoMode=").append(mIsDemoMode)
+                .append("mSatelliteSubscriptionInfo=").append(mSatelliteSubscriptionInfo)
+                .append("}")
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteModemEnableRequestAttributes that = (SatelliteModemEnableRequestAttributes) o;
+        return mIsEnabled == that.mIsEnabled && mIsDemoMode == that.mIsDemoMode
+                && mIsEmergencyMode == that.mIsEmergencyMode && mSatelliteSubscriptionInfo.equals(
+                that.mSatelliteSubscriptionInfo);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIsEnabled, mIsDemoMode, mIsEmergencyMode, mSatelliteSubscriptionInfo);
+    }
+
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    public boolean isDemoMode() {
+        return mIsDemoMode;
+    }
+
+    public boolean isEmergencyMode() {
+        return mIsEmergencyMode;
+    }
+
+    @NonNull public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
+        return mSatelliteSubscriptionInfo;
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
new file mode 100644
index 0000000..2ef19f8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
@@ -0,0 +1,106 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteSubscriptionInfo is used to pack subscription related info needed by modem to allow
+ * carrier to roam to satellite.
+ *
+ * @hide
+ */
+public final class SatelliteSubscriptionInfo implements Parcelable {
+    /**
+     * The ICC ID used for satellite attachment.
+     */
+    @NonNull private final String mIccId;
+
+    /**
+     * The NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+     */
+    @NonNull private final String mNiddApn;
+
+    public SatelliteSubscriptionInfo(@NonNull String iccId, @NonNull String niddApn) {
+        mIccId = iccId;
+        mNiddApn = niddApn;
+    }
+
+    private SatelliteSubscriptionInfo(Parcel in) {
+        mIccId = in.readString8();
+        mNiddApn = in.readString8();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mIccId);
+        dest.writeString8(mNiddApn);
+    }
+
+    @NonNull public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
+        @Override
+        public SatelliteSubscriptionInfo createFromParcel(Parcel in) {
+            return new SatelliteSubscriptionInfo(in);
+        }
+
+        @Override
+        public SatelliteSubscriptionInfo[] newArray(int size) {
+            return new SatelliteSubscriptionInfo[size];
+        }
+    };
+
+    @Override
+    @NonNull public String toString() {
+        return (new StringBuilder()).append("SatelliteSubscriptionInfo{")
+                .append("IccId=").append(mIccId)
+                .append(", NiddApn=").append(mNiddApn)
+                .append("}")
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteSubscriptionInfo that = (SatelliteSubscriptionInfo) o;
+        return mIccId.equals(that.getIccId()) && mNiddApn.equals(that.getNiddApn());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getIccId(), getNiddApn());
+    }
+
+    @NonNull
+    public String getIccId() {
+        return mIccId;
+    }
+
+    @NonNull
+    public String getNiddApn() {
+        return mNiddApn;
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl
new file mode 100644
index 0000000..0a40e15
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.SatelliteSubscriptionInfo;
+
+/**
+ * {@hide}
+ */
+ parcelable SatelliteModemEnableRequestAttributes {
+    /**
+     * {@code true} to enable satellite and {@code false} to disable.
+     */
+    boolean isEnabled;
+    /**
+     * {@code true} to enable demo mode and {@code false} to disable.
+     */
+    boolean isDemoMode;
+    /**
+     * {@code true} to enable emergency modeand {@code false} to disable.
+     */
+    boolean isEmergencyMode;
+    /**
+     * The subscription related info.
+     */
+    SatelliteSubscriptionInfo satelliteSubscriptionInfo;
+ }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
new file mode 100644
index 0000000..f664dda
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+/**
+ * {@hide}
+ */
+ parcelable SatelliteSubscriptionInfo {
+    /**
+     * The ICC ID used for satellite attachment.
+     */
+    String iccId;
+    /**
+     * The NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+     */
+    String niddApn;
+ }
\ No newline at end of file
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ab406ef..5b17825 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -1867,7 +1867,7 @@
             return true;
         }
 
-        public String getName() {
+        public String getUniqueIdentifier() {
             return mName;
         }
 
diff --git a/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java b/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java
index 80d495d..cb26edc 100644
--- a/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java
@@ -30,6 +30,8 @@
  */
 public class ResizeHWLayerActivity extends AppCompatActivity {
 
+    private ValueAnimator mAnimator;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -43,10 +45,10 @@
         PropertyValuesHolder pvhWidth = PropertyValuesHolder.ofInt("width", width, 1);
         PropertyValuesHolder pvhHeight = PropertyValuesHolder.ofInt("height", height, 1);
         final LayoutParams params = child.getLayoutParams();
-        ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(pvhWidth, pvhHeight);
-        animator.setRepeatMode(ValueAnimator.REVERSE);
-        animator.setRepeatCount(ValueAnimator.INFINITE);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+        mAnimator = ValueAnimator.ofPropertyValuesHolder(pvhWidth, pvhHeight);
+        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator valueAnimator) {
                 params.width = (Integer)valueAnimator.getAnimatedValue("width");
@@ -54,7 +56,15 @@
                 child.requestLayout();
             }
         });
-        animator.start();
+        mAnimator.start();
         setContentView(child);
     }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
index 910bf59..f59e143 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
@@ -15,6 +15,8 @@
  */
 package com.android.hoststubgen
 
+import java.io.File
+
 /**
  * We will not print the stack trace for exceptions implementing it.
  */
@@ -49,4 +51,22 @@
 /**
  * We use this for general "user" errors.
  */
-class HostStubGenUserErrorException(message: String) : Exception(message), UserErrorException
+class GeneralUserErrorException(message: String) : Exception(message), UserErrorException
+
+/** Base exception class for invalid command line arguments. */
+open class ArgumentsException(message: String?) : Exception(message), UserErrorException
+
+/** Thrown when the same annotation is used with different annotation arguments. */
+class DuplicateAnnotationException(annotationName: String?) :
+    ArgumentsException("Duplicate annotation specified: '$annotationName'")
+
+/** Thrown when an input file does not exist. */
+class InputFileNotFoundException(filename: String) :
+    ArgumentsException("File '$filename' not found")
+
+fun String.ensureFileExists(): String {
+    if (!File(this).exists()) {
+        throw InputFileNotFoundException(this)
+    }
+    return this
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
index 6b01d48..a218c55 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
@@ -19,6 +19,6 @@
     open fun onErrorFound(message: String) {
         // TODO: For now, we just throw as soon as any error is found, but eventually we should keep
         // all errors and print them at the end.
-        throw HostStubGenUserErrorException(message)
+        throw GeneralUserErrorException(message)
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index fcdf824..4bcee40 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -89,6 +89,8 @@
         addPrinter(StreamPrinter(level, PrintWriter(BufferedOutputStream(
             FileOutputStream(logFilename)))))
 
+        log.i("Log file set: $logFilename for $level")
+
         return this
     }
 
@@ -122,6 +124,9 @@
     }
 
     fun println(level: LogLevel, message: String) {
+        if (message.isEmpty()) {
+            return // Don't print an empty message.
+        }
         printers.forEach {
             if (it.logLevel.ordinal >= level.ordinal) {
                 it.println(level, indent, message)
@@ -185,31 +190,45 @@
         println(LogLevel.Debug, format, *args)
     }
 
-    inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): T {
+    inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): Double {
+        var ret: Double = -1.0
         val start = System.currentTimeMillis()
         try {
-            return block()
+            block()
         } finally {
             val end = System.currentTimeMillis()
+            ret = (end - start) / 1000.0
             if (isEnabled(level)) {
                 println(level,
                     String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0))
             }
         }
+        return ret
     }
 
-    inline fun <T> iTime(message: String, block: () -> T): T {
+    /** Do an "i" log with how long it took. */
+    inline fun <T> iTime(message: String, block: () -> T): Double {
         return logTime(LogLevel.Info, message, block)
     }
 
-    inline fun <T> vTime(message: String, block: () -> T): T {
+    /** Do a "v" log with how long it took. */
+    inline fun <T> vTime(message: String, block: () -> T): Double {
         return logTime(LogLevel.Verbose, message, block)
     }
 
-    inline fun <T> dTime(message: String, block: () -> T): T {
+    /** Do a "d" log with how long it took. */
+    inline fun <T> dTime(message: String, block: () -> T): Double {
         return logTime(LogLevel.Debug, message, block)
     }
 
+    /**
+     * Similar to the other "xTime" methods, but the message is not supposed to be printed.
+     * It's only used to measure the duration with the same interface as other log methods.
+     */
+    inline fun <T> nTime(block: () -> T): Double {
+        return logTime(LogLevel.Debug, "", block)
+    }
+
     inline fun forVerbose(block: () -> Unit) {
         if (isEnabled(LogLevel.Verbose)) {
             block()
@@ -253,6 +272,21 @@
             }
         }
     }
+
+    /**
+     * Handle log-related command line arguments.
+     */
+    fun maybeHandleCommandLineArg(currentArg: String, nextArgProvider: () -> String): Boolean {
+        when (currentArg) {
+            "-v", "--verbose" -> setConsoleLogLevel(LogLevel.Verbose)
+            "-d", "--debug" -> setConsoleLogLevel(LogLevel.Debug)
+            "-q", "--quiet" -> setConsoleLogLevel(LogLevel.None)
+            "--verbose-log" -> addFilePrinter(LogLevel.Verbose, nextArgProvider())
+            "--debug-log" -> addFilePrinter(LogLevel.Debug, nextArgProvider())
+            else -> return false
+        }
+        return true
+    }
 }
 
 private interface LogPrinter {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 45e7e30..8506466 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -24,20 +24,32 @@
  */
 fun main(args: Array<String>) {
     executableName = "HostStubGen"
+    runMainWithBoilerplate {
+        // Parse the command line arguments.
+        var clanupOnError = false
+        try {
+            val options = HostStubGenOptions.parseArgs(args)
+            clanupOnError = options.cleanUpOnError.get
 
+            log.v("$executableName started")
+            log.v("Options: $options")
+
+            // Run.
+            HostStubGen(options).run()
+        } catch (e: Throwable) {
+            if (clanupOnError) {
+                TODO("Remove output jars here")
+            }
+            throw e
+        }
+    }
+}
+
+inline fun runMainWithBoilerplate(realMain: () -> Unit) {
     var success = false
-    var clanupOnError = false
 
     try {
-        // Parse the command line arguments.
-        val options = HostStubGenOptions.parseArgs(args)
-        clanupOnError = options.cleanUpOnError.get
-
-        log.v("$executableName started")
-        log.v("Options: $options")
-
-        // Run.
-        HostStubGen(options).run()
+        realMain()
 
         success = true
     } catch (e: Throwable) {
@@ -45,9 +57,6 @@
         if (e !is UserErrorException) {
             e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
         }
-        if (clanupOnError) {
-            TODO("Remove output jars here")
-        }
     } finally {
         log.i("$executableName finished")
         log.flush()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 2f833a8..f88b107 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -17,20 +17,17 @@
 
 import com.android.hoststubgen.filters.FilterPolicy
 import java.io.BufferedReader
-import java.io.File
 import java.io.FileReader
 
 /**
  * A single value that can only set once.
  */
-class SetOnce<T>(
-        private var value: T,
-) {
+open class SetOnce<T>(private var value: T) {
     class SetMoreThanOnceException : Exception()
 
     private var set = false
 
-    fun set(v: T) {
+    fun set(v: T): T {
         if (set) {
             throw SetMoreThanOnceException()
         }
@@ -39,6 +36,7 @@
         }
         set = true
         value = v
+        return v
     }
 
     val get: T
@@ -59,6 +57,16 @@
     }
 }
 
+class IntSetOnce(value: Int) : SetOnce<Int>(value) {
+    fun set(v: String): Int {
+        try {
+            return this.set(v.toInt())
+        } catch (e: NumberFormatException) {
+            throw ArgumentsException("Invalid integer $v")
+        }
+    }
+}
+
 /**
  * Options that can be set from command line arguments.
  */
@@ -113,18 +121,11 @@
 
         var apiListFile: SetOnce<String?> = SetOnce(null),
 
-        var numShards: SetOnce<Int> = SetOnce(1),
-        var shard: SetOnce<Int> = SetOnce(0),
+        var numShards: IntSetOnce = IntSetOnce(1),
+        var shard: IntSetOnce = IntSetOnce(0),
 ) {
     companion object {
 
-        private fun String.ensureFileExists(): String {
-            if (!File(this).exists()) {
-                throw InputFileNotFoundException(this)
-            }
-            return this
-        }
-
         private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> {
             val colon = fromColonTo.indexOf(':')
             if ((colon < 1) || (colon + 1 >= fromColonTo.length)) {
@@ -137,7 +138,7 @@
         fun parseArgs(args: Array<String>): HostStubGenOptions {
             val ret = HostStubGenOptions()
 
-            val ai = ArgIterator(expandAtFiles(args))
+            val ai = ArgIterator.withAtFiles(args)
 
             var allAnnotations = mutableSetOf<String>()
 
@@ -148,11 +149,6 @@
                 return name
             }
 
-            fun setLogFile(level: LogLevel, filename: String) {
-                log.addFilePrinter(level, filename)
-                log.i("$level log file: $filename")
-            }
-
             while (true) {
                 val arg = ai.nextArgOptional()
                 if (arg == null) {
@@ -161,33 +157,23 @@
 
                 // Define some shorthands...
                 fun nextArg(): String = ai.nextArgRequired(arg)
-                fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) }
-                fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) }
                 fun MutableSet<String>.addUniqueAnnotationArg(): String =
                         nextArg().also { this += ensureUniqueAnnotation(it) }
-                fun SetOnce<Int>.setNextIntArg(): String = nextArg().also {
-                    try {
-                        this.set(it.toInt())
-                    } catch (e: NumberFormatException) {
-                        throw ArgumentsException("Invalid integer for $arg: $it")
-                    }
-                }
 
+                if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+                    continue
+                }
                 try {
                     when (arg) {
                         // TODO: Write help
                         "-h", "--help" -> TODO("Help is not implemented yet")
 
-                        "-v", "--verbose" -> log.setConsoleLogLevel(LogLevel.Verbose)
-                        "-d", "--debug" -> log.setConsoleLogLevel(LogLevel.Debug)
-                        "-q", "--quiet" -> log.setConsoleLogLevel(LogLevel.None)
-
-                        "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists()
-                        "--out-stub-jar" -> ret.outStubJar.setNextStringArg()
-                        "--out-impl-jar" -> ret.outImplJar.setNextStringArg()
+                        "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
+                        "--out-stub-jar" -> ret.outStubJar.set(nextArg())
+                        "--out-impl-jar" -> ret.outImplJar.set(nextArg())
 
                         "--policy-override-file" ->
-                            ret.policyOverrideFile.setNextStringArg().ensureFileExists()
+                            ret.policyOverrideFile.set(nextArg())!!.ensureFileExists()
 
                         "--clean-up-on-error" -> ret.cleanUpOnError.set(true)
                         "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false)
@@ -231,19 +217,19 @@
                             ret.packageRedirects += parsePackageRedirect(nextArg())
 
                         "--annotation-allowed-classes-file" ->
-                            ret.annotationAllowedClassesFile.setNextStringArg()
+                            ret.annotationAllowedClassesFile.set(nextArg())
 
                         "--default-class-load-hook" ->
-                            ret.defaultClassLoadHook.setNextStringArg()
+                            ret.defaultClassLoadHook.set(nextArg())
 
                         "--default-method-call-hook" ->
-                            ret.defaultMethodCallHook.setNextStringArg()
+                            ret.defaultMethodCallHook.set(nextArg())
 
                         "--intersect-stub-jar" ->
                             ret.intersectStubJars += nextArg().ensureFileExists()
 
                         "--gen-keep-all-file" ->
-                            ret.inputJarAsKeepAllFile.setNextStringArg()
+                            ret.inputJarAsKeepAllFile.set(nextArg())
 
                         // Following options are for debugging.
                         "--enable-class-checker" -> ret.enableClassChecker.set(true)
@@ -261,16 +247,21 @@
                         "--no-non-stub-method-check" ->
                             ret.enableNonStubMethodCallDetection.set(false)
 
-                        "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg()
+                        "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg())
 
-                        "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
-                        "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
+                        "--stats-file" -> ret.statsFile.set(nextArg())
+                        "--supported-api-list-file" -> ret.apiListFile.set(nextArg())
 
-                        "--stats-file" -> ret.statsFile.setNextStringArg()
-                        "--supported-api-list-file" -> ret.apiListFile.setNextStringArg()
-
-                        "--num-shards" -> ret.numShards.setNextIntArg()
-                        "--shard-index" -> ret.shard.setNextIntArg()
+                        "--num-shards" -> ret.numShards.set(nextArg()).also {
+                            if (it < 1) {
+                                throw ArgumentsException("$arg must be positive integer")
+                            }
+                        }
+                        "--shard-index" -> ret.shard.set(nextArg()).also {
+                            if (it < 0) {
+                                throw ArgumentsException("$arg must be positive integer or zero")
+                            }
+                        }
 
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
@@ -286,6 +277,15 @@
                 log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
                         " $executableName will not generate jar files.")
             }
+            if (ret.numShards.isSet != ret.shard.isSet) {
+                throw ArgumentsException("--num-shards and --shard-index must be used together")
+            }
+
+            if (ret.numShards.isSet) {
+                if (ret.shard.get >= ret.numShards.get) {
+                    throw ArgumentsException("--shard-index must be smaller than --num-shards")
+                }
+            }
 
             if (ret.enableNonStubMethodCallDetection.get) {
                 log.w("--enable-non-stub-method-check is not fully implemented yet." +
@@ -294,87 +294,6 @@
 
             return ret
         }
-
-        /**
-         * Scan the arguments, and if any of them starts with an `@`, then load from the file
-         * and use its content as arguments.
-         *
-         * In this file, each line is treated as a single argument.
-         *
-         * The file can contain '#' as comments.
-         */
-        private fun expandAtFiles(args: Array<String>): List<String> {
-            val ret = mutableListOf<String>()
-
-            args.forEach { arg ->
-                if (!arg.startsWith('@')) {
-                    ret += arg
-                    return@forEach
-                }
-                // Read from the file, and add each line to the result.
-                val filename = arg.substring(1).ensureFileExists()
-
-                log.v("Expanding options file $filename")
-
-                BufferedReader(FileReader(filename)).use { reader ->
-                    while (true) {
-                        var line = reader.readLine()
-                        if (line == null) {
-                            break // EOF
-                        }
-
-                        line = normalizeTextLine(line)
-                        if (line.isNotEmpty()) {
-                            ret += line
-                        }
-                    }
-                }
-            }
-            return ret
-        }
-    }
-
-    open class ArgumentsException(message: String?) : Exception(message), UserErrorException
-
-    /** Thrown when the same annotation is used with different annotation arguments. */
-    class DuplicateAnnotationException(annotationName: String?) :
-            ArgumentsException("Duplicate annotation specified: '$annotationName'")
-
-    /** Thrown when an input file does not exist. */
-    class InputFileNotFoundException(filename: String) :
-            ArgumentsException("File '$filename' not found")
-
-    private class ArgIterator(
-            private val args: List<String>,
-            private var currentIndex: Int = -1
-    ) {
-        val current: String
-            get() = args.get(currentIndex)
-
-        /**
-         * Get the next argument, or [null] if there's no more arguments.
-         */
-        fun nextArgOptional(): String? {
-            if ((currentIndex + 1) >= args.size) {
-                return null
-            }
-            return args.get(++currentIndex)
-        }
-
-        /**
-         * Get the next argument, or throw if
-         */
-        fun nextArgRequired(argName: String): String {
-            nextArgOptional().let {
-                if (it == null) {
-                    throw ArgumentsException("Missing parameter for option $argName")
-                }
-                if (it.isEmpty()) {
-                    throw ArgumentsException("Parameter can't be empty for option $argName")
-                }
-                return it
-            }
-        }
     }
 
     override fun toString(): String {
@@ -415,3 +334,80 @@
             """.trimIndent()
     }
 }
+
+class ArgIterator(
+    private val args: List<String>,
+    private var currentIndex: Int = -1
+) {
+    val current: String
+        get() = args.get(currentIndex)
+
+    /**
+     * Get the next argument, or [null] if there's no more arguments.
+     */
+    fun nextArgOptional(): String? {
+        if ((currentIndex + 1) >= args.size) {
+            return null
+        }
+        return args.get(++currentIndex)
+    }
+
+    /**
+     * Get the next argument, or throw if
+     */
+    fun nextArgRequired(argName: String): String {
+        nextArgOptional().let {
+            if (it == null) {
+                throw ArgumentsException("Missing parameter for option $argName")
+            }
+            if (it.isEmpty()) {
+                throw ArgumentsException("Parameter can't be empty for option $argName")
+            }
+            return it
+        }
+    }
+
+    companion object {
+        fun withAtFiles(args: Array<String>): ArgIterator {
+            return ArgIterator(expandAtFiles(args))
+        }
+    }
+}
+
+/**
+ * Scan the arguments, and if any of them starts with an `@`, then load from the file
+ * and use its content as arguments.
+ *
+ * In this file, each line is treated as a single argument.
+ *
+ * The file can contain '#' as comments.
+ */
+private fun expandAtFiles(args: Array<String>): List<String> {
+    val ret = mutableListOf<String>()
+
+    args.forEach { arg ->
+        if (!arg.startsWith('@')) {
+            ret += arg
+            return@forEach
+        }
+        // Read from the file, and add each line to the result.
+        val filename = arg.substring(1).ensureFileExists()
+
+        log.v("Expanding options file $filename")
+
+        BufferedReader(FileReader(filename)).use { reader ->
+            while (true) {
+                var line = reader.readLine()
+                if (line == null) {
+                    break // EOF
+                }
+
+                line = normalizeTextLine(line)
+                if (line.isNotEmpty()) {
+                    ret += line
+                }
+            }
+        }
+    }
+    return ret
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index f219dac..6cf2143 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -58,7 +58,24 @@
     return null
 }
 
-fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+fun ClassNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+    return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun MethodNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+    return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun FieldNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+    return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun <T> findAnnotationValueAsObject(
+    an: AnnotationNode,
+    propertyName: String,
+    expectedTypeHumanReadableName: String,
+    converter: (Any?) -> T?,
+): T? {
     for (i in 0..(an.values?.size ?: 0) - 2 step 2) {
         val name = an.values[i]
 
@@ -66,16 +83,30 @@
             continue
         }
         val value = an.values[i + 1]
-        if (value is String) {
-            return value
+        if (value == null) {
+            return null
         }
-        throw ClassParseException(
-                "The type of '$name' in annotation \"${an.desc}\" must be String" +
-                        ", but is ${value?.javaClass?.canonicalName}")
+
+        try {
+            return converter(value)
+        } catch (e: ClassCastException) {
+            throw ClassParseException(
+                "The type of '$propertyName' in annotation @${an.desc} must be " +
+                        "$expectedTypeHumanReadableName, but is ${value?.javaClass?.canonicalName}")
+        }
     }
     return null
 }
 
+fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+    return findAnnotationValueAsObject(an, propertyName, "String", {it as String})
+}
+
+fun findAnnotationValueAsType(an: AnnotationNode, propertyName: String): Type? {
+    return findAnnotationValueAsObject(an, propertyName, "Class", {it as Type})
+}
+
+
 val periodOrSlash = charArrayOf('.', '/')
 
 fun getPackageNameFromFullClassName(fullClassName: String): String {
@@ -125,6 +156,24 @@
     return Pair(name.substring(0, pos), name.substring(pos + 1))
 }
 
+fun String.startsWithAny(vararg prefixes: String): Boolean {
+    prefixes.forEach {
+        if (this.startsWith(it)) {
+            return true
+        }
+    }
+    return false
+}
+
+fun String.endsWithAny(vararg suffixes: String): Boolean {
+    suffixes.forEach {
+        if (this.endsWith(it)) {
+            return true
+        }
+    }
+    return false
+}
+
 fun String.toJvmClassName(): String {
     return this.replace('.', '/')
 }
@@ -137,6 +186,14 @@
     return this.replace('/', '.')
 }
 
+fun zipEntryNameToClassName(entryFilename: String): String? {
+    val suffix = ".class"
+    if (!entryFilename.endsWith(suffix)) {
+        return null
+    }
+    return entryFilename.substring(0, entryFilename.length - suffix.length)
+}
+
 private val numericalInnerClassName = """.*\$\d+$""".toRegex()
 
 fun isAnonymousInnerClass(cn: ClassNode): Boolean {
@@ -278,6 +335,14 @@
     return (this.access and Opcodes.ACC_STATIC) != 0
 }
 
+fun MethodNode.isPublic(): Boolean {
+    return (this.access and Opcodes.ACC_PUBLIC) != 0
+}
+
+fun MethodNode.isSpecial(): Boolean {
+    return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name
+}
+
 fun FieldNode.isEnum(): Boolean {
     return (this.access and Opcodes.ACC_ENUM) != 0
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 2607df6..e2647eb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -27,6 +27,11 @@
 import java.io.BufferedInputStream
 import java.io.PrintWriter
 import java.util.Arrays
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Consumer
+import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
 
 /**
@@ -183,10 +188,43 @@
         /**
          * Load all the classes, without code.
          */
-        fun loadClassStructures(inJar: String): ClassNodes {
-            log.iTime("Reading class structure from $inJar") {
-                val allClasses = ClassNodes()
+        fun loadClassStructures(
+            inJar: String,
+            timeCollector: Consumer<Double>? = null,
+        ): ClassNodes {
+            val allClasses = ClassNodes()
 
+            // Load classes in parallel.
+            val executor = Executors.newFixedThreadPool(4)
+
+            // First exception defected.
+            val exception = AtomicReference<Throwable>()
+
+            // Called on a BG thread. Read a single jar entry and add it to [allClasses].
+            fun parseClass(inZip: ZipFile, entry: ZipEntry) {
+                try {
+                    inZip.getInputStream(entry).use { ins ->
+                        val cr = ClassReader(BufferedInputStream(ins))
+                        val cn = ClassNode()
+                        cr.accept(
+                            cn, ClassReader.SKIP_CODE
+                                    or ClassReader.SKIP_DEBUG
+                                    or ClassReader.SKIP_FRAMES
+                        )
+                        synchronized(allClasses) {
+                            if (!allClasses.addClass(cn)) {
+                                log.w("Duplicate class found: ${cn.name}")
+                            }
+                        }
+                    }
+                } catch (e: Throwable) {
+                    log.e("Failed to load class: $e")
+                    exception.compareAndSet(null, e)
+                }
+            }
+
+            // Actually open the jar and read it on worker threads.
+            val time = log.iTime("Reading class structure from $inJar") {
                 log.withIndent {
                     ZipFile(inJar).use { inZip ->
                         val inEntries = inZip.entries()
@@ -194,40 +232,42 @@
                         while (inEntries.hasMoreElements()) {
                             val entry = inEntries.nextElement()
 
-                            BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                                if (entry.name.endsWith(".class")) {
-                                    val cr = ClassReader(bis)
-                                    val cn = ClassNode()
-                                    cr.accept(
-                                        cn, ClassReader.SKIP_CODE
-                                                or ClassReader.SKIP_DEBUG
-                                                or ClassReader.SKIP_FRAMES
-                                    )
-                                    if (!allClasses.addClass(cn)) {
-                                        log.w("Duplicate class found: ${cn.name}")
-                                    }
-                                } else if (entry.name.endsWith(".dex")) {
-                                    // Seems like it's an ART jar file. We can't process it.
-                                    // It's a fatal error.
-                                    throw InvalidJarFileException(
-                                        "$inJar is not a desktop jar file."
-                                        + " It contains a *.dex file."
-                                    )
-                                } else {
-                                    // Unknown file type. Skip.
-                                    while (bis.available() > 0) {
-                                        bis.skip((1024 * 1024).toLong())
-                                    }
+                            if (entry.name.endsWith(".class")) {
+                                executor.submit {
+                                    parseClass(inZip, entry)
                                 }
+                            } else if (entry.name.endsWith(".dex")) {
+                                // Seems like it's an ART jar file. We can't process it.
+                                // It's a fatal error.
+                                throw InvalidJarFileException(
+                                    "$inJar is not a desktop jar file."
+                                            + " It contains a *.dex file."
+                                )
+                            } else {
+                                // Unknown file type. Skip.
                             }
                         }
+                        // Wait for all the work to complete. (must do it before closing the zip)
+                        log.i("Waiting for all loaders to finish...")
+                        executor.shutdown()
+                        executor.awaitTermination(5, TimeUnit.MINUTES)
+                        log.i("All loaders to finished.")
                     }
                 }
+
+                // If any exception is detected, throw it.
+                exception.get()?.let {
+                    throw it
+                }
+
                 if (allClasses.size == 0) {
                     log.w("$inJar contains no *.class files.")
+                } else {
+                    log.i("Loaded ${allClasses.size} classes from $inJar.")
                 }
-                return allClasses
             }
+            timeCollector?.accept(time)
+            return allClasses
         }
     }
 }
\ No newline at end of file