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>,