Merge "Moved panel expansion classes to shade package"
diff --git a/core/api/current.txt b/core/api/current.txt
index 7a412d8..27d5ac3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19548,6 +19548,7 @@
     method public boolean isFullTracking();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementRequest> CREATOR;
+    field public static final int PASSIVE_INTERVAL = 2147483647; // 0x7fffffff
   }
 
   public static final class GnssMeasurementRequest.Builder {
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 8af923e..eddb519 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -34,13 +34,26 @@
      * The intent extra key for the {@code RequestInfo} object when launching the UX
      * activities.
      */
-    public static final String EXTRA_REQUEST_INFO = "android.credentials.ui.extra.REQUEST_INFO";
+    public static final @NonNull String EXTRA_REQUEST_INFO =
+            "android.credentials.ui.extra.REQUEST_INFO";
+
+    /** Type value for an executeGetCredential request. */
+    public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
+    /** Type value for an executeCreateCredential request. */
+    public static final @NonNull String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
 
     @NonNull
     private final IBinder mToken;
 
-    public RequestInfo(@NonNull IBinder token) {
+    @NonNull
+    private final String mType;
+
+    private final boolean mIsFirstUsage;
+
+    public RequestInfo(@NonNull IBinder token, @NonNull String type, boolean isFirstUsage) {
         mToken = token;
+        mType = type;
+        mIsFirstUsage = isFirstUsage;
     }
 
     /** Returns the request token matching the user request. */
@@ -49,15 +62,39 @@
         return mToken;
     }
 
+    /** Returns the request type. */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns whether this is the first Credential Manager usage for this user on the device.
+     *
+     * If true, the user will be prompted for a provider-centric dialog first to confirm their
+     * provider choices.
+     */
+    public boolean isFirstUsage() {
+        return mIsFirstUsage;
+    }
+
     protected RequestInfo(@NonNull Parcel in) {
         IBinder token = in.readStrongBinder();
+        String type = in.readString8();
+        boolean isFirstUsage = in.readBoolean();
+
         mToken = token;
         AnnotationValidations.validate(NonNull.class, null, mToken);
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mIsFirstUsage = isFirstUsage;
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mToken);
+        dest.writeString8(mType);
+        dest.writeBoolean(mIsFirstUsage);
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index df1c0d7..10a7538 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -826,7 +826,9 @@
      * <p> Here, SC Map, refers to the {@link StreamConfigurationMap}, the target stream sizes must
      * be chosen from. {@code DEFAULT} refers to the default sensor pixel mode {@link
      * StreamConfigurationMap} and {@code MAX_RES} refers to the maximum resolution {@link
-     * StreamConfigurationMap}. The same capture request must not mix targets from
+     * StreamConfigurationMap}. For {@code MAX_RES} streams, {@code MAX} in the {@code Max size} column refers to the maximum size from
+     * {@link StreamConfigurationMap#getOutputSizes} and {@link StreamConfigurationMap#getHighResolutionOutputSizes}.
+     * Note: The same capture request must not mix targets from
      * {@link StreamConfigurationMap}s corresponding to different sensor pixel modes. </p>
      *
      * <p> 10-bit output capable
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 26415d3..0905e1b 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1723,7 +1723,17 @@
                 }
 
                 if (isUltraHighResolution) {
-                    sizes.add(getMaxSize(sm.getOutputSizes(formatChosen)));
+                    Size [] outputSizes = sm.getOutputSizes(formatChosen);
+                    Size [] highResolutionOutputSizes =
+                            sm.getHighResolutionOutputSizes(formatChosen);
+                    Size maxBurstSize = getMaxSizeOrNull(outputSizes);
+                    Size maxHighResolutionSize = getMaxSizeOrNull(highResolutionOutputSizes);
+                    Size chosenMaxSize =
+                            maxBurstSize != null ? maxBurstSize : maxHighResolutionSize;
+                    if (maxBurstSize != null && maxHighResolutionSize != null) {
+                        chosenMaxSize = getMaxSize(maxBurstSize, maxHighResolutionSize);
+                    }
+                    sizes.add(chosenMaxSize);
                 } else {
                     if (formatChosen == ImageFormat.RAW_SENSOR) {
                         // RAW_SENSOR always has MAXIMUM threshold.
@@ -2126,6 +2136,21 @@
         }
 
         /**
+         * Get the largest size by area.
+         *
+         * @param sizes an array of sizes
+         *
+         * @return Largest Size or null if sizes was null or had 0 elements
+         */
+        public static @Nullable Size getMaxSizeOrNull(Size... sizes) {
+            if (sizes == null || sizes.length == 0) {
+                return null;
+            }
+
+            return getMaxSize(sizes);
+        }
+
+        /**
          * Whether or not the hardware level reported by android.info.supportedHardwareLevel is
          * at least the desired one (but could be higher)
          */
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index adae034..d451765 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4418,6 +4418,9 @@
         int type = readInt();
         if (isLengthPrefixed(type)) {
             int objectLength = readInt();
+            if (objectLength < 0) {
+                return null;
+            }
             int end = MathUtils.addOrThrow(dataPosition(), objectLength);
             int valueLength = end - start;
             setDataPosition(end);
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 71cb0e3..a924b97 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -31,6 +31,15 @@
  * This class contains extra parameters to pass in a GNSS measurement request.
  */
 public final class GnssMeasurementRequest implements Parcelable {
+    /**
+     * Represents a passive only request. Such a request will not trigger any active GNSS
+     * measurements or power usage itself, but may receive GNSS measurements generated in response
+     * to other requests.
+     *
+     * @see GnssMeasurementRequest#getIntervalMillis()
+     */
+    public static final int PASSIVE_INTERVAL = Integer.MAX_VALUE;
+
     private final boolean mCorrelationVectorOutputsEnabled;
     private final boolean mFullTracking;
     private final int mIntervalMillis;
@@ -76,7 +85,10 @@
     }
 
     /**
-     * Represents the requested time interval between the reported measurements in milliseconds.
+     * Returns the requested time interval between the reported measurements in milliseconds, or
+     * {@link #PASSIVE_INTERVAL} if this is a passive, no power request. A passive request will not
+     * actively generate GNSS measurement updates, but may receive GNSS measurement updates
+     * generated as a result of other GNSS measurement requests.
      *
      * <p>If the time interval is not set, the default value is 0, which means the fastest rate the
      * GNSS chipset can report.
@@ -213,7 +225,9 @@
 
         /**
          * Set the time interval between the reported measurements in milliseconds, which is 0 by
-         * default.
+         * default. The request interval may be set to {@link #PASSIVE_INTERVAL} which indicates
+         * this request will not actively generate GNSS measurement updates, but may receive
+         * GNSS measurement updates generated as a result of other GNSS measurement requests.
          *
          * <p>An interval of 0 milliseconds means the fastest rate the chipset can report.
          *
diff --git a/media/Android.bp b/media/Android.bp
index e97f077..97970da 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -90,8 +90,13 @@
         "aidl/android/media/audio/common/AudioStreamType.aidl",
         "aidl/android/media/audio/common/AudioUsage.aidl",
         "aidl/android/media/audio/common/AudioUuid.aidl",
+        "aidl/android/media/audio/common/Boolean.aidl",
+        "aidl/android/media/audio/common/Byte.aidl",
         "aidl/android/media/audio/common/ExtraAudioDescriptor.aidl",
+        "aidl/android/media/audio/common/Float.aidl",
+        "aidl/android/media/audio/common/Double.aidl",
         "aidl/android/media/audio/common/Int.aidl",
+        "aidl/android/media/audio/common/Long.aidl",
         "aidl/android/media/audio/common/PcmType.aidl",
     ],
     stability: "vintf",
diff --git a/media/aidl/android/media/audio/common/Boolean.aidl b/media/aidl/android/media/audio/common/Boolean.aidl
new file mode 100644
index 0000000..fddd5324
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Boolean.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'boolean', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Boolean {
+    boolean value;
+}
diff --git a/media/aidl/android/media/audio/common/Byte.aidl b/media/aidl/android/media/audio/common/Byte.aidl
new file mode 100644
index 0000000..f0a31a2
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Byte.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'byte', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Byte {
+    byte value;
+}
diff --git a/media/aidl/android/media/audio/common/Double.aidl b/media/aidl/android/media/audio/common/Double.aidl
new file mode 100644
index 0000000..d7ab7b8
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Double.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'double', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Double {
+    double value;
+}
diff --git a/media/aidl/android/media/audio/common/Float.aidl b/media/aidl/android/media/audio/common/Float.aidl
new file mode 100644
index 0000000..4c5257e
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Float.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'float', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Float {
+    float value;
+}
diff --git a/media/aidl/android/media/audio/common/Long.aidl b/media/aidl/android/media/audio/common/Long.aidl
new file mode 100644
index 0000000..a4aeb53
--- /dev/null
+++ b/media/aidl/android/media/audio/common/Long.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.common;
+
+/**
+ * This is a simple wrapper around a 'long', putting it in a parcelable, so it
+ * can be used as an 'inout' parameter, be made '@nullable', etc.
+ *
+ * {@hide}
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable Long {
+    long value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl
new file mode 100644
index 0000000..bc996e4
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Boolean.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Boolean {
+  boolean value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl
new file mode 100644
index 0000000..604e74d
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Byte.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Byte {
+  byte value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl
new file mode 100644
index 0000000..a525629
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Double.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Double {
+  double value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl
new file mode 100644
index 0000000..af98eab
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Float.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Float {
+  float value;
+}
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl
new file mode 100644
index 0000000..e403dd3
--- /dev/null
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/Long.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.media.audio.common;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Long {
+  long value;
+}
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 1f89f99..6aead43 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -30,8 +30,10 @@
 
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 
 
 /**
@@ -50,10 +52,10 @@
 @SystemApi
 public class AudioMixingRule {
 
-    private AudioMixingRule(int mixType, ArrayList<AudioMixMatchCriterion> criteria,
+    private AudioMixingRule(int mixType, Collection<AudioMixMatchCriterion> criteria,
                             boolean allowPrivilegedMediaPlaybackCapture,
                             boolean voiceCommunicationCaptureAllowed) {
-        mCriteria = criteria;
+        mCriteria = new ArrayList<>(criteria);
         mTargetMixType = mixType;
         mAllowPrivilegedPlaybackCapture = allowPrivilegedMediaPlaybackCapture;
         mVoiceCommunicationCaptureAllowed = voiceCommunicationCaptureAllowed;
@@ -140,6 +142,20 @@
             return Objects.hash(mAttr, mIntProp, mRule);
         }
 
+        @Override
+        public boolean equals(Object object) {
+            if (object == null || this.getClass() != object.getClass()) {
+                return false;
+            }
+            if (object == this) {
+                return true;
+            }
+            AudioMixMatchCriterion other = (AudioMixMatchCriterion) object;
+            return mRule == other.mRule
+                    && mIntProp == other.mIntProp
+                    && Objects.equals(mAttr, other.mAttr);
+        }
+
         void writeToParcel(Parcel dest) {
             dest.writeInt(mRule);
             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
@@ -192,15 +208,6 @@
         return false;
     }
 
-    private static boolean areCriteriaEquivalent(ArrayList<AudioMixMatchCriterion> cr1,
-            ArrayList<AudioMixMatchCriterion> cr2) {
-        if (cr1 == null || cr2 == null) return false;
-        if (cr1 == cr2) return true;
-        if (cr1.size() != cr2.size()) return false;
-        //TODO iterate over rules to check they contain the same criterion
-        return (cr1.hashCode() == cr2.hashCode());
-    }
-
     private final int mTargetMixType;
     int getTargetMixType() {
         return mTargetMixType;
@@ -286,9 +293,9 @@
 
         final AudioMixingRule that = (AudioMixingRule) o;
         return (this.mTargetMixType == that.mTargetMixType)
-                && (areCriteriaEquivalent(this.mCriteria, that.mCriteria)
-                && this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture
-                && this.mVoiceCommunicationCaptureAllowed
+                && Objects.equals(mCriteria, that.mCriteria)
+                && (this.mAllowPrivilegedPlaybackCapture == that.mAllowPrivilegedPlaybackCapture)
+                && (this.mVoiceCommunicationCaptureAllowed
                     == that.mVoiceCommunicationCaptureAllowed);
     }
 
@@ -372,7 +379,7 @@
      * Builder class for {@link AudioMixingRule} objects
      */
     public static class Builder {
-        private ArrayList<AudioMixMatchCriterion> mCriteria;
+        private final Set<AudioMixMatchCriterion> mCriteria;
         private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
         private boolean mAllowPrivilegedMediaPlaybackCapture = false;
         // This value should be set internally according to a permission check
@@ -382,7 +389,7 @@
          * Constructs a new Builder with no rules.
          */
         public Builder() {
-            mCriteria = new ArrayList<AudioMixMatchCriterion>();
+            mCriteria = new HashSet<>();
         }
 
         /**
@@ -547,7 +554,12 @@
                 throw new IllegalArgumentException("Illegal argument for mix role");
             }
 
-            Log.i("AudioMixingRule", "Builder setTargetMixRole " + mixRole);
+            if (mCriteria.stream().map(AudioMixMatchCriterion::getRule)
+                    .anyMatch(mixRole == MIX_ROLE_PLAYERS
+                            ? AudioMixingRule::isRecorderRule : AudioMixingRule::isPlayerRule)) {
+                throw new IllegalArgumentException(
+                        "Target mix role is not compatible with mix rules.");
+            }
             mTargetMixType = mixRole == MIX_ROLE_INJECTOR
                     ? AudioMix.MIX_TYPE_RECORDERS : AudioMix.MIX_TYPE_PLAYERS;
             return this;
@@ -604,17 +616,15 @@
          */
         private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
                 throws IllegalArgumentException {
-            // as rules are added to the Builder, we verify they are consistent with the type
-            // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
+            // If mix type is invalid and added rule is valid only for the players / recorders,
+            // adjust the mix type accordingly.
+            // Otherwise, if the mix type was already deduced or set explicitly, verify the rule
+            // is valid for the mix type.
             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
                 if (isPlayerRule(rule)) {
                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
                 } else if (isRecorderRule(rule)) {
                     mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
-                } else {
-                    // For rules which are not player or recorder specific (e.g. RULE_MATCH_UID),
-                    // the default mix type is MIX_TYPE_PLAYERS.
-                    mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
                 }
             } else if ((isPlayerRule(rule) && (mTargetMixType != AudioMix.MIX_TYPE_PLAYERS))
                     || (isRecorderRule(rule)) && (mTargetMixType != AudioMix.MIX_TYPE_RECORDERS))
@@ -622,75 +632,13 @@
                 throw new IllegalArgumentException("Incompatible rule for mix");
             }
             synchronized (mCriteria) {
-                Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
-                final int match_rule = rule & ~RULE_EXCLUSION_MASK;
-                while (crIterator.hasNext()) {
-                    final AudioMixMatchCriterion criterion = crIterator.next();
-
-                    if ((criterion.mRule & ~RULE_EXCLUSION_MASK) != match_rule) {
-                        continue; // The two rules are not of the same type
-                    }
-                    switch (match_rule) {
-                        case RULE_MATCH_ATTRIBUTE_USAGE:
-                            // "usage"-based rule
-                            if (criterion.mAttr.getSystemUsage() == attrToMatch.getSystemUsage()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for " + attrToMatch);
-                                }
-                            }
-                            break;
-                        case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                            // "capture preset"-base rule
-                            if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for " + attrToMatch);
-                                }
-                            }
-                            break;
-                        case RULE_MATCH_UID:
-                            // "usage"-based rule
-                            if (criterion.mIntProp == intProp.intValue()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for UID " + intProp);
-                                }
-                            }
-                            break;
-                        case RULE_MATCH_USERID:
-                            // "userid"-based rule
-                            if (criterion.mIntProp == intProp.intValue()) {
-                                if (criterion.mRule == rule) {
-                                    // rule already exists, we're done
-                                    return this;
-                                } else {
-                                    // criterion already exists with a another rule,
-                                    // it is incompatible
-                                    throw new IllegalArgumentException("Contradictory rule exists"
-                                            + " for userId " + intProp);
-                                }
-                            }
-                            break;
-                    }
+                int oppositeRule = rule ^ RULE_EXCLUSION_MASK;
+                if (mCriteria.stream().anyMatch(criterion -> criterion.mRule == oppositeRule)) {
+                    throw new IllegalArgumentException("AudioMixingRule cannot contain RULE_MATCH_*"
+                            + " and RULE_EXCLUDE_* for the same dimension.");
                 }
-                // rule didn't exist, add it
-                switch (match_rule) {
+                int ruleWithoutExclusion = rule & ~RULE_EXCLUSION_MASK;
+                switch (ruleWithoutExclusion) {
                     case RULE_MATCH_ATTRIBUTE_USAGE:
                     case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                         mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
@@ -734,8 +682,11 @@
          * @return a new {@link AudioMixingRule} object
          */
         public AudioMixingRule build() {
-            return new AudioMixingRule(mTargetMixType, mCriteria,
-                mAllowPrivilegedMediaPlaybackCapture, mVoiceCommunicationCaptureAllowed);
+            return new AudioMixingRule(
+                    mTargetMixType == AudioMix.MIX_TYPE_INVALID
+                            ? AudioMix.MIX_TYPE_PLAYERS : mTargetMixType,
+                    mCriteria, mAllowPrivilegedMediaPlaybackCapture,
+                    mVoiceCommunicationCaptureAllowed);
         }
     }
 }
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index fcf91f0..7963ff2 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -14,6 +14,7 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "guava",
+        "hamcrest-library",
         "platform-test-annotations",
     ],
     platform_apis: true,
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
new file mode 100644
index 0000000..ad7ab97
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.audiopolicytest;
+
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+
+import android.media.AudioAttributes;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for AudioPolicy.
+ *
+ * Run with "atest AudioMixingRuleUnitTests".
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioMixingRuleUnitTests {
+    private static final AudioAttributes USAGE_MEDIA_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
+    private static final AudioAttributes CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build();
+    private static final int TEST_UID = 42;
+    private static final int OTHER_UID = 77;
+
+    @Test
+    public void testConstructValidRule() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                .build();
+
+        // Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS,
+        // since the rules are valid for both MIX_ROLE_PLAYERS & MIX_ROLE_INJECTOR.
+        assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
+        assertThat(rule.getCriteria(), containsInAnyOrder(
+                isAudioMixMatchUsageCriterion(USAGE_MEDIA),
+                isAudioMixMatchUidCriterion(TEST_UID)));
+    }
+
+    @Test
+    public void testConstructRuleWithConflictingCriteriaFails() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        .addMixRule(RULE_MATCH_UID, TEST_UID)
+                        // Conflicts with previous criterion.
+                        .addMixRule(RULE_EXCLUDE_UID, OTHER_UID)
+                        .build());
+    }
+
+    @Test
+    public void testRuleBuilderDedupsCriteria() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                // Identical to previous criterion.
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                // Identical to first criterion.
+                .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                .build();
+
+        assertThat(rule.getCriteria(), hasSize(2));
+        assertThat(rule.getCriteria(), containsInAnyOrder(
+                isAudioMixMatchUsageCriterion(USAGE_MEDIA),
+                isAudioMixMatchUidCriterion(TEST_UID)));
+    }
+
+    @Test
+    public void failsWhenAddAttributeRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match attribute usage requires AudioAttributes, not
+                        // just the int enum value of the usage.
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
+                        .build());
+    }
+
+    @Test
+    public void failsWhenExcludeAttributeRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match attribute usage requires AudioAttributes, not
+                        // just the int enum value of the usage.
+                        .excludeMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
+                        .build());
+    }
+
+    @Test
+    public void failsWhenAddIntRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match uid requires Integer not AudioAttributes.
+                        .addMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        .build());
+    }
+
+    @Test
+    public void failsWhenExcludeIntRuleCalledWithInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // Rule match uid requires Integer not AudioAttributes.
+                        .excludeMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        .build());
+    }
+
+    @Test
+    public void injectorMixTypeDeductionWithGenericRuleSucceeds() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                // UID rule can be used both with MIX_ROLE_PLAYERS and MIX_ROLE_INJECTOR.
+                .addMixRule(RULE_MATCH_UID, TEST_UID)
+                // Capture preset rule is only valid for injector, MIX_ROLE_INJECTOR should
+                // be deduced.
+                .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                        CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
+                .build();
+
+        assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
+        assertThat(rule.getCriteria(), containsInAnyOrder(
+                isAudioMixMatchUidCriterion(TEST_UID),
+                isAudioMixMatchCapturePresetCriterion(VOICE_RECOGNITION)));
+    }
+
+    @Test
+    public void settingTheMixTypeToIncompatibleInjectorMixFails() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
+                        // Capture preset cannot be defined for MIX_ROLE_PLAYERS.
+                        .setTargetMixRole(MIX_ROLE_PLAYERS)
+                        .build());
+    }
+
+    @Test
+    public void addingPlayersOnlyRuleWithInjectorsOnlyRuleFails() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new AudioMixingRule.Builder()
+                        // MIX_ROLE_PLAYERS only rule.
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
+                        // MIX ROLE_INJECTOR only rule.
+                        .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
+                        .build());
+    }
+
+
+    private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int expectedRule = exclude ? RULE_EXCLUDE_UID : RULE_MATCH_UID;
+                return item.getRule() == expectedRule && item.getIntProp() == uid;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with uid %d",
+                                exclude ? "exclude" : "match", uid));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixMatchUidCriterion(int uid) {
+        return isAudioMixUidCriterion(uid, /*exclude=*/ false);
+    }
+
+    private static Matcher isAudioMixCapturePresetCriterion(int audioSource, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int expectedRule = exclude
+                        ? RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET
+                        : RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
+                AudioAttributes attributes = item.getAudioAttributes();
+                return item.getRule() == expectedRule
+                        && attributes != null && attributes.getCapturePreset() == audioSource;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with capture preset %d",
+                                exclude ? "exclude" : "match", audioSource));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixMatchCapturePresetCriterion(int audioSource) {
+        return isAudioMixCapturePresetCriterion(audioSource, /*exclude=*/ false);
+    }
+
+    private static Matcher isAudioMixUsageCriterion(int usage, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("usage mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int expectedRule =
+                        exclude ? RULE_EXCLUDE_ATTRIBUTE_USAGE : RULE_MATCH_ATTRIBUTE_USAGE;
+                AudioAttributes attributes = item.getAudioAttributes();
+                return item.getRule() == expectedRule
+                        && attributes != null && attributes.getUsage() == usage;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with usage %d",
+                                exclude ? "exclude" : "match", usage));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixMatchUsageCriterion(int usage) {
+        return isAudioMixUsageCriterion(usage, /*exclude=*/ false);
+    }
+
+
+}
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index 0a5afe4..db54ae3 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,9 @@
         android:layout_width="24dp"
         android:layout_height="24dp"
         android:layout_marginStart="24dp"
-        android:tint="@android:color/system_accent1_600"/>
+        android:tint="@android:color/system_accent1_600"
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
 
     <TextView
         android:id="@android:id/text1"
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 54916a2..a3d71b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -30,7 +30,8 @@
         android:layout_height="24dp"
         android:layout_marginTop="8dp"
         android:layout_marginEnd="12dp"
-        android:contentDescription="Permission Icon"/>
+        android:importantForAccessibility="no"
+        android:contentDescription="@null"/>
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 75b0df5..b6876a4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -105,7 +105,7 @@
 
     private final Runnable mTimeoutRunnable = this::timeout;
 
-    private boolean mStopAfterFirstMatch;;
+    private boolean mStopAfterFirstMatch;
 
     /**
      * A state enum for devices' discovery.
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
index 6986961..9d063e9 100644
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml
@@ -21,11 +21,12 @@
             android:paddingEnd="0dp">
     <item>
         <shape android:shape="rectangle">
-          <solid android:color="?androidprv:attr/colorSurface" />
+          <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
             <corners android:radius="32dp" />
         </shape>
     </item>
     <item
+        android:id="@+id/user_switcher_key_down"
         android:drawable="@drawable/ic_ksh_key_down"
         android:gravity="end|center_vertical"
         android:width="32dp"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2bdb1b8..632fcd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1073,6 +1073,11 @@
                         android.R.attr.textColorPrimary));
                 header.setBackground(mView.getContext().getDrawable(
                         R.drawable.bouncer_user_switcher_header_bg));
+                Drawable keyDownDrawable =
+                        ((LayerDrawable) header.getBackground().mutate()).findDrawableByLayerId(
+                                R.id.user_switcher_key_down);
+                keyDownDrawable.setTintList(Utils.getColorAttr(mView.getContext(),
+                        android.R.attr.textColorPrimary));
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 744e7da..3c375f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2575,7 +2575,7 @@
         }
 
         final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
-        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive
                 && !statusBarShadeLocked;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -2621,7 +2621,7 @@
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
-                (mBouncerFullyShown && !mGoingToSleep
+                (mBouncerFullyShown
                         || mAuthInterruptActive
                         || mOccludingAppRequestingFace
                         || awakeKeyguard
@@ -2633,6 +2633,7 @@
                 && strongAuthAllowsScanning && mIsPrimaryUser
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
                 && !faceAuthenticated
+                && !mGoingToSleep
                 && !fpOrFaceIsLockedOut;
 
         // Aggregate relevant fields for debug logging.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 2503d3c..821e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -28,6 +28,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -158,17 +161,38 @@
         private final Context mContext;
         private final ControlsComponent mControlsComponent;
 
+        private final UiEventLogger mUiEventLogger;
+
+        @VisibleForTesting
+        public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
+            @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+            DREAM_HOME_CONTROLS_TAPPED(1212);
+
+            private final int mId;
+
+            DreamOverlayEvent(int id) {
+                mId = id;
+            }
+
+            @Override
+            public int getId() {
+                return mId;
+            }
+        }
+
         @Inject
         DreamHomeControlsChipViewController(
                 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 ActivityStarter activityStarter,
                 Context context,
-                ControlsComponent controlsComponent) {
+                ControlsComponent controlsComponent,
+                UiEventLogger uiEventLogger) {
             super(view);
 
             mActivityStarter = activityStarter;
             mContext = context;
             mControlsComponent = controlsComponent;
+            mUiEventLogger = uiEventLogger;
         }
 
         @Override
@@ -184,6 +208,8 @@
         private void onClickHomeControls(View v) {
             if (DEBUG) Log.d(TAG, "home controls complication tapped");
 
+            mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+
             final Intent intent = new Intent(mContext, ControlsActivity.class)
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
                     .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 23c96e1..3b629f7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -274,6 +274,10 @@
     public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
             new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
 
+    @Keep
+    public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+            new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
+
     // 1200 - predictive back
     @Keep
     public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
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 e377501..6821b14 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
@@ -1297,7 +1297,9 @@
                 + mAmbientState.getOverExpansion()
                 - getCurrentOverScrollAmount(false /* top */);
         float fraction = mAmbientState.getExpansionFraction();
-        if (mAmbientState.isBouncerInTransit()) {
+        // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
+        // overlap. Otherwise, we maintain the normal fraction for smoothness.
+        if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5a33603..da6d455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -221,8 +221,10 @@
 
         mEditText.setTextColor(textColor);
         mEditText.setHintTextColor(hintColor);
-        mEditText.getTextCursorDrawable().setColorFilter(
-                accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+        if (mEditText.getTextCursorDrawable() != null) {
+            mEditText.getTextCursorDrawable().setColorFilter(
+                    accentColor.getDefaultColor(), PorterDuff.Mode.SRC_IN);
+        }
         mContentBackground.setColor(editBgColor);
         mContentBackground.setStroke(stroke, accentColor);
         mDelete.setImageTintList(ColorStateList.valueOf(deleteFgColor));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 14b637c..d2116b9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1476,6 +1476,27 @@
     }
 
     @Test
+    public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+            throws RemoteException {
+        // Preconditions for face auth to run
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+        deviceNotGoingToSleep();
+        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+        deviceGoingToSleep();
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+    }
+
+    @Test
     public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
@@ -1662,6 +1683,10 @@
         mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
     }
 
+    private void deviceGoingToSleep() {
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(/* value doesn't matter */1);
+    }
+
     private void deviceIsInteractive() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 04ff7ae..db6082d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -29,9 +29,12 @@
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
@@ -40,6 +43,7 @@
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,6 +83,15 @@
     @Captor
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
+    @Mock
+    private ImageView mView;
+
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    @Mock
+    UiEventLogger mUiEventLogger;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -151,6 +164,30 @@
         verify(mDreamOverlayStateController).addComplication(mComplication);
     }
 
+    /**
+     * Ensures clicking home controls chip logs UiEvent.
+     */
+    @Test
+    public void testClick_logsUiEvent() {
+        final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
+                new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
+                        mView,
+                        mActivityStarter,
+                        mContext,
+                        mControlsComponent,
+                        mUiEventLogger);
+        viewController.onViewAttached();
+
+        final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+        clickListenerCaptor.getValue().onClick(mView);
+        verify(mUiEventLogger).log(
+                DreamHomeControlsComplication.DreamHomeControlsChipViewController
+                        .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+    }
+
     private void setHaveFavorites(boolean value) {
         final List<StructureInfo> favorites = mock(List.class);
         when(favorites.isEmpty()).thenReturn(!value);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6ae021b..4353036 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -55,6 +55,7 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -179,6 +180,40 @@
     }
 
     @Test
+    public void testUpdateStackHeight_qsExpansionGreaterThanZero() {
+        final float expansionFraction = 0.2f;
+        final float overExpansion = 50f;
+
+        mStackScroller.setQsExpansionFraction(1f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        mAmbientState.setOverExpansion(overExpansion);
+        when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+
+        mStackScroller.setExpandedHeight(100f);
+
+        float expected = MathUtils.lerp(0, overExpansion,
+                BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansionFraction));
+        assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+    }
+
+    @Test
+    public void testUpdateStackHeight_qsExpansionZero() {
+        final float expansionFraction = 0.2f;
+        final float overExpansion = 50f;
+
+        mStackScroller.setQsExpansionFraction(0f);
+        mAmbientState.setExpansionFraction(expansionFraction);
+        mAmbientState.setOverExpansion(overExpansion);
+        when(mAmbientState.isBouncerInTransit()).thenReturn(true);
+
+        mStackScroller.setExpandedHeight(100f);
+
+        float expected = MathUtils.lerp(0, overExpansion, expansionFraction);
+        assertThat(mAmbientState.getStackY()).isEqualTo(expected);
+    }
+
+    @Test
     public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
         final float dozeAmount = 0.5f;
         mAmbientState.setDozeAmount(dozeAmount);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f17f8f7..4829470 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -49,6 +49,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
@@ -359,6 +360,13 @@
                 EditorInfo editorInfo, boolean restarting) {
             mService.scheduleStartInput(remoteAccessibilityInputConnection, editorInfo, restarting);
         }
+
+        @Override
+        public boolean isTouchExplorationEnabled(@UserIdInt int userId) {
+            synchronized (mService.mLock) {
+                return mService.getUserStateLocked(userId).isTouchExplorationEnabledLocked();
+            }
+        }
     }
 
     public static final class Lifecycle extends SystemService {
diff --git a/services/core/java/com/android/server/AccessibilityManagerInternal.java b/services/core/java/com/android/server/AccessibilityManagerInternal.java
index 6ca32af..faa45ca 100644
--- a/services/core/java/com/android/server/AccessibilityManagerInternal.java
+++ b/services/core/java/com/android/server/AccessibilityManagerInternal.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.view.inputmethod.EditorInfo;
@@ -49,6 +50,15 @@
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             EditorInfo editorInfo, boolean restarting);
 
+    /**
+     * Queries whether touch-exploration mode is enabled or not for the specified user.
+     *
+     * @param userId User ID to be queried about.
+     * @return {@code true} if touch-exploration mode is enabled.
+     * @see android.view.accessibility.AccessibilityManager#isTouchExplorationEnabled()
+     */
+    public abstract boolean isTouchExplorationEnabled(@UserIdInt int userId);
+
     private static final AccessibilityManagerInternal NOP = new AccessibilityManagerInternal() {
         @Override
         public void setImeSessionEnabled(SparseArray<IAccessibilityInputMethodSession> sessions,
@@ -71,6 +81,11 @@
         public void startInput(IRemoteAccessibilityInputConnection remoteAccessibility,
                 EditorInfo editorInfo, boolean restarting) {
         }
+
+        @Override
+        public boolean isTouchExplorationEnabled(int userId) {
+            return false;
+        }
     };
 
     /**
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 5dbdb9b..c441859 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -16,6 +16,7 @@
 # Health
 per-file BatteryService.java = file:platform/hardware/interfaces:/health/aidl/OWNERS
 
+per-file *Accessibility* = file:/services/accessibility/OWNERS
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
 per-file *AppOp* = file:/core/java/android/permission/OWNERS
 per-file *Battery* = file:/BATTERY_STATS_OWNERS
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index eb0f3f0..672ee0e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -89,6 +89,7 @@
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -3100,7 +3101,7 @@
                              */
                             if (!checkKeyIntent(
                                     Binder.getCallingUid(),
-                                    intent)) {
+                                    result)) {
                                 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                                         "invalid intent in bundle returned");
                                 return;
@@ -3519,7 +3520,7 @@
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class)) != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent)) {
+                        result)) {
                     onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                             "invalid intent in bundle returned");
                     return;
@@ -4870,7 +4871,13 @@
          * into launching arbitrary intents on the device via by tricking to click authenticator
          * supplied entries in the system Settings app.
          */
-         protected boolean checkKeyIntent(int authUid, Intent intent) {
+        protected boolean checkKeyIntent(int authUid, Bundle bundle) {
+            if (!checkKeyIntentParceledCorrectly(bundle)) {
+                EventLog.writeEvent(0x534e4554, "250588548", authUid, "");
+                return false;
+            }
+
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
             // Explicitly set an empty ClipData to ensure that we don't offer to
             // promote any Uris contained inside for granting purposes
             if (intent.getClipData() == null) {
@@ -4905,6 +4912,25 @@
             }
         }
 
+        /**
+         * Simulate the client side's deserialization of KEY_INTENT value, to make sure they don't
+         * violate our security policy.
+         *
+         * In particular we want to make sure the Authenticator doesn't trick users
+         * into launching arbitrary intents on the device via exploiting any other Parcel read/write
+         * mismatch problems.
+         */
+        private boolean checkKeyIntentParceledCorrectly(Bundle bundle) {
+            Parcel p = Parcel.obtain();
+            p.writeBundle(bundle);
+            p.setDataPosition(0);
+            Bundle simulateBundle = p.readBundle();
+            p.recycle();
+            Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
+            return (intent.filterEquals(simulateBundle.getParcelable(AccountManager.KEY_INTENT,
+                Intent.class)));
+        }
+
         private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
             String className = activityInfo.name;
             return "android".equals(activityInfo.packageName) &&
@@ -5051,7 +5077,7 @@
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT, android.content.Intent.class)) != null) {
                 if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent)) {
+                        result)) {
                     onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                             "invalid intent in bundle returned");
                     return;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 30bc8a3..82b6fa5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,10 +41,12 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.bluetooth.BluetoothAdapter;
@@ -1190,6 +1192,8 @@
         mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_safe_media_volume_index) * 10;
 
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
@@ -1207,7 +1211,7 @@
         mPlaybackMonitor =
                 new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
                         device -> onMuteAwaitConnectionTimeout(device));
-        mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
+        mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
 
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
@@ -1313,6 +1317,7 @@
 
         intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
         intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+        intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
 
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
                 Context.RECEIVER_EXPORTED);
@@ -1932,13 +1937,7 @@
         if (state == AudioService.CONNECTION_STATE_CONNECTED) {
             // DEVICE_OUT_HDMI is now connected
             if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
-                sendMsg(mAudioHandler,
-                        MSG_CHECK_MUSIC_ACTIVE,
-                        SENDMSG_REPLACE,
-                        0,
-                        0,
-                        caller,
-                        MUSIC_ACTIVE_POLL_PERIOD_MS);
+                scheduleMusicActiveCheck();
             }
 
             if (isPlatformTelevision()) {
@@ -3827,8 +3826,9 @@
     }
 
     private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
+    private AtomicBoolean mMediaPlaybackActive = new AtomicBoolean(false);
 
-    private final IPlaybackConfigDispatcher mVoicePlaybackActivityMonitor =
+    private final IPlaybackConfigDispatcher mPlaybackActivityMonitor =
             new IPlaybackConfigDispatcher.Stub() {
         @Override
         public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
@@ -3841,19 +3841,26 @@
 
     private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
         boolean voiceActive = false;
+        boolean mediaActive = false;
         for (AudioPlaybackConfiguration config : configs) {
             final int usage = config.getAudioAttributes().getUsage();
-            if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
-                    || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
-                    && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+            if (!config.isActive()) {
+                continue;
+            }
+            if (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                    || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
                 voiceActive = true;
-                break;
+            }
+            if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+                mediaActive = true;
             }
         }
         if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
             updateHearingAidVolumeOnVoiceActivityUpdate();
         }
-
+        if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
+            scheduleMusicActiveCheck();
+        }
         // Update playback active state for all apps in audio mode stack.
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
@@ -6036,30 +6043,52 @@
         return mContentResolver;
     }
 
+    private void scheduleMusicActiveCheck() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            cancelMusicActiveCheck();
+            mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+                REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+                new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime()
+                    + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+        }
+    }
+
+    private void cancelMusicActiveCheck() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            if (mMusicActiveIntent != null) {
+                mAlarmManager.cancel(mMusicActiveIntent);
+                mMusicActiveIntent = null;
+            }
+        }
+    }
     private void onCheckMusicActive(String caller) {
         synchronized (mSafeMediaVolumeStateLock) {
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
                 int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-
-                if (mSafeMediaVolumeDevices.contains(device)) {
-                    sendMsg(mAudioHandler,
-                            MSG_CHECK_MUSIC_ACTIVE,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            caller,
-                            MUSIC_ACTIVE_POLL_PERIOD_MS);
+                if (mSafeMediaVolumeDevices.contains(device)
+                        && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+                    scheduleMusicActiveCheck();
                     int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
-                    if (mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
-                            && (index > safeMediaVolumeIndex(device))) {
+                    if (index > safeMediaVolumeIndex(device)) {
                         // Approximate cumulative active music time
-                        mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+                        long curTimeMs = SystemClock.elapsedRealtime();
+                        if (mLastMusicActiveTimeMs != 0) {
+                            mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+                        }
+                        mLastMusicActiveTimeMs = curTimeMs;
+                        Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
                             setSafeMediaVolumeEnabled(true, caller);
                             mMusicActiveMs = 0;
                         }
                         saveMusicActiveMs();
                     }
+                } else {
+                    cancelMusicActiveCheck();
+                    mLastMusicActiveTimeMs = 0;
                 }
             }
         }
@@ -6128,6 +6157,7 @@
                         } else {
                             // We have existing playback time recorded, already confirmed.
                             mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+                            mLastMusicActiveTimeMs = 0;
                         }
                     }
                 } else {
@@ -8637,13 +8667,7 @@
     @VisibleForTesting
     public void checkMusicActive(int deviceType, String caller) {
         if (mSafeMediaVolumeDevices.contains(deviceType)) {
-            sendMsg(mAudioHandler,
-                    MSG_CHECK_MUSIC_ACTIVE,
-                    SENDMSG_REPLACE,
-                    0,
-                    0,
-                    caller,
-                    MUSIC_ACTIVE_POLL_PERIOD_MS);
+            scheduleMusicActiveCheck();
         }
     }
 
@@ -8768,6 +8792,8 @@
                                 suspendedPackages[i], suspendedUids[i]);
                     }
                 }
+            } else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
+                onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
             }
         }
     } // end class AudioServiceBroadcastReceiver
@@ -9713,12 +9739,20 @@
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
     // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
     private int mMusicActiveMs;
+    private long mLastMusicActiveTimeMs = 0;
+    private PendingIntent mMusicActiveIntent = null;
+    private AlarmManager mAlarmManager;
+
     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
     private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
     private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;  // 30s after boot completed
     // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
     private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
 
+    private static final String ACTION_CHECK_MUSIC_ACTIVE =
+            AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+    private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
     private int safeMediaVolumeIndex(int device) {
         if (!mSafeMediaVolumeDevices.contains(device)) {
             return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -9740,14 +9774,9 @@
                 } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
                     mMusicActiveMs = 1;  // nonzero = confirmed
+                    mLastMusicActiveTimeMs = 0;
                     saveMusicActiveMs();
-                    sendMsg(mAudioHandler,
-                            MSG_CHECK_MUSIC_ACTIVE,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            caller,
-                            MUSIC_ACTIVE_POLL_PERIOD_MS);
+                    scheduleMusicActiveCheck();
                 }
             }
         }
@@ -9789,7 +9818,9 @@
     public void disableSafeMediaVolume(String callingPackage) {
         enforceVolumeController("disable the safe media volume");
         synchronized (mSafeMediaVolumeStateLock) {
+            final long identity = Binder.clearCallingIdentity();
             setSafeMediaVolumeEnabled(false, callingPackage);
+            Binder.restoreCallingIdentity(identity);
             if (mPendingVolumeCommand != null) {
                 onSetStreamVolume(mPendingVolumeCommand.mStreamType,
                                   mPendingVolumeCommand.mIndex,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 520a471..ab05e03 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -137,7 +137,6 @@
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
@@ -312,9 +311,6 @@
     @NonNull private final InputMethodBindingController mBindingController;
     @NonNull private final AutofillSuggestionsController mAutofillController;
 
-    // TODO(b/219056452): Use AccessibilityManagerInternal instead.
-    private final AccessibilityManager mAccessibilityManager;
-
     /**
      * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
      *
@@ -1734,7 +1730,6 @@
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mUserManager = mContext.getSystemService(UserManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-        mAccessibilityManager = AccessibilityManager.getInstance(context);
         mHasFeature = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_INPUT_METHODS);
 
@@ -5469,7 +5464,8 @@
         public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
                 InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
             // Get the device global touch exploration state before lock to avoid deadlock.
-            boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
+            final boolean touchExplorationEnabled = AccessibilityManagerInternal.get()
+                    .isTouchExplorationEnabled(userId);
 
             synchronized (ImfLock.class) {
                 mAutofillController.onCreateInlineSuggestionsRequest(userId, requestInfo, cb,
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 9f2a9cf..07e9fe6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -112,6 +112,9 @@
     @Override
     protected boolean registerWithService(GnssMeasurementRequest request,
             Collection<GnssListenerRegistration> registrations) {
+        if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+            return true;
+        }
         if (mGnssNative.startMeasurementCollection(request.isFullTracking(),
                 request.isCorrelationVectorOutputsEnabled(),
                 request.getIntervalMillis())) {
@@ -157,7 +160,7 @@
             Collection<GnssListenerRegistration> registrations) {
         boolean fullTracking = false;
         boolean enableCorrVecOutputs = false;
-        int intervalMillis = Integer.MAX_VALUE;
+        int intervalMillis = GnssMeasurementRequest.PASSIVE_INTERVAL;
 
         if (mSettingsHelper.isGnssMeasurementsFullTrackingEnabled()) {
             fullTracking = true;
@@ -165,6 +168,10 @@
 
         for (GnssListenerRegistration registration : registrations) {
             GnssMeasurementRequest request = registration.getRequest();
+            // passive requests do not contribute to the merged request
+            if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+                continue;
+            }
             if (request.isFullTracking()) {
                 fullTracking = true;
             }
@@ -175,10 +182,10 @@
         }
 
         return new GnssMeasurementRequest.Builder()
-                    .setFullTracking(fullTracking)
-                    .setCorrelationVectorOutputsEnabled(enableCorrVecOutputs)
-                    .setIntervalMillis(intervalMillis)
-                    .build();
+                .setFullTracking(fullTracking)
+                .setCorrelationVectorOutputsEnabled(enableCorrVecOutputs)
+                .setIntervalMillis(intervalMillis)
+                .build();
     }
 
     @Override
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index dfebdcc..f29d9b2 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -323,8 +323,15 @@
         parentPolicyExceptions: Set<Immutable.Policy.Exception>,
         nonInterfaceClassFailure: () -> String = { MessageUtils.nonInterfaceReturnFailure() },
     ): Boolean {
+        // Skip if the symbol being considered is itself ignored
         if (isIgnored(symbol)) return false
+
+        // Skip if the type being checked, like for a typeArg or return type, is ignored
         if (isIgnored(type)) return false
+
+        // Skip if that typeArg is itself ignored when inspected at the class header level
+        if (isIgnored(type.asElement())) return false
+
         if (type.isPrimitive) return false
         if (type.isPrimitiveOrVoid) {
             printError(parentChain, symbol, MessageUtils.voidReturnFailure())
@@ -355,6 +362,8 @@
         var anyError = false
 
         type.typeArguments.forEachIndexed { index, typeArg ->
+            if (isIgnored(typeArg.asElement())) return@forEachIndexed
+
             val argError =
                 visitType(parentChain, seenTypesByPolicy, symbol, typeArg, newPolicyExceptions) {
                     MessageUtils.nonInterfaceReturnFailure(
diff --git a/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
index 2f7d59a..43caa45 100644
--- a/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
+++ b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt
@@ -287,6 +287,42 @@
         )
     }
 
+    @Test
+    fun ignoredClass() = test(
+        JavaFileObjects.forSourceString(
+            "$PACKAGE_PREFIX.$DATA_CLASS_NAME",
+            /* language=JAVA */ """
+            package $PACKAGE_PREFIX;
+
+            import java.util.List;
+            import java.util.Map;
+
+            @Immutable
+            public interface $DATA_CLASS_NAME {
+                IgnoredClass getInnerClassOne();
+                NotIgnoredClass getInnerClassTwo();
+                Map<String, IgnoredClass> getInnerClassThree();
+                Map<String, NotIgnoredClass> getInnerClassFour();
+
+                @Immutable.Ignore
+                final class IgnoredClass {
+                    public String innerField;
+                }
+
+                final class NotIgnoredClass {
+                    public String innerField;
+                }
+            }
+            """.trimIndent()
+        ), errors = listOf(
+            nonInterfaceReturnFailure(line = 9),
+            nonInterfaceReturnFailure(line = 11, prefix = "Value NotIgnoredClass"),
+            classNotImmutableFailure(line = 18, className = "NotIgnoredClass"),
+            nonInterfaceClassFailure(line = 18),
+            memberNotMethodFailure(line = 19),
+        )
+    )
+
     private fun test(
         source: JavaFileObject,
         errors: List<CompilationError>,