Merge "Build input-related HAL code for host"
diff --git a/audio/README.md b/audio/README.md
index 1938ad4..3f40d72 100644
--- a/audio/README.md
+++ b/audio/README.md
@@ -2,10 +2,29 @@
Directory structure of the audio HAL related code.
-Run `common/all-versions/copyHAL.sh` to create a new version of the audio HAL
-based on an existing one.
+## Directory Structure for AIDL audio HAL
-## Directory Structure
+The AIDL version is located inside `aidl` directory. The tree below explains
+the role of each subdirectory:
+
+* `aidl_api` — snapshots of the API created each Android release. Every
+ release, the current version of the API becomes "frozen" and gets assigned
+ the next version number. If the API needs further modifications, they are
+ made on the "current" version. After making modifications, run
+ `m <package name>-update-api` to update the snapshot of the "current"
+ version.
+* `android/hardware/audio/common` — data structures and interfaces shared
+ between various HALs: BT HAL, core and effects audio HALs.
+* `android/hardware/audio/core` — data structures and interfaces of the
+ core audio HAL.
+* `default` — the default, reference implementation of the audio HAL service.
+* `vts` — VTS tests for the AIDL HAL.
+
+## Directory Structure for HIDL audio HAL
+
+Run `common/all-versions/copyHAL.sh` to create a new version of the HIDL audio
+HAL based on an existing one. Note that this isn't possible since Android T
+release. Android U and above uses AIDL audio HAL.
* `2.0` — version 2.0 of the core HIDL API. Note that `.hal` files
can not be moved into the `core` directory because that would change
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index fd893ec..d6e6f50 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -33,7 +33,7 @@
"android/hardware/audio/common/SourceMetadata.aidl",
],
imports: [
- "android.media.audio.common.types-V1",
+ "android.media.audio.common.types-V2",
],
stability: "vintf",
backend: {
@@ -51,7 +51,7 @@
ndk: {
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
min_sdk_version: "31",
},
@@ -59,8 +59,126 @@
versions_with_info: [
{
version: "1",
- imports: ["android.media.audio.common.types-V1"],
+ imports: ["android.media.audio.common.types-V2"],
},
+ // IMPORTANT: Update latest_android_hardware_audio_common every time you
+ // add the latest frozen version to versions_with_info
],
}
+
+// Note: This should always be one version ahead of the last frozen version
+latest_android_hardware_audio_common = "android.hardware.audio.common-V1"
+
+// Modules that depend on android.hardware.audio.common directly can include
+// the following cc_defaults to avoid explicitly managing dependency versions
+// across many scattered files.
+cc_defaults {
+ name: "latest_android_hardware_audio_common_cpp_static",
+ static_libs: [
+ latest_android_hardware_audio_common + "-cpp",
+ ],
+}
+
+cc_defaults {
+ name: "latest_android_hardware_audio_common_ndk_static",
+ static_libs: [
+ latest_android_hardware_audio_common + "-ndk",
+ ],
+}
+
+aidl_interface {
+ name: "android.hardware.audio.core",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/audio/core/AudioPatch.aidl",
+ "android/hardware/audio/core/AudioRoute.aidl",
+ "android/hardware/audio/core/IConfig.aidl",
+ "android/hardware/audio/core/IModule.aidl",
+ "android/hardware/audio/core/IStreamIn.aidl",
+ "android/hardware/audio/core/IStreamOut.aidl",
+ "android/hardware/audio/core/MmapBufferDescriptor.aidl",
+ "android/hardware/audio/core/ModuleDebug.aidl",
+ "android/hardware/audio/core/StreamDescriptor.aidl",
+ ],
+ imports: [
+ "android.hardware.common-V2",
+ "android.hardware.common.fmq-V1",
+ "android.hardware.audio.common-V1",
+ "android.media.audio.common.types-V2",
+ ],
+ stability: "vintf",
+ backend: {
+ // The C++ backend is disabled transitively due to use of FMQ.
+ cpp: {
+ enabled: false,
+ },
+ java: {
+ sdk_version: "module_current",
+ },
+ },
+ versions_with_info: [
+ // IMPORTANT: Update latest_android_hardware_audio_core every time you
+ // add the latest frozen version to versions_with_info
+ ],
+}
+
+// Note: This should always be one version ahead of the last frozen version
+latest_android_hardware_audio_core = "android.hardware.audio.core-V1"
+
+// Modules that depend on android.hardware.audio.core directly can include
+// the following cc_defaults to avoid explicitly managing dependency versions
+// across many scattered files.
+cc_defaults {
+ name: "latest_android_hardware_audio_core_ndk_shared",
+ shared_libs: [
+ latest_android_hardware_audio_core + "-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "latest_android_hardware_audio_core_ndk_static",
+ static_libs: [
+ latest_android_hardware_audio_core + "-ndk",
+ ],
+}
+
+aidl_interface {
+ name: "android.hardware.audio.effect",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/audio/effect/Descriptor.aidl",
+ "android/hardware/audio/effect/IEffect.aidl",
+ "android/hardware/audio/effect/IFactory.aidl",
+ ],
+ imports: [
+ "android.hardware.audio.common-V1",
+ "android.media.audio.common.types-V2",
+ ],
+ stability: "vintf",
+ backend: {
+ // The C++ backend is disabled transitively due to use of FMQ.
+ cpp: {
+ enabled: false,
+ },
+ java: {
+ sdk_version: "module_current",
+ },
+ },
+}
+
+latest_android_hardware_audio_effect = "android.hardware.audio.effect-V1"
+
+cc_defaults {
+ name: "latest_android_hardware_audio_effect_ndk_shared",
+ shared_libs: [
+ latest_android_hardware_audio_effect + "-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "latest_android_hardware_audio_effect_ndk_static",
+ static_libs: [
+ latest_android_hardware_audio_effect + "-ndk",
+ ],
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl
new file mode 100644
index 0000000..078b5ea
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioPatch {
+ int id;
+ int[] sourcePortConfigIds;
+ int[] sinkPortConfigIds;
+ int minimumStreamBufferSizeFrames;
+ int[] latenciesMs;
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl
new file mode 100644
index 0000000..deeef87
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AudioRoute {
+ int[] sourcePortIds;
+ int sinkPortId;
+ boolean isExclusive;
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl
new file mode 100644
index 0000000..fd80715
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.hardware.audio.core;
+@VintfStability
+interface IConfig {
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
new file mode 100644
index 0000000..a8bbb15
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,75 @@
+/*
+ * 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.hardware.audio.core;
+@VintfStability
+interface IModule {
+ void setModuleDebug(in android.hardware.audio.core.ModuleDebug debug);
+ android.media.audio.common.AudioPort connectExternalDevice(in android.media.audio.common.AudioPort templateIdAndAdditionalData);
+ void disconnectExternalDevice(int portId);
+ android.hardware.audio.core.AudioPatch[] getAudioPatches();
+ android.media.audio.common.AudioPort getAudioPort(int portId);
+ android.media.audio.common.AudioPortConfig[] getAudioPortConfigs();
+ android.media.audio.common.AudioPort[] getAudioPorts();
+ android.hardware.audio.core.AudioRoute[] getAudioRoutes();
+ android.hardware.audio.core.AudioRoute[] getAudioRoutesForAudioPort(int portId);
+ android.hardware.audio.core.IModule.OpenInputStreamReturn openInputStream(in android.hardware.audio.core.IModule.OpenInputStreamArguments args);
+ android.hardware.audio.core.IModule.OpenOutputStreamReturn openOutputStream(in android.hardware.audio.core.IModule.OpenOutputStreamArguments args);
+ android.hardware.audio.core.AudioPatch setAudioPatch(in android.hardware.audio.core.AudioPatch requested);
+ boolean setAudioPortConfig(in android.media.audio.common.AudioPortConfig requested, out android.media.audio.common.AudioPortConfig suggested);
+ void resetAudioPatch(int patchId);
+ void resetAudioPortConfig(int portConfigId);
+ @VintfStability
+ parcelable OpenInputStreamArguments {
+ int portConfigId;
+ android.hardware.audio.common.SinkMetadata sinkMetadata;
+ long bufferSizeFrames;
+ }
+ @VintfStability
+ parcelable OpenInputStreamReturn {
+ android.hardware.audio.core.IStreamIn stream;
+ android.hardware.audio.core.StreamDescriptor desc;
+ }
+ @VintfStability
+ parcelable OpenOutputStreamArguments {
+ int portConfigId;
+ android.hardware.audio.common.SourceMetadata sourceMetadata;
+ @nullable android.media.audio.common.AudioOffloadInfo offloadInfo;
+ long bufferSizeFrames;
+ }
+ @VintfStability
+ parcelable OpenOutputStreamReturn {
+ android.hardware.audio.core.IStreamOut stream;
+ android.hardware.audio.core.StreamDescriptor desc;
+ }
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl
new file mode 100644
index 0000000..d5ab3e8
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.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.hardware.audio.core;
+@VintfStability
+interface IStreamIn {
+ void close();
+ void updateMetadata(in android.hardware.audio.common.SinkMetadata sinkMetadata);
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl
new file mode 100644
index 0000000..3021d94
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.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.hardware.audio.core;
+@VintfStability
+interface IStreamOut {
+ void close();
+ void updateMetadata(in android.hardware.audio.common.SourceMetadata sourceMetadata);
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/MmapBufferDescriptor.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/MmapBufferDescriptor.aidl
new file mode 100644
index 0000000..6ea1c69
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/MmapBufferDescriptor.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable MmapBufferDescriptor {
+ android.hardware.common.Ashmem sharedMemory;
+ long burstSizeFrames;
+ int flags;
+ const int FLAG_INDEX_APPLICATION_SHAREABLE = 0;
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
new file mode 100644
index 0000000..80ee185
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ModuleDebug {
+ boolean simulateDeviceConnections;
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
new file mode 100644
index 0000000..db1ac22
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
@@ -0,0 +1,66 @@
+/*
+ * 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.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable StreamDescriptor {
+ android.hardware.common.fmq.MQDescriptor<android.hardware.audio.core.StreamDescriptor.Command,android.hardware.common.fmq.SynchronizedReadWrite> command;
+ android.hardware.common.fmq.MQDescriptor<android.hardware.audio.core.StreamDescriptor.Reply,android.hardware.common.fmq.SynchronizedReadWrite> reply;
+ int frameSizeBytes;
+ long bufferSizeFrames;
+ android.hardware.audio.core.StreamDescriptor.AudioBuffer audio;
+ const int COMMAND_BURST = 1;
+ @FixedSize @VintfStability
+ parcelable Position {
+ long frames;
+ long timeNs;
+ }
+ @FixedSize @VintfStability
+ parcelable Command {
+ int code;
+ int fmqByteCount;
+ }
+ @FixedSize @VintfStability
+ parcelable Reply {
+ int status;
+ int fmqByteCount;
+ android.hardware.audio.core.StreamDescriptor.Position observable;
+ android.hardware.audio.core.StreamDescriptor.Position hardware;
+ int latencyMs;
+ }
+ @VintfStability
+ union AudioBuffer {
+ android.hardware.common.fmq.MQDescriptor<byte,android.hardware.common.fmq.SynchronizedReadWrite> fmq;
+ android.hardware.audio.core.MmapBufferDescriptor mmap;
+ }
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Descriptor.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Descriptor.aidl
new file mode 100644
index 0000000..94cacd9
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Descriptor.aidl
@@ -0,0 +1,60 @@
+/*
+ * 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.hardware.audio.effect;
+@VintfStability
+parcelable Descriptor {
+ android.hardware.audio.effect.Descriptor.Common common;
+ const String EFFECT_TYPE_UUID_ENV_REVERB = "c2e5d5f0-94bd-4763-9cac-4e234d06839e";
+ const String EFFECT_TYPE_UUID_PRESET_REVERB = "47382d60-ddd8-11db-bf3a-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_EQUALIZER = "0bed4300-ddd6-11db-8f34-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_BASS_BOOST = "0634f220-ddd4-11db-a0fc-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_VIRTUALIZER = "37cc2c00-dddd-11db-8577-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_AGC = "0a8abfe0-654c-11e0-ba26-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_AEC = "7b491460-8d4d-11e0-bd61-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_NS = "58b4b260-8e06-11e0-aa8e-0002a5d5c51b";
+ const String EFFECT_TYPE_UUID_LOUDNESS_ENHANCER = "fe3199be-aed0-413f-87bb-11260eb63cf1";
+ const String EFFECT_TYPE_UUID_DYNAMICS_PROCESSING = "7261676f-6d75-7369-6364-28e2fd3ac39e";
+ const String EFFECT_TYPE_UUID_HAPTIC_GENERATOR = "1411e6d6-aecd-4021-a1cf-a6aceb0d71e5";
+ const String EFFECT_TYPE_UUID_SPATIALIZER = "ccd4cf09-a79d-46c2-9aae-06a1698d6c8f";
+ const String EFFECT_TYPE_UUID_VOLUME = "09e8ede0-ddde-11db-b4f6-0002a5d5c51b";
+ @VintfStability
+ parcelable Identity {
+ android.media.audio.common.AudioUuid type;
+ android.media.audio.common.AudioUuid uuid;
+ }
+ @VintfStability
+ parcelable Common {
+ android.hardware.audio.effect.Descriptor.Identity id;
+ }
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IEffect.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IEffect.aidl
new file mode 100644
index 0000000..7f868ad
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IEffect.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.hardware.audio.effect;
+@VintfStability
+interface IEffect {
+ void open();
+ void close();
+ android.hardware.audio.effect.Descriptor getDescriptor();
+}
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl
new file mode 100644
index 0000000..db06475
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.hardware.audio.effect;
+@VintfStability
+interface IFactory {
+ android.hardware.audio.effect.Descriptor.Identity[] queryEffects(in @nullable android.media.audio.common.AudioUuid type, in @nullable android.media.audio.common.AudioUuid implementation);
+ android.hardware.audio.effect.IEffect createEffect(in android.media.audio.common.AudioUuid implUuid);
+ void destroyEffect(in android.hardware.audio.effect.IEffect handle);
+}
diff --git a/audio/aidl/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl
new file mode 100644
index 0000000..005d4c0
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.hardware.audio.core;
+
+/**
+ * Audio patch specifies a connection between multiple audio port
+ * configurations.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioPatch {
+ /** The ID of the patch, unique within the HAL module. */
+ int id;
+ /**
+ * The list of IDs of source audio port configs ('AudioPortConfig.id').
+ * There must be at least one source in a valid patch and all IDs must be
+ * unique.
+ */
+ int[] sourcePortConfigIds;
+ /**
+ * The list of IDs of sink audio port configs ('AudioPortConfig.id').
+ * There must be at least one sink in a valid patch and all IDs must be
+ * unique.
+ */
+ int[] sinkPortConfigIds;
+ /**
+ * The minimum buffer size, in frames, which streams must use for
+ * this connection configuration. This field is filled out by the
+ * HAL module on creation of the patch and must be a positive number.
+ */
+ int minimumStreamBufferSizeFrames;
+ /**
+ * Latencies, in milliseconds, associated with each sink port config from
+ * the 'sinkPortConfigIds' field. This field is filled out by the HAL module
+ * on creation or updating of the patch and must be a positive number. This
+ * is a nominal value. The current value of latency is provided via
+ * 'StreamDescriptor' command exchange on each audio I/O operation.
+ */
+ int[] latenciesMs;
+}
diff --git a/audio/aidl/android/hardware/audio/core/AudioRoute.aidl b/audio/aidl/android/hardware/audio/core/AudioRoute.aidl
new file mode 100644
index 0000000..1e7b441
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/AudioRoute.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.
+ */
+
+package android.hardware.audio.core;
+
+/**
+ * Audio route specifies a path from multiple audio source ports to one audio
+ * sink port. As an example, when emitting audio output, source ports typically
+ * are mix ports (audio data from the framework), the sink is a device
+ * port. When acquiring audio, source ports are device ports, the sink is a mix
+ * port.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable AudioRoute {
+ /**
+ * The list of IDs of source audio ports ('AudioPort.id').
+ * There must be at least one source in a valid route and all IDs must be
+ * unique.
+ */
+ int[] sourcePortIds;
+ /** The ID of the sink audio port ('AudioPort.id'). */
+ int sinkPortId;
+ /** If set, only one source can be active, mixing is not supported. */
+ boolean isExclusive;
+}
diff --git a/audio/aidl/android/hardware/audio/core/IConfig.aidl b/audio/aidl/android/hardware/audio/core/IConfig.aidl
new file mode 100644
index 0000000..c7bb414
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IConfig.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.hardware.audio.core;
+
+/**
+ * This interface provides system-wide configuration parameters for audio I/O
+ * (by "system" here we mean the device running Android).
+ */
+@VintfStability
+interface IConfig {}
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
new file mode 100644
index 0000000..735f87f
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,484 @@
+/*
+ * 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.hardware.audio.core;
+
+import android.hardware.audio.common.SinkMetadata;
+import android.hardware.audio.common.SourceMetadata;
+import android.hardware.audio.core.AudioPatch;
+import android.hardware.audio.core.AudioRoute;
+import android.hardware.audio.core.IStreamIn;
+import android.hardware.audio.core.IStreamOut;
+import android.hardware.audio.core.ModuleDebug;
+import android.hardware.audio.core.StreamDescriptor;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.audio.common.AudioPort;
+import android.media.audio.common.AudioPortConfig;
+
+/**
+ * Each instance of IModule corresponds to a separate audio module. The system
+ * (the term "system" as used here applies to the entire device running Android)
+ * may have multiple modules due to the physical architecture, for example, it
+ * can have multiple DSPs or other audio I/O units which are not interconnected
+ * in hardware directly. Usually there is at least one audio module which is
+ * responsible for the "main" (or "built-in") audio functionality of the
+ * system. Even if the system lacks any physical audio I/O capabilities, there
+ * will be a "null" audio module.
+ *
+ * On a typical mobile phone there is usually a main DSP module which handles
+ * most of the phone's audio I/O via the built-in speakers and microphones. USB
+ * audio can exist as a separate module. Some audio modules can be implemented
+ * purely in software, for example, the remote submix module.
+ */
+@VintfStability
+interface IModule {
+ /**
+ * Sets debugging configuration for the HAL module. This method is only
+ * called during xTS testing and is intended for validating the aspects of
+ * the HAL module behavior that would otherwise require human intervention.
+ *
+ * The HAL module must throw an error if there is an attempt to change
+ * the debug behavior for the aspect which is currently in use.
+ *
+ * @param debug The debug options.
+ * @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality
+ * which is currently in use.
+ */
+ void setModuleDebug(in ModuleDebug debug);
+
+ /**
+ * Set a device port of an external device into connected state.
+ *
+ * This method is used to inform the HAL module that an external device has
+ * been connected to a device port selected using the 'id' field of the
+ * input AudioPort parameter. This device port must have dynamic profiles
+ * (an empty list of profiles). This port is further referenced to as "port
+ * template" because it acts as a template for creating a new instance of a
+ * "connected" device port which gets returned from this method.
+ *
+ * The input AudioPort parameter may contain any additional data obtained by
+ * the system side from other subsystems. The nature of data depends on the
+ * type of the connection. For example, for point-to-multipoint external
+ * device connections, the input parameter may contain the address of the
+ * connected external device. Another example is EDID information for HDMI
+ * connections (ExtraAudioDescriptor), which can be provided by the HDMI-CEC
+ * HAL module.
+ *
+ * It is the responsibility of the HAL module to query audio profiles
+ * supported by the connected device and return them as part of the returned
+ * AudioPort instance. In the case when the HAL is unable to query the
+ * external device, an error must be thrown.
+ *
+ * Thus, the returned audio port instance is the result of combining the
+ * following information:
+ * - a unique port ID generated by the HAL module;
+ * - static information from the port template;
+ * - list of audio profiles supported by the connected device;
+ * - additional data from the input AudioPort parameter.
+ *
+ * The HAL module must also update the list of audio routes to include the
+ * ID of the instantiated connected device port. Normally, the connected
+ * port allows the same routing as the port template.
+ *
+ * Also see notes on 'ModuleDebug.simulateDeviceConnections'.
+ *
+ * The following protocol is used by HAL module client for handling
+ * connection of an external device:
+ * 1. Obtain the list of device ports and their IDs via 'getAudioPorts'
+ * method. Select the appropriate port template using
+ * AudioDeviceDescription ('ext.device' field of AudioPort).
+ * 2. Combine the ID of the port template with any additional data and call
+ * 'connectExternalDevice'. The HAL module returns a new instance of
+ * AudioPort created using the rules explained above. Both
+ * 'getAudioPort' and 'getAudioPorts' methods will be returning the same
+ * information for this port until disconnection.
+ * 3. Configure the connected port with one of supported profiles using
+ * 'setAudioPortConfig'.
+ * 4. Query the list of AudioRoutes for the new AudioPort using
+ * 'getAudioRoutesForAudioPort' or 'getAudioRoutes' methods.
+ *
+ * External devices are distinguished by the connection type and device
+ * address. Calling this method multiple times to inform about connection of
+ * the same external device without disconnecting it first is an error.
+ *
+ * The HAL module must perform validation of the input parameter and throw
+ * an error if it is lacking required information, for example, when no
+ * device address is specified for a point-to-multipoint external device
+ * connection.
+ *
+ * Handling of a disconnect is done in a reverse order:
+ * 1. Reset port configuration using the 'resetAudioPortConfig' method.
+ * 2. Release the connected device port by calling the 'disconnectExternalDevice'
+ * method. This also removes the audio routes associated with this
+ * device port.
+ *
+ * @return New instance of an audio port for the connected external device.
+ * @param templateIdAndAdditionalData Specifies port template ID and any
+ * additional data.
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the template port can not be found by the ID.
+ * - If the template is not a device port, or
+ * it does not have dynamic profiles.
+ * - If the input parameter is lacking required
+ * information.
+ * @throws EX_ILLEGAL_STATE In the following cases:
+ * - If the HAL module is unable to query audio profiles.
+ * - If the external device has already been connected.
+ */
+ AudioPort connectExternalDevice(in AudioPort templateIdAndAdditionalData);
+
+ /**
+ * Set a device port of a an external device into disconnected state.
+ *
+ * This method is used to inform the HAL module that an external device has
+ * been disconnected. The 'portId' must be of a connected device port
+ * instance previously instantiated using the 'connectExternalDevice'
+ * method.
+ *
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the port can not be found by the ID.
+ * - If this is not a connected device port.
+ * @throws EX_ILLEGAL_STATE If the port has active configurations.
+ */
+ void disconnectExternalDevice(int portId);
+
+ /**
+ * Return all audio patches of this module.
+ *
+ * Returns a list of audio patches, that is, established connections between
+ * audio port configurations.
+ *
+ * @return The list of audio patches.
+ */
+ AudioPatch[] getAudioPatches();
+
+ /**
+ * Return the current state of the audio port.
+ *
+ * Using the port ID provided on input, returns the current state of the
+ * audio port. The values of the AudioPort structure must be the same as
+ * currently returned by the 'getAudioPorts' method. The 'getAudioPort'
+ * method is provided to reduce overhead in the case when the client needs
+ * to check the state of one port only.
+ *
+ * @return The current state of an audio port.
+ * @param portId The ID of the audio port.
+ * @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID.
+ */
+ AudioPort getAudioPort(int portId);
+
+ /**
+ * Return all active audio port configurations of this module.
+ *
+ * Returns a list of active configurations that are currently set for mix
+ * ports and device ports. Each returned configuration must have an unique
+ * ID within this module ('AudioPortConfig.id' field), which can coincide
+ * with an ID of an audio port, if the port only supports a single active
+ * configuration. Each returned configuration must also have a reference to
+ * an existing port ('AudioPortConfig.portId' field). All optional
+ * (nullable) fields of the configurations must be initialized by the HAL
+ * module.
+ *
+ * @return The list of active audio port configurations.
+ */
+ AudioPortConfig[] getAudioPortConfigs();
+
+ /**
+ * Return the current state of all audio ports provided by this module.
+ *
+ * Returns a list of all mix ports and device ports provided by this HAL
+ * module, reflecting their current states. Each returned port must have a
+ * unique ID within this module ('AudioPort.id' field). The list also
+ * includes "connected" ports created using 'connectExternalDevice' method.
+ *
+ * @return The list of audio ports.
+ */
+ AudioPort[] getAudioPorts();
+
+ /**
+ * Return all current audio routes of this module.
+ *
+ * Returns the current list of audio routes, that is, allowed connections
+ * between audio ports. The list can change when new device audio ports
+ * get created as a result of connecting or disconnecting of external
+ * devices.
+ *
+ * @return The list of audio routes.
+ */
+ AudioRoute[] getAudioRoutes();
+
+ /**
+ * Return audio routes related to the specified audio port.
+ *
+ * Returns the list of audio routes that include the specified port ID
+ * as a source or as a sink. The returned list is a subset of the result
+ * returned by the 'getAudioRoutes' method, filtered by the port ID.
+ * An empty list can be returned, indicating that the audio port can not
+ * be used for creating audio patches.
+ *
+ * @return The list of audio routes.
+ * @param portId The ID of the audio port.
+ * @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID.
+ */
+ AudioRoute[] getAudioRoutesForAudioPort(int portId);
+
+ /**
+ * Open an input stream using an existing audio mix port configuration.
+ *
+ * The audio port configuration ID must be obtained by calling
+ * 'setAudioPortConfig' method. Existence of an audio patch involving this
+ * port configuration is not required for successful opening of a stream.
+ *
+ * The requested buffer size is expressed in frames, thus the actual size
+ * in bytes depends on the audio port configuration. Also, the HAL module
+ * may end up providing a larger buffer, thus the requested size is treated
+ * as the minimum size that the client needs. The minimum buffer size
+ * suggested by the HAL is in the 'AudioPatch.minimumStreamBufferSizeFrames'
+ * field, returned as a result of calling the 'setAudioPatch' method.
+ *
+ * Only one stream is allowed per audio port configuration. HAL module can
+ * also set a limit on how many output streams can be opened for a particular
+ * mix port by using its 'AudioPortMixExt.maxOpenStreamCount' field.
+ *
+ * Note that although it's not prohibited to open a stream on a mix port
+ * configuration which is not connected (using a patch) to any device port,
+ * and set up a patch afterwards, this sequence of calls is not recommended,
+ * because setting up of a patch might fail due to an insufficient stream
+ * buffer size. Another consequence of having a stream on an unconnected mix
+ * port is that capture positions can not be determined because there is no
+ * "external observer," thus read operations done via StreamDescriptor will
+ * be completing with an error, although data (zero filled) will still be
+ * provided.
+ *
+ * @return An opened input stream and the associated descriptor.
+ * @param args The pack of arguments, see 'OpenInputStreamArguments' parcelable.
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the port config can not be found by the ID.
+ * - If the port config is not of an input mix port.
+ * - If a buffer of the requested size can not be provided.
+ * @throws EX_ILLEGAL_STATE In the following cases:
+ * - If the port config already has a stream opened on it.
+ * - If the limit on the open stream count for the port has
+ * been reached.
+ * - If the HAL module failed to initialize the stream.
+ */
+ @VintfStability
+ parcelable OpenInputStreamArguments {
+ /** The ID of the audio mix port config. */
+ int portConfigId;
+ /** Description of the audio that will be recorded. */
+ SinkMetadata sinkMetadata;
+ /** Requested audio I/O buffer minimum size, in frames. */
+ long bufferSizeFrames;
+ }
+ @VintfStability
+ parcelable OpenInputStreamReturn {
+ IStreamIn stream;
+ StreamDescriptor desc;
+ }
+ OpenInputStreamReturn openInputStream(in OpenInputStreamArguments args);
+
+ /**
+ * Open an output stream using an existing audio mix port configuration.
+ *
+ * The audio port configuration ID must be obtained by calling
+ * 'setAudioPortConfig' method. Existence of an audio patch involving this
+ * port configuration is not required for successful opening of a stream.
+ *
+ * If the port configuration has 'COMPRESS_OFFLOAD' output flag set,
+ * the framework must provide additional information about the encoded
+ * audio stream in 'offloadInfo' argument.
+ *
+ * The requested buffer size is expressed in frames, thus the actual size
+ * in bytes depends on the audio port configuration. Also, the HAL module
+ * may end up providing a larger buffer, thus the requested size is treated
+ * as the minimum size that the client needs. The minimum buffer size
+ * suggested by the HAL is in the 'AudioPatch.minimumStreamBufferSizeFrames'
+ * field, returned as a result of calling the 'setAudioPatch' method.
+ *
+ * Only one stream is allowed per audio port configuration. HAL module can
+ * also set a limit on how many output streams can be opened for a particular
+ * mix port by using its 'AudioPortMixExt.maxOpenStreamCount' field.
+ * Only one stream can be opened on the audio port with 'PRIMARY' output
+ * flag. This rule can not be overridden with 'maxOpenStreamCount' field.
+ *
+ * Note that although it's not prohibited to open a stream on a mix port
+ * configuration which is not connected (using a patch) to any device port,
+ * and set up a patch afterwards, this sequence of calls is not recommended,
+ * because setting up of a patch might fail due to an insufficient stream
+ * buffer size. Another consequence of having a stream on an unconnected mix
+ * port is that presentation positions can not be determined because there
+ * is no "external observer," thus write operations done via
+ * StreamDescriptor will be completing with an error, although the data
+ * will still be accepted and immediately discarded.
+ *
+ * @return An opened output stream and the associated descriptor.
+ * @param args The pack of arguments, see 'OpenOutputStreamArguments' parcelable.
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the port config can not be found by the ID.
+ * - If the port config is not of an output mix port.
+ * - If the offload info is not provided for an offload
+ * port configuration.
+ * - If a buffer of the requested size can not be provided.
+ * @throws EX_ILLEGAL_STATE In the following cases:
+ * - If the port config already has a stream opened on it.
+ * - If the limit on the open stream count for the port has
+ * been reached.
+ * - If another opened stream already exists for the 'PRIMARY'
+ * output port.
+ * - If the HAL module failed to initialize the stream.
+ */
+ @VintfStability
+ parcelable OpenOutputStreamArguments {
+ /** The ID of the audio mix port config. */
+ int portConfigId;
+ /** Description of the audio that will be played. */
+ SourceMetadata sourceMetadata;
+ /** Additional information used for offloaded playback only. */
+ @nullable AudioOffloadInfo offloadInfo;
+ /** Requested audio I/O buffer minimum size, in frames. */
+ long bufferSizeFrames;
+ }
+ @VintfStability
+ parcelable OpenOutputStreamReturn {
+ IStreamOut stream;
+ StreamDescriptor desc;
+ }
+ OpenOutputStreamReturn openOutputStream(in OpenOutputStreamArguments args);
+
+ /**
+ * Set an audio patch.
+ *
+ * This method creates new or updates an existing audio patch. If the
+ * requested audio patch does not have a specified id, then a new patch is
+ * created and an ID is allocated for it by the HAL module. Otherwise an
+ * attempt to update an existing patch is made.
+ *
+ * The operation of updating an existing audio patch must not change
+ * playback state of audio streams opened on the audio port configurations
+ * of the patch. That is, the HAL module must still be able to consume or
+ * to provide data from / to streams continuously during the patch
+ * switching. Natural intermittent audible loss of some audio frames due to
+ * switching between device ports which does not affect stream playback is
+ * allowed. If the HAL module is unable to avoid playback or recording
+ * state change when updating a certain patch, it must return an error. In
+ * that case, the client must take care of changing port configurations,
+ * patches, and recreating streams in a way which provides an acceptable
+ * user experience.
+ *
+ * Audio port configurations specified in the patch must be obtained by
+ * calling 'setAudioPortConfig' method. There must be an audio route which
+ * allows connection between the audio ports whose configurations are used.
+ *
+ * When updating an existing audio patch, nominal latency values may change
+ * and must be provided by the HAL module in the returned 'AudioPatch'
+ * structure.
+ *
+ * @return Resulting audio patch.
+ * @param requested Requested audio patch.
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the patch is invalid (see AudioPatch).
+ * - If a port config can not be found from the specified IDs.
+ * - If there are no routes satisfying the patch.
+ * - If an existing patch can not be found by the ID.
+ * @throws EX_ILLEGAL_STATE In the following cases:
+ * - If application of the patch can only use a route with an
+ * exclusive use the sink port, and it is already patched.
+ * - If updating an existing patch will cause interruption
+ * of audio, or requires re-opening of streams due to
+ * change of minimum audio I/O buffer size.
+ * @throws EX_UNSUPPORTED_OPERATION If the patch can not be established because
+ * the HAL module does not support this otherwise valid
+ * patch configuration. For example, if it's a patch
+ * between multiple sources and sinks, and the HAL module
+ * does not support this.
+ */
+ AudioPatch setAudioPatch(in AudioPatch requested);
+
+ /**
+ * Set the active configuration of an audio port.
+ *
+ * This method is used to create or update an active configuration for a mix
+ * port or a device port. The port is specified using the
+ * 'AudioPortConfig.portId' field. If the requested audio port
+ * configuration does not have a specified id in the 'AudioPortConfig.id'
+ * field, then a new configuration is created and an ID is allocated for it
+ * by the HAL module. Otherwise an attempt to update an existing port
+ * configuration is made. The HAL module returns the resulting audio port
+ * configuration. Depending on the port and on the capabilities of the HAL
+ * module, it can either update an existing port configuration (same port
+ * configuration ID remains), or create a new one. The resulting port
+ * configuration ID is returned in the 'id' field of the 'suggested'
+ * argument.
+ *
+ * If the specified port configuration can not be set, this method must
+ * return 'false' and provide its own suggestion in the output
+ * parameter. The framework can then set the suggested configuration on a
+ * subsequent retry call to this method.
+ *
+ * Device ports with dynamic audio profiles (an empty list of profiles)
+ * can not be used with this method. The list of profiles must be filled in
+ * as a result of calling 'connectExternalDevice' method.
+ *
+ * @return Whether the requested configuration has been applied.
+ * @param requested Requested audio port configuration.
+ * @param suggested Same as requested configuration, if it was applied.
+ * Suggested audio port configuration if the requested
+ * configuration can't be applied.
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If neither port config ID, nor port ID are specified.
+ * - If an existing port config can not be found by the ID.
+ * - If the port can not be found by the port ID.
+ * - If it is not possible to generate a suggested port
+ * configuration, for example, if the port only has dynamic
+ * profiles.
+ */
+ boolean setAudioPortConfig(in AudioPortConfig requested, out AudioPortConfig suggested);
+
+ /**
+ * Reset the audio patch.
+ *
+ * Resets previously created audio patch using its ID ('AudioPatch.id'). It
+ * is allowed to reset a patch which uses audio port configurations having
+ * associated streams. In this case the mix port becomes disconnected from
+ * the hardware, but the stream does not close.
+ *
+ * @param patchId The ID of the audio patch.
+ * @throws EX_ILLEGAL_ARGUMENT If an existing patch can not be found by the ID.
+ */
+ void resetAudioPatch(int patchId);
+
+ /**
+ * Reset the audio port configuration.
+ *
+ * Resets the specified audio port configuration, discarding all changes
+ * previously done by the framework. That means, if a call to this method is
+ * a success, the effect of all previous calls to 'setAudioPortConfig' which
+ * used or initially have generated the provided 'portConfigId', since the
+ * module start, or since the last call to this method, has been canceled.
+ *
+ * Audio port configurations of mix ports with streams opened on them can
+ * not be reset. Also can not be reset port configurations currently used by
+ * any patches.
+ *
+ * @param portConfigId The ID of the audio port config.
+ * @throws EX_ILLEGAL_ARGUMENT If the port config can not be found by the ID.
+ * @throws EX_ILLEGAL_STATE In the following cases:
+ * - If the port config has a stream opened on it;
+ * - If the port config is used by a patch.
+ */
+ void resetAudioPortConfig(int portConfigId);
+}
diff --git a/audio/aidl/android/hardware/audio/core/IStreamIn.aidl b/audio/aidl/android/hardware/audio/core/IStreamIn.aidl
new file mode 100644
index 0000000..0c3e3d1
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamIn.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.hardware.audio.core;
+
+import android.hardware.audio.common.SinkMetadata;
+
+/**
+ * This interface provides means for receiving audio data from input devices.
+ */
+@VintfStability
+interface IStreamIn {
+ /**
+ * Close the stream.
+ *
+ * Releases any resources allocated for this stream on the HAL module side.
+ * This includes the fast message queues and shared memories returned via
+ * the StreamDescriptor. Thus, the stream can not be operated anymore after
+ * it has been closed. The client needs to release the audio data I/O
+ * objects after the call to this method returns.
+ *
+ * Methods of this interface throw EX_ILLEGAL_STATE for a closed stream.
+ *
+ * @throws EX_ILLEGAL_STATE If the stream has already been closed.
+ */
+ void close();
+
+ /**
+ * Update stream metadata.
+ *
+ * Updates the metadata initially provided at the stream creation.
+ *
+ * @param sinkMetadata Updated metadata.
+ * @throws EX_ILLEGAL_STATE If the stream is closed.
+ */
+ void updateMetadata(in SinkMetadata sinkMetadata);
+}
diff --git a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
new file mode 100644
index 0000000..9fdb37d
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.hardware.audio.core;
+
+import android.hardware.audio.common.SourceMetadata;
+
+/**
+ * This interface provides means for sending audio data to output devices.
+ */
+@VintfStability
+interface IStreamOut {
+ /**
+ * Close the stream.
+ *
+ * Releases any resources allocated for this stream on the HAL module side.
+ * This includes the fast message queues and shared memories returned via
+ * the StreamDescriptor. Thus, the stream can not be operated anymore after
+ * it has been closed. The client needs to release the audio data I/O
+ * objects after the call to this method returns.
+ *
+ * Methods of this interface throw EX_ILLEGAL_STATE for a closed stream.
+ *
+ * @throws EX_ILLEGAL_STATE If the stream has already been closed.
+ */
+ void close();
+
+ /**
+ * Update stream metadata.
+ *
+ * Updates the metadata initially provided at the stream creation.
+ *
+ * @param sourceMetadata Updated metadata.
+ * @throws EX_ILLEGAL_STATE If the stream is closed.
+ */
+ void updateMetadata(in SourceMetadata sourceMetadata);
+}
diff --git a/audio/aidl/android/hardware/audio/core/MmapBufferDescriptor.aidl b/audio/aidl/android/hardware/audio/core/MmapBufferDescriptor.aidl
new file mode 100644
index 0000000..108bcbe
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/MmapBufferDescriptor.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.hardware.audio.core;
+
+import android.hardware.common.Ashmem;
+
+/**
+ * MMap buffer descriptor is used by streams opened in MMap No IRQ mode.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable MmapBufferDescriptor {
+ /**
+ * MMap memory buffer.
+ */
+ Ashmem sharedMemory;
+ /**
+ * Transfer size granularity in frames.
+ */
+ long burstSizeFrames;
+ /**
+ * Attributes describing the buffer. Bitmask indexed by FLAG_INDEX_*
+ * constants.
+ */
+ int flags;
+
+ /**
+ * Whether the buffer can be securely shared to untrusted applications
+ * through the AAudio exclusive mode.
+ *
+ * Only set this flag if applications are restricted from accessing the
+ * memory surrounding the audio data buffer by a kernel mechanism.
+ * See Linux kernel's dma-buf
+ * (https://www.kernel.org/doc/html/v4.16/driver-api/dma-buf.html).
+ */
+ const int FLAG_INDEX_APPLICATION_SHAREABLE = 0;
+}
diff --git a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
new file mode 100644
index 0000000..858a9bd
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.hardware.audio.core;
+
+/**
+ * This structure contains flags used for enabling various debugging aspects
+ * in a HAL module. By default, all debugging aspects are turned off. They
+ * can be enabled during xTS tests for functionality that, for example, would
+ * otherwise require human intervention (e.g. connection of external devices).
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable ModuleDebug {
+ /**
+ * When set to 'true', HAL module must simulate connection of external
+ * devices. An external device becomes 'connected' after a call to
+ * IModule.connectExternalDevice, simulation of connection requires:
+ * - provision of at least one non-dynamic device port profile on
+ * connection (as if it was retrieved from a connected device);
+ * - simulating successful application of port configurations for reported
+ * profiles.
+ */
+ boolean simulateDeviceConnections;
+}
diff --git a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
new file mode 100644
index 0000000..2b1ed8c
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
@@ -0,0 +1,213 @@
+/*
+ * 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.hardware.audio.core;
+
+import android.hardware.audio.core.MmapBufferDescriptor;
+import android.hardware.common.fmq.MQDescriptor;
+import android.hardware.common.fmq.SynchronizedReadWrite;
+
+/**
+ * Stream descriptor contains fast message queues and buffers used for sending
+ * and receiving audio data. The descriptor complements IStream* interfaces by
+ * providing communication channels that serve as an alternative to Binder
+ * transactions.
+ *
+ * Handling of audio data and commands must be done by the HAL module on a
+ * dedicated thread with high priority, for all modes, including MMap No
+ * IRQ. The HAL module is responsible for creating this thread and setting its
+ * priority. The HAL module is also responsible for serializing access to the
+ * internal components of the stream while serving commands invoked via the
+ * stream's AIDL interface and commands invoked via the command queue of the
+ * descriptor.
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable StreamDescriptor {
+ /**
+ * Position binds together a position within the stream and time.
+ *
+ * The timestamp must use "monotonic" clock.
+ *
+ * The frame count must advance between consecutive I/O operations, and stop
+ * advancing when the stream was put into the 'standby' mode. On exiting the
+ * 'standby' mode, the frame count must not reset, but continue counting.
+ */
+ @VintfStability
+ @FixedSize
+ parcelable Position {
+ /** Frame count. */
+ long frames;
+ /** Timestamp in nanoseconds. */
+ long timeNs;
+ }
+
+ /**
+ * The command used for audio I/O, see 'AudioBuffer'. For MMap No IRQ mode
+ * this command only provides updated positions and latency because actual
+ * audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
+ */
+ const int COMMAND_BURST = 1;
+
+ /**
+ * Used for sending commands to the HAL module. The client writes into
+ * the queue, the HAL module reads. The queue can only contain a single
+ * command.
+ */
+ @VintfStability
+ @FixedSize
+ parcelable Command {
+ /**
+ * One of COMMAND_* codes.
+ */
+ int code;
+ /**
+ * For output streams: the amount of bytes that the client requests the
+ * HAL module to read from the 'audio.fmq' queue.
+ * For input streams: the amount of bytes requested by the client to
+ * read from the hardware into the 'audio.fmq' queue.
+ *
+ * In both cases it is allowed for this field to contain any
+ * non-negative number. The value 0 can be used if the client only needs
+ * to retrieve current positions and latency. Any sufficiently big value
+ * which exceeds the size of the queue's area which is currently
+ * available for reading or writing by the HAL module must be trimmed by
+ * the HAL module to the available size. Note that the HAL module is
+ * allowed to consume or provide less data than requested, and it must
+ * return the amount of actually read or written data via the
+ * 'Reply.fmqByteCount' field. Thus, only attempts to pass a negative
+ * number must be constituted as a client's error.
+ */
+ int fmqByteCount;
+ }
+ MQDescriptor<Command, SynchronizedReadWrite> command;
+
+ /**
+ * Used for providing replies to commands. The HAL module writes into
+ * the queue, the client reads. The queue can only contain a single reply,
+ * corresponding to the last command sent by the client.
+ */
+ @VintfStability
+ @FixedSize
+ parcelable Reply {
+ /**
+ * One of Binder STATUS_* statuses:
+ * - STATUS_OK: the command has completed successfully;
+ * - STATUS_BAD_VALUE: invalid value in the 'Command' structure;
+ * - STATUS_INVALID_OPERATION: the mix port is not connected
+ * to any producer or consumer, thus
+ * positions can not be reported;
+ * - STATUS_NOT_ENOUGH_DATA: a read or write error has
+ * occurred for the 'audio.fmq' queue;
+ *
+ */
+ int status;
+ /**
+ * For output streams: the amount of bytes actually consumed by the HAL
+ * module from the 'audio.fmq' queue.
+ * For input streams: the amount of bytes actually provided by the HAL
+ * in the 'audio.fmq' queue.
+ *
+ * The returned value must not exceed the value passed in the
+ * 'fmqByteCount' field of the corresponding command or be negative.
+ */
+ int fmqByteCount;
+ /**
+ * For output streams: the moment when the specified stream position
+ * was presented to an external observer (i.e. presentation position).
+ * For input streams: the moment when data at the specified stream position
+ * was acquired (i.e. capture position).
+ */
+ Position observable;
+ /**
+ * Used only for MMap streams to provide the hardware read / write
+ * position for audio data in the shared memory buffer 'audio.mmap'.
+ */
+ Position hardware;
+ /**
+ * Current latency reported by the hardware.
+ */
+ int latencyMs;
+ }
+ MQDescriptor<Reply, SynchronizedReadWrite> reply;
+
+ /**
+ * The size of one frame of audio data in bytes. For PCM formats this is
+ * usually equal to the size of a sample multiplied by the number of
+ * channels used. For encoded bitstreams encapsulated into PCM the sample
+ * size of the underlying PCM stream is used. For encoded bitstreams that
+ * are passed without encapsulation, the frame size is usually 1 byte.
+ */
+ int frameSizeBytes;
+ /**
+ * Total buffer size in frames. This applies both to the size of the 'audio.fmq'
+ * queue and to the size of the shared memory buffer for MMap No IRQ streams.
+ * Note that this size may end up being slightly larger than the size requested
+ * in a call to 'IModule.openInputStream' or 'openOutputStream' due to memory
+ * alignment requirements.
+ */
+ long bufferSizeFrames;
+
+ /**
+ * Used for sending or receiving audio data to/from the stream. In the case
+ * of MMap No IRQ streams this structure only contains the information about
+ * the shared memory buffer. Audio data is sent via the shared buffer
+ * directly.
+ */
+ @VintfStability
+ union AudioBuffer {
+ /**
+ * The fast message queue used for all modes except MMap No IRQ. Both
+ * reads and writes into this queue are non-blocking because access to
+ * this queue is synchronized via the 'command' and 'reply' queues as
+ * described below. The queue nevertheless uses 'SynchronizedReadWrite'
+ * because there is only one reader, and the reading position must be
+ * shared.
+ *
+ * For output streams the following sequence of operations is used:
+ * 1. The client writes audio data into the 'audio.fmq' queue.
+ * 2. The client writes the 'BURST' command into the 'command' queue,
+ * and hangs on waiting on a read from the 'reply' queue.
+ * 3. The high priority thread in the HAL module wakes up due to 2.
+ * 4. The HAL module reads the command and audio data.
+ * 5. The HAL module writes the command status and current positions
+ * into 'reply' queue, and hangs on waiting on a read from
+ * the 'command' queue.
+ * 6. The client wakes up due to 5. and reads the reply.
+ *
+ * For input streams the following sequence of operations is used:
+ * 1. The client writes the 'BURST' command into the 'command' queue,
+ * and hangs on waiting on a read from the 'reply' queue.
+ * 2. The high priority thread in the HAL module wakes up due to 1.
+ * 3. The HAL module writes audio data into the 'audio.fmq' queue.
+ * 4. The HAL module writes the command status and current positions
+ * into 'reply' queue, and hangs on waiting on a read from
+ * the 'command' queue.
+ * 5. The client wakes up due to 4.
+ * 6. The client reads the reply and audio data.
+ */
+ MQDescriptor<byte, SynchronizedReadWrite> fmq;
+ /**
+ * MMap buffers are shared directly with the DSP, which operates
+ * independently from the CPU. Writes and reads into these buffers
+ * are not synchronized with 'command' and 'reply' queues. However,
+ * the client still uses the 'BURST' command for obtaining current
+ * positions from the HAL module.
+ */
+ MmapBufferDescriptor mmap;
+ }
+ AudioBuffer audio;
+}
diff --git a/audio/aidl/android/hardware/audio/effect/Descriptor.aidl b/audio/aidl/android/hardware/audio/effect/Descriptor.aidl
new file mode 100644
index 0000000..51b31c2
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/effect/Descriptor.aidl
@@ -0,0 +1,82 @@
+/*
+ * 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.hardware.audio.effect;
+
+import android.media.audio.common.AudioUuid;
+
+/**
+ * Effect descriptor contains all information (capabilities, attributes, and ownership) for an
+ * effect implemented in the Audio Effect HAL. Framework uses this information to decide when and
+ * how to apply the effect.
+ */
+@VintfStability
+parcelable Descriptor {
+ /**
+ * UUID for effect types, these definitions are in sync with SDK, see @c AudioEffect.java.
+ */
+ // UUID for environmental reverberation effect type.
+ const String EFFECT_TYPE_UUID_ENV_REVERB = "c2e5d5f0-94bd-4763-9cac-4e234d06839e";
+ // UUID for preset reverberation effect type.
+ const String EFFECT_TYPE_UUID_PRESET_REVERB = "47382d60-ddd8-11db-bf3a-0002a5d5c51b";
+ // UUID for equalizer effect type.
+ const String EFFECT_TYPE_UUID_EQUALIZER = "0bed4300-ddd6-11db-8f34-0002a5d5c51b";
+ // UUID for bass boost effect type.
+ const String EFFECT_TYPE_UUID_BASS_BOOST = "0634f220-ddd4-11db-a0fc-0002a5d5c51b";
+ // UUID for virtualizer effect type.
+ const String EFFECT_TYPE_UUID_VIRTUALIZER = "37cc2c00-dddd-11db-8577-0002a5d5c51b";
+ // UUID for Automatic Gain Control (AGC) type.
+ const String EFFECT_TYPE_UUID_AGC = "0a8abfe0-654c-11e0-ba26-0002a5d5c51b";
+ // UUID for Acoustic Echo Canceler (AEC) type.
+ const String EFFECT_TYPE_UUID_AEC = "7b491460-8d4d-11e0-bd61-0002a5d5c51b";
+ // UUID for Noise Suppressor (NS) type.
+ const String EFFECT_TYPE_UUID_NS = "58b4b260-8e06-11e0-aa8e-0002a5d5c51b";
+ // UUID for Loudness Enhancer type.
+ const String EFFECT_TYPE_UUID_LOUDNESS_ENHANCER = "fe3199be-aed0-413f-87bb-11260eb63cf1";
+ // UUID for Dynamics Processing type.
+ const String EFFECT_TYPE_UUID_DYNAMICS_PROCESSING = "7261676f-6d75-7369-6364-28e2fd3ac39e";
+ // UUID for Haptic Generator type.
+ const String EFFECT_TYPE_UUID_HAPTIC_GENERATOR = "1411e6d6-aecd-4021-a1cf-a6aceb0d71e5";
+ // UUID for Spatializer type.
+ const String EFFECT_TYPE_UUID_SPATIALIZER = "ccd4cf09-a79d-46c2-9aae-06a1698d6c8f";
+ // UUID for Volume type. The volume effect is used for automated tests only.
+ const String EFFECT_TYPE_UUID_VOLUME = "09e8ede0-ddde-11db-b4f6-0002a5d5c51b";
+
+ /**
+ * This structure completely identifies an effect implementation.
+ */
+ @VintfStability
+ parcelable Identity {
+ /**
+ * UUID for the type of effect.
+ */
+ AudioUuid type;
+ /**
+ * UUID for this particular implementation.
+ */
+ AudioUuid uuid;
+ }
+
+ // Common attributes of all effect implementation.
+ @VintfStability
+ parcelable Common {
+ /**
+ * Identity of effect implementation.
+ */
+ Identity id;
+ }
+ Common common;
+}
diff --git a/audio/aidl/android/hardware/audio/effect/IEffect.aidl b/audio/aidl/android/hardware/audio/effect/IEffect.aidl
new file mode 100644
index 0000000..d7a9501
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/effect/IEffect.aidl
@@ -0,0 +1,65 @@
+/*
+ * 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.hardware.audio.effect;
+
+import android.hardware.audio.effect.Descriptor;
+
+/**
+ * Effect interfaces definitions to configure and control the effect instance.
+ */
+@VintfStability
+interface IEffect {
+ /**
+ * Open an effect instance, effect should not start processing data before receive START
+ * command. All necessary information should be allocated and instance should transfer to IDLE
+ * state after open() call has been handled successfully.
+ * After open, the effect instance should be able to handle all IEffect interface calls.
+ *
+ * @throws a EX_UNSUPPORTED_OPERATION if device capability/resource is not enough or system
+ * failure happens.
+ * @note Open an already-opened effect instance should do nothing and should not throw an error.
+ */
+ void open();
+
+ /**
+ * Called by the client to close the effect instance, processing thread should be destroyed and
+ * consume no CPU after close.
+ *
+ * It is recommended to close the effect on the client side as soon as it becomes unused, it's
+ * client responsibility to make sure all parameter/buffer is correct if client wants to reopen
+ * a closed instance.
+ *
+ * Effect instance close interface should always succeed unless:
+ * 1. The effect instance is not in a proper state to be closed, for example it's still in
+ * processing state.
+ * 2. There is system/hardware related failure when close.
+ *
+ * @throws EX_ILLEGAL_STATE if the effect instance is not in a proper state to be closed.
+ * @throws EX_UNSUPPORTED_OPERATION if the effect instance failed to close for any other reason.
+ * @note Close an already-closed effect should do nothing and should not throw an error.
+ */
+ void close();
+
+ /**
+ * Return the @c Descriptor of this effect instance.
+ *
+ * Must be available for the effect instance at anytime and should always succeed.
+ *
+ * @return Descriptor The @c Descriptor of this effect instance.
+ */
+ Descriptor getDescriptor();
+}
diff --git a/audio/aidl/android/hardware/audio/effect/IFactory.aidl b/audio/aidl/android/hardware/audio/effect/IFactory.aidl
new file mode 100644
index 0000000..4873d3a
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/effect/IFactory.aidl
@@ -0,0 +1,72 @@
+/*
+ * 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.hardware.audio.effect;
+
+import android.hardware.audio.effect.Descriptor;
+import android.hardware.audio.effect.IEffect;
+import android.media.audio.common.AudioUuid;
+
+/**
+ * Provides system-wide effect factory interfaces.
+ *
+ * An android.hardware.audio.effect.IFactory platform service is registered with ServiceManager, and
+ * is always available on the device.
+ *
+ */
+@VintfStability
+interface IFactory {
+ /**
+ * Return a list of effect identities supported by this device, with the optional
+ * filter by type and/or by instance UUID.
+ *
+ * @param type UUID identifying the effect type.
+ * This is an optional parameter, pass in null if this parameter is not necessary; if non
+ * null, used as a filter for effect type UUIDs.
+ * @param implementation Indicates the particular implementation of the effect in that type.
+ * This is an optional parameter, pass in null if this parameter is not necessary; if
+ * non null, used as a filter for effect type UUIDs.
+ * @return List of effect identities supported and filtered by type/implementation UUID.
+ */
+ Descriptor.Identity[] queryEffects(
+ in @nullable AudioUuid type, in @nullable AudioUuid implementation);
+
+ /**
+ * Called by the audio framework to create the effect (identified by the implementation UUID
+ * parameter).
+ *
+ * The effect instance should be able to maintain its own context and parameters after creation.
+ *
+ * @param implUuid UUID for the effect implementation which instance will be created based on.
+ * @return The effect instance handle created.
+ * @throws EX_ILLEGAL_ARGUMENT if the implUuid is not valid.
+ * @throws EX_TRANSACTION_FAILED if device capability/resource is not enough to create the
+ * effect instance.
+ */
+ IEffect createEffect(in AudioUuid implUuid);
+
+ /**
+ * Called by the framework to destroy the effect and free up all currently allocated resources.
+ * It is recommended to destroy the effect from the client side as soon as it is becomes unused.
+ *
+ * The client must ensure effect instance is closed before destroy.
+ *
+ * @param handle The handle of effect instance to be destroyed.
+ * @throws EX_ILLEGAL_ARGUMENT if the effect handle is not valid.
+ * @throws EX_ILLEGAL_STATE if the effect instance is not in a proper state to be destroyed.
+ */
+ void destroyEffect(in IEffect handle);
+}
diff --git a/audio/aidl/common/Android.bp b/audio/aidl/common/Android.bp
new file mode 100644
index 0000000..a3f7f0b
--- /dev/null
+++ b/audio/aidl/common/Android.bp
@@ -0,0 +1,68 @@
+/*
+ * 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_library {
+ name: "libaudioaidlcommon",
+ host_supported: true,
+ vendor_available: true,
+ export_include_dirs: ["include"],
+ header_libs: [
+ "libbase_headers",
+ "libsystem_headers",
+ ],
+ export_header_lib_headers: [
+ "libbase_headers",
+ "libsystem_headers",
+ ],
+ srcs: [
+ "StreamWorker.cpp",
+ ],
+}
+
+cc_test {
+ name: "libaudioaidlcommon_test",
+ host_supported: true,
+ vendor_available: true,
+ static_libs: [
+ "android.media.audio.common.types-V1-ndk",
+ "libaudioaidlcommon",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "tests/streamworker_tests.cpp",
+ "tests/utils_tests.cpp",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/audio/aidl/common/StreamWorker.cpp b/audio/aidl/common/StreamWorker.cpp
new file mode 100644
index 0000000..9bca760
--- /dev/null
+++ b/audio/aidl/common/StreamWorker.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+#include <pthread.h>
+#include <sched.h>
+#include <sys/resource.h>
+
+#include "include/StreamWorker.h"
+
+namespace android::hardware::audio::common::internal {
+
+bool ThreadController::start(const std::string& name, int priority) {
+ mThreadName = name;
+ mThreadPriority = priority;
+ mWorker = std::thread(&ThreadController::workerThread, this);
+ std::unique_lock<std::mutex> lock(mWorkerLock);
+ android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
+ mWorkerCv.wait(lock, [&]() {
+ android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
+ return mWorkerState == WorkerState::RUNNING || !mError.empty();
+ });
+ mWorkerStateChangeRequest = false;
+ return mWorkerState == WorkerState::RUNNING;
+}
+
+void ThreadController::stop() {
+ {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ if (mWorkerState != WorkerState::STOPPED) {
+ mWorkerState = WorkerState::STOPPED;
+ mWorkerStateChangeRequest = true;
+ }
+ }
+ if (mWorker.joinable()) {
+ mWorker.join();
+ }
+}
+
+bool ThreadController::waitForAtLeastOneCycle() {
+ WorkerState newState;
+ switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED, &newState);
+ if (newState != WorkerState::PAUSED) return false;
+ switchWorkerStateSync(newState, WorkerState::RESUME_REQUESTED, &newState);
+ return newState == WorkerState::RUNNING;
+}
+
+void ThreadController::switchWorkerStateSync(WorkerState oldState, WorkerState newState,
+ WorkerState* finalState) {
+ std::unique_lock<std::mutex> lock(mWorkerLock);
+ android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
+ if (mWorkerState != oldState) {
+ if (finalState) *finalState = mWorkerState;
+ return;
+ }
+ mWorkerState = newState;
+ mWorkerStateChangeRequest = true;
+ mWorkerCv.wait(lock, [&]() {
+ android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
+ return mWorkerState != newState;
+ });
+ if (finalState) *finalState = mWorkerState;
+}
+
+void ThreadController::workerThread() {
+ using Status = StreamLogic::Status;
+
+ std::string error = mLogic->init();
+ if (error.empty() && !mThreadName.empty()) {
+ std::string compliantName(mThreadName.substr(0, 15));
+ if (int errCode = pthread_setname_np(pthread_self(), compliantName.c_str()); errCode != 0) {
+ error.append("Failed to set thread name: ").append(strerror(errCode));
+ }
+ }
+ if (error.empty() && mThreadPriority != ANDROID_PRIORITY_DEFAULT) {
+ if (int result = setpriority(PRIO_PROCESS, 0, mThreadPriority); result != 0) {
+ int errCode = errno;
+ error.append("Failed to set thread priority: ").append(strerror(errCode));
+ }
+ }
+ {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ mWorkerState = error.empty() ? WorkerState::RUNNING : WorkerState::STOPPED;
+ mError = error;
+ }
+ mWorkerCv.notify_one();
+ if (!error.empty()) return;
+
+ for (WorkerState state = WorkerState::RUNNING; state != WorkerState::STOPPED;) {
+ bool needToNotify = false;
+ if (Status status = state != WorkerState::PAUSED ? mLogic->cycle()
+ : (sched_yield(), Status::CONTINUE);
+ status == Status::CONTINUE) {
+ {
+ // See https://developer.android.com/training/articles/smp#nonracing
+ android::base::ScopedLockAssertion lock_assertion(mWorkerLock);
+ if (!mWorkerStateChangeRequest.load(std::memory_order_relaxed)) continue;
+ }
+ //
+ // Pause and resume are synchronous. One worker cycle must complete
+ // before the worker indicates a state change. This is how 'mWorkerState' and
+ // 'state' interact:
+ //
+ // mWorkerState == RUNNING
+ // client sets mWorkerState := PAUSE_REQUESTED
+ // last workerCycle gets executed, state := mWorkerState := PAUSED by us
+ // (or the workers enters the 'error' state if workerCycle fails)
+ // client gets notified about state change in any case
+ // thread is doing a busy wait while 'state == PAUSED'
+ // client sets mWorkerState := RESUME_REQUESTED
+ // state := mWorkerState (RESUME_REQUESTED)
+ // mWorkerState := RUNNING, but we don't notify the client yet
+ // first workerCycle gets executed, the code below triggers a client notification
+ // (or if workerCycle fails, worker enters 'error' state and also notifies)
+ // state := mWorkerState (RUNNING)
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ if (state == WorkerState::RESUME_REQUESTED) {
+ needToNotify = true;
+ }
+ state = mWorkerState;
+ if (mWorkerState == WorkerState::PAUSE_REQUESTED) {
+ state = mWorkerState = WorkerState::PAUSED;
+ needToNotify = true;
+ } else if (mWorkerState == WorkerState::RESUME_REQUESTED) {
+ mWorkerState = WorkerState::RUNNING;
+ }
+ } else {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ if (state == WorkerState::RESUME_REQUESTED ||
+ mWorkerState == WorkerState::PAUSE_REQUESTED) {
+ needToNotify = true;
+ }
+ state = mWorkerState = WorkerState::STOPPED;
+ if (status == Status::ABORT) {
+ mError = "Received ABORT from the logic cycle";
+ }
+ }
+ if (needToNotify) {
+ {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ mWorkerStateChangeRequest = false;
+ }
+ mWorkerCv.notify_one();
+ }
+ }
+}
+
+} // namespace android::hardware::audio::common::internal
diff --git a/audio/aidl/common/TEST_MAPPING b/audio/aidl/common/TEST_MAPPING
new file mode 100644
index 0000000..9dcf44e
--- /dev/null
+++ b/audio/aidl/common/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libaudioaidlcommon_test"
+ }
+ ]
+}
diff --git a/audio/aidl/common/include/StreamWorker.h b/audio/aidl/common/include/StreamWorker.h
new file mode 100644
index 0000000..6260eca
--- /dev/null
+++ b/audio/aidl/common/include/StreamWorker.h
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <android-base/thread_annotations.h>
+#include <system/thread_defs.h>
+
+namespace android::hardware::audio::common {
+
+class StreamLogic;
+
+namespace internal {
+
+class ThreadController {
+ enum class WorkerState { STOPPED, RUNNING, PAUSE_REQUESTED, PAUSED, RESUME_REQUESTED };
+
+ public:
+ explicit ThreadController(StreamLogic* logic) : mLogic(logic) {}
+ ~ThreadController() { stop(); }
+
+ bool start(const std::string& name, int priority);
+ void pause() { switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED); }
+ void resume() { switchWorkerStateSync(WorkerState::PAUSED, WorkerState::RESUME_REQUESTED); }
+ bool hasError() {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ return !mError.empty();
+ }
+ std::string getError() {
+ std::lock_guard<std::mutex> lock(mWorkerLock);
+ return mError;
+ }
+ void stop();
+ bool waitForAtLeastOneCycle();
+
+ // Only used by unit tests.
+ void lockUnlockMutex(bool lock) NO_THREAD_SAFETY_ANALYSIS {
+ lock ? mWorkerLock.lock() : mWorkerLock.unlock();
+ }
+ std::thread::native_handle_type getThreadNativeHandle() { return mWorker.native_handle(); }
+
+ private:
+ void switchWorkerStateSync(WorkerState oldState, WorkerState newState,
+ WorkerState* finalState = nullptr);
+ void workerThread();
+
+ StreamLogic* const mLogic;
+ std::string mThreadName;
+ int mThreadPriority = ANDROID_PRIORITY_DEFAULT;
+ std::thread mWorker;
+ std::mutex mWorkerLock;
+ std::condition_variable mWorkerCv;
+ WorkerState mWorkerState GUARDED_BY(mWorkerLock) = WorkerState::STOPPED;
+ std::string mError GUARDED_BY(mWorkerLock);
+ // The atomic lock-free variable is used to prevent priority inversions
+ // that can occur when a high priority worker tries to acquire the lock
+ // which has been taken by a lower priority control thread which in its turn
+ // got preempted. To prevent a PI under normal operating conditions, that is,
+ // when there are no errors or state changes, the worker does not attempt
+ // taking `mWorkerLock` unless `mWorkerStateChangeRequest` is set.
+ // To make sure that updates to `mWorkerState` and `mWorkerStateChangeRequest`
+ // are serialized, they are always made under a lock.
+ static_assert(std::atomic<bool>::is_always_lock_free);
+ std::atomic<bool> mWorkerStateChangeRequest GUARDED_BY(mWorkerLock) = false;
+};
+
+} // namespace internal
+
+class StreamLogic {
+ public:
+ friend class internal::ThreadController;
+
+ virtual ~StreamLogic() = default;
+
+ protected:
+ enum class Status { ABORT, CONTINUE, EXIT };
+
+ /* Called once at the beginning of the thread loop. Must return
+ * an empty string to enter the thread loop, otherwise the thread loop
+ * exits and the worker switches into the 'error' state, setting
+ * the error to the returned value.
+ */
+ virtual std::string init() = 0;
+
+ /* Called for each thread loop unless the thread is in 'paused' state.
+ * Must return 'CONTINUE' to continue running, otherwise the thread loop
+ * exits. If the result from worker cycle is 'ABORT' then the worker switches
+ * into the 'error' state with a generic error message. It is recommended that
+ * the subclass reports any problems via logging facilities. Returning the 'EXIT'
+ * status is equivalent to calling 'stop()' method. This is just a way of
+ * of stopping the worker by its own initiative.
+ */
+ virtual Status cycle() = 0;
+};
+
+template <class LogicImpl>
+class StreamWorker : public LogicImpl {
+ public:
+ template <class... Args>
+ explicit StreamWorker(Args&&... args) : LogicImpl(std::forward<Args>(args)...), mThread(this) {}
+
+ // Methods of LogicImpl are available via inheritance.
+ // Forwarded methods of ThreadController follow.
+
+ // Note that 'priority' here is what is known as the 'nice number' in *nix systems.
+ // The nice number is used with the default scheduler. For threads that
+ // need to use a specialized scheduler (e.g. SCHED_FIFO) and set the priority within it,
+ // it is recommended to implement an appropriate configuration sequence within
+ // 'LogicImpl' or 'StreamLogic::init'.
+ bool start(const std::string& name = "", int priority = ANDROID_PRIORITY_DEFAULT) {
+ return mThread.start(name, priority);
+ }
+ void pause() { mThread.pause(); }
+ void resume() { mThread.resume(); }
+ bool hasError() { return mThread.hasError(); }
+ std::string getError() { return mThread.getError(); }
+ void stop() { return mThread.stop(); }
+ bool waitForAtLeastOneCycle() { return mThread.waitForAtLeastOneCycle(); }
+
+ // Only used by unit tests.
+ void testLockUnlockMutex(bool lock) { mThread.lockUnlockMutex(lock); }
+ std::thread::native_handle_type testGetThreadNativeHandle() {
+ return mThread.getThreadNativeHandle();
+ }
+
+ private:
+ // The ThreadController gets destroyed before LogicImpl.
+ // After the controller has been destroyed, it is guaranteed that
+ // the thread was joined, thus the 'cycle' method of LogicImpl
+ // will not be called anymore, and it is safe to destroy LogicImpl.
+ internal::ThreadController mThread;
+};
+
+} // namespace android::hardware::audio::common
diff --git a/audio/aidl/common/include/Utils.h b/audio/aidl/common/include/Utils.h
new file mode 100644
index 0000000..1026134
--- /dev/null
+++ b/audio/aidl/common/include/Utils.h
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <initializer_list>
+#include <type_traits>
+
+#include <aidl/android/media/audio/common/AudioChannelLayout.h>
+#include <aidl/android/media/audio/common/AudioFormatDescription.h>
+#include <aidl/android/media/audio/common/AudioInputFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+#include <aidl/android/media/audio/common/PcmType.h>
+
+namespace android::hardware::audio::common {
+
+constexpr size_t getPcmSampleSizeInBytes(::aidl::android::media::audio::common::PcmType pcm) {
+ using ::aidl::android::media::audio::common::PcmType;
+ switch (pcm) {
+ case PcmType::UINT_8_BIT:
+ return 1;
+ case PcmType::INT_16_BIT:
+ return 2;
+ case PcmType::INT_32_BIT:
+ return 4;
+ case PcmType::FIXED_Q_8_24:
+ return 4;
+ case PcmType::FLOAT_32_BIT:
+ return 4;
+ case PcmType::INT_24_BIT:
+ return 3;
+ }
+ return 0;
+}
+
+constexpr size_t getChannelCount(
+ const ::aidl::android::media::audio::common::AudioChannelLayout& layout) {
+ using Tag = ::aidl::android::media::audio::common::AudioChannelLayout::Tag;
+ switch (layout.getTag()) {
+ case Tag::none:
+ return 0;
+ case Tag::invalid:
+ return 0;
+ case Tag::indexMask:
+ return __builtin_popcount(layout.get<Tag::indexMask>());
+ case Tag::layoutMask:
+ return __builtin_popcount(layout.get<Tag::layoutMask>());
+ case Tag::voiceMask:
+ return __builtin_popcount(layout.get<Tag::voiceMask>());
+ }
+ return 0;
+}
+
+constexpr size_t getFrameSizeInBytes(
+ const ::aidl::android::media::audio::common::AudioFormatDescription& format,
+ const ::aidl::android::media::audio::common::AudioChannelLayout& layout) {
+ if (format == ::aidl::android::media::audio::common::AudioFormatDescription{}) {
+ // Unspecified format.
+ return 0;
+ }
+ using ::aidl::android::media::audio::common::AudioFormatType;
+ if (format.type == AudioFormatType::PCM) {
+ return getPcmSampleSizeInBytes(format.pcm) * getChannelCount(layout);
+ } else if (format.type == AudioFormatType::NON_PCM) {
+ // For non-PCM formats always use the underlying PCM size. The default value for
+ // PCM is "UINT_8_BIT", thus non-encapsulated streams have the frame size of 1.
+ return getPcmSampleSizeInBytes(format.pcm);
+ }
+ // Something unexpected.
+ return 0;
+}
+
+// The helper functions defined below are only applicable to the case when an enum type
+// specifies zero-based bit positions, not bit masks themselves. This is why instantiation
+// is restricted to certain enum types.
+template <typename E>
+using is_bit_position_enum = std::integral_constant<
+ bool, std::is_same_v<E, ::aidl::android::media::audio::common::AudioInputFlags> ||
+ std::is_same_v<E, ::aidl::android::media::audio::common::AudioOutputFlags>>;
+
+template <typename E, typename U = std::underlying_type_t<E>,
+ typename = std::enable_if_t<is_bit_position_enum<E>::value>>
+constexpr U makeBitPositionFlagMask(E flag) {
+ return 1 << static_cast<U>(flag);
+}
+
+template <typename E, typename U = std::underlying_type_t<E>,
+ typename = std::enable_if_t<is_bit_position_enum<E>::value>>
+constexpr bool isBitPositionFlagSet(U mask, E flag) {
+ return (mask & makeBitPositionFlagMask(flag)) != 0;
+}
+
+template <typename E, typename U = std::underlying_type_t<E>,
+ typename = std::enable_if_t<is_bit_position_enum<E>::value>>
+constexpr U makeBitPositionFlagMask(std::initializer_list<E> flags) {
+ U result = 0;
+ for (const auto flag : flags) {
+ result |= makeBitPositionFlagMask(flag);
+ }
+ return result;
+}
+
+} // namespace android::hardware::audio::common
diff --git a/audio/aidl/common/tests/streamworker_tests.cpp b/audio/aidl/common/tests/streamworker_tests.cpp
new file mode 100644
index 0000000..e3e484d
--- /dev/null
+++ b/audio/aidl/common/tests/streamworker_tests.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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.
+ */
+
+#include <pthread.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include <atomic>
+
+#include <StreamWorker.h>
+
+#include <gtest/gtest.h>
+#define LOG_TAG "StreamWorker_Test"
+#include <log/log.h>
+
+using android::hardware::audio::common::StreamLogic;
+using android::hardware::audio::common::StreamWorker;
+
+class TestWorkerLogic : public StreamLogic {
+ public:
+ struct Stream {
+ void setErrorStatus() { status = Status::ABORT; }
+ void setStopStatus() { status = Status::EXIT; }
+ std::atomic<Status> status = Status::CONTINUE;
+ };
+
+ // Use nullptr to test error reporting from the worker thread.
+ explicit TestWorkerLogic(Stream* stream) : mStream(stream) {}
+
+ size_t getWorkerCycles() const { return mWorkerCycles; }
+ int getPriority() const { return mPriority; }
+ bool hasWorkerCycleCalled() const { return mWorkerCycles != 0; }
+ bool hasNoWorkerCycleCalled(useconds_t usec) {
+ const size_t cyclesBefore = mWorkerCycles;
+ usleep(usec);
+ return mWorkerCycles == cyclesBefore;
+ }
+
+ protected:
+ // StreamLogic implementation
+ std::string init() override { return mStream != nullptr ? "" : "Expected error"; }
+ Status cycle() override {
+ mPriority = getpriority(PRIO_PROCESS, 0);
+ do {
+ mWorkerCycles++;
+ } while (mWorkerCycles == 0);
+ return mStream->status;
+ }
+
+ private:
+ Stream* const mStream;
+ std::atomic<size_t> mWorkerCycles = 0;
+ std::atomic<int> mPriority = ANDROID_PRIORITY_DEFAULT;
+};
+using TestWorker = StreamWorker<TestWorkerLogic>;
+
+// The parameter specifies whether an extra call to 'stop' is made at the end.
+class StreamWorkerInvalidTest : public testing::TestWithParam<bool> {
+ public:
+ StreamWorkerInvalidTest() : StreamWorkerInvalidTest(nullptr) {}
+ void TearDown() override {
+ if (GetParam()) {
+ worker.stop();
+ }
+ }
+
+ protected:
+ StreamWorkerInvalidTest(TestWorker::Stream* stream)
+ : testing::TestWithParam<bool>(), worker(stream) {}
+ TestWorker worker;
+};
+
+TEST_P(StreamWorkerInvalidTest, Uninitialized) {
+ EXPECT_FALSE(worker.hasWorkerCycleCalled());
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, UninitializedPauseIgnored) {
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, UninitializedResumeIgnored) {
+ EXPECT_FALSE(worker.hasError());
+ worker.resume();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, Start) {
+ EXPECT_FALSE(worker.start());
+ EXPECT_FALSE(worker.hasWorkerCycleCalled());
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, PauseIgnored) {
+ EXPECT_FALSE(worker.start());
+ EXPECT_TRUE(worker.hasError());
+ worker.pause();
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, ResumeIgnored) {
+ EXPECT_FALSE(worker.start());
+ EXPECT_TRUE(worker.hasError());
+ worker.resume();
+ EXPECT_TRUE(worker.hasError());
+}
+
+INSTANTIATE_TEST_SUITE_P(StreamWorkerInvalid, StreamWorkerInvalidTest, testing::Bool());
+
+class StreamWorkerTest : public StreamWorkerInvalidTest {
+ public:
+ StreamWorkerTest() : StreamWorkerInvalidTest(&stream) {}
+
+ protected:
+ TestWorker::Stream stream;
+};
+
+static constexpr unsigned kWorkerIdleCheckTime = 50 * 1000;
+
+TEST_P(StreamWorkerTest, Uninitialized) {
+ EXPECT_FALSE(worker.hasWorkerCycleCalled());
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, Start) {
+ ASSERT_TRUE(worker.start());
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, StartStop) {
+ ASSERT_TRUE(worker.start());
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_FALSE(worker.hasError());
+ worker.stop();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, WorkerExit) {
+ ASSERT_TRUE(worker.start());
+ stream.setStopStatus();
+ worker.waitForAtLeastOneCycle();
+ EXPECT_FALSE(worker.hasError());
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, WorkerError) {
+ ASSERT_TRUE(worker.start());
+ stream.setErrorStatus();
+ worker.waitForAtLeastOneCycle();
+ EXPECT_TRUE(worker.hasError());
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, StopAfterError) {
+ ASSERT_TRUE(worker.start());
+ stream.setErrorStatus();
+ worker.waitForAtLeastOneCycle();
+ EXPECT_TRUE(worker.hasError());
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ worker.stop();
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, PauseResume) {
+ ASSERT_TRUE(worker.start());
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ EXPECT_FALSE(worker.hasError());
+ const size_t workerCyclesBefore = worker.getWorkerCycles();
+ worker.resume();
+ // 'resume' is synchronous and returns after the worker has looped at least once.
+ EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore);
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, StopPaused) {
+ ASSERT_TRUE(worker.start());
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ worker.stop();
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, PauseAfterErrorIgnored) {
+ ASSERT_TRUE(worker.start());
+ stream.setErrorStatus();
+ worker.waitForAtLeastOneCycle();
+ EXPECT_TRUE(worker.hasError());
+ worker.pause();
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, ResumeAfterErrorIgnored) {
+ ASSERT_TRUE(worker.start());
+ stream.setErrorStatus();
+ worker.waitForAtLeastOneCycle();
+ EXPECT_TRUE(worker.hasError());
+ worker.resume();
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+ EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, WorkerErrorOnResume) {
+ ASSERT_TRUE(worker.start());
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_FALSE(worker.hasError());
+ worker.pause();
+ EXPECT_FALSE(worker.hasError());
+ stream.setErrorStatus();
+ EXPECT_FALSE(worker.hasError());
+ worker.resume();
+ worker.waitForAtLeastOneCycle();
+ EXPECT_TRUE(worker.hasError());
+ EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, WaitForAtLeastOneCycle) {
+ ASSERT_TRUE(worker.start());
+ const size_t workerCyclesBefore = worker.getWorkerCycles();
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore);
+}
+
+TEST_P(StreamWorkerTest, WaitForAtLeastOneCycleError) {
+ ASSERT_TRUE(worker.start());
+ stream.setErrorStatus();
+ EXPECT_FALSE(worker.waitForAtLeastOneCycle());
+}
+
+TEST_P(StreamWorkerTest, MutexDoesNotBlockWorker) {
+ ASSERT_TRUE(worker.start());
+ const size_t workerCyclesBefore = worker.getWorkerCycles();
+ worker.testLockUnlockMutex(true);
+ while (worker.getWorkerCycles() == workerCyclesBefore) {
+ usleep(kWorkerIdleCheckTime);
+ }
+ worker.testLockUnlockMutex(false);
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, ThreadName) {
+ const std::string workerName = "TestWorker";
+ ASSERT_TRUE(worker.start(workerName)) << worker.getError();
+ char nameBuf[128];
+ ASSERT_EQ(0, pthread_getname_np(worker.testGetThreadNativeHandle(), nameBuf, sizeof(nameBuf)));
+ EXPECT_EQ(workerName, nameBuf);
+}
+
+TEST_P(StreamWorkerTest, ThreadPriority) {
+ const int priority = ANDROID_PRIORITY_LOWEST;
+ ASSERT_TRUE(worker.start("", priority)) << worker.getError();
+ EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+ EXPECT_EQ(priority, worker.getPriority());
+}
+
+INSTANTIATE_TEST_SUITE_P(StreamWorker, StreamWorkerTest, testing::Bool());
diff --git a/audio/aidl/common/tests/utils_tests.cpp b/audio/aidl/common/tests/utils_tests.cpp
new file mode 100644
index 0000000..d7f1a5d
--- /dev/null
+++ b/audio/aidl/common/tests/utils_tests.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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.
+ */
+
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <Utils.h>
+
+#include <gtest/gtest.h>
+#define LOG_TAG "Utils_Test"
+#include <log/log.h>
+
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::PcmType;
+using android::hardware::audio::common::getChannelCount;
+using android::hardware::audio::common::getFrameSizeInBytes;
+using android::hardware::audio::common::getPcmSampleSizeInBytes;
+
+TEST(UtilsTest, ChannelCountOddCases) {
+ using Tag = AudioChannelLayout::Tag;
+ EXPECT_EQ(0UL, getChannelCount(AudioChannelLayout{}));
+ EXPECT_EQ(0UL, getChannelCount(AudioChannelLayout::make<Tag::invalid>(0)));
+ EXPECT_EQ(0UL, getChannelCount(AudioChannelLayout::make<Tag::invalid>(-1)));
+}
+
+TEST(UtilsTest, ChannelCountForIndexMask) {
+ using Tag = AudioChannelLayout::Tag;
+ EXPECT_EQ(0UL, getChannelCount(AudioChannelLayout::make<Tag::indexMask>(0)));
+#define VERIFY_INDEX_MASK(N) \
+ { \
+ const auto l = \
+ AudioChannelLayout::make<Tag::indexMask>(AudioChannelLayout::INDEX_MASK_##N); \
+ EXPECT_EQ(N##UL, getChannelCount(l)) << l.toString(); \
+ }
+ VERIFY_INDEX_MASK(1);
+ VERIFY_INDEX_MASK(2);
+ VERIFY_INDEX_MASK(3);
+ VERIFY_INDEX_MASK(4);
+ VERIFY_INDEX_MASK(5);
+ VERIFY_INDEX_MASK(6);
+ VERIFY_INDEX_MASK(7);
+ VERIFY_INDEX_MASK(8);
+ VERIFY_INDEX_MASK(9);
+ VERIFY_INDEX_MASK(10);
+ VERIFY_INDEX_MASK(11);
+ VERIFY_INDEX_MASK(12);
+ VERIFY_INDEX_MASK(13);
+ VERIFY_INDEX_MASK(14);
+ VERIFY_INDEX_MASK(15);
+ VERIFY_INDEX_MASK(16);
+ VERIFY_INDEX_MASK(17);
+ VERIFY_INDEX_MASK(18);
+ VERIFY_INDEX_MASK(19);
+ VERIFY_INDEX_MASK(20);
+ VERIFY_INDEX_MASK(21);
+ VERIFY_INDEX_MASK(22);
+ VERIFY_INDEX_MASK(23);
+ VERIFY_INDEX_MASK(24);
+#undef VERIFY_INDEX_MASK
+}
+
+TEST(UtilsTest, ChannelCountForLayoutMask) {
+ using Tag = AudioChannelLayout::Tag;
+ const std::vector<std::pair<size_t, int32_t>> kTestLayouts = {
+ std::make_pair(0UL, 0),
+ std::make_pair(1UL, AudioChannelLayout::LAYOUT_MONO),
+ std::make_pair(2UL, AudioChannelLayout::LAYOUT_STEREO),
+ std::make_pair(6UL, AudioChannelLayout::LAYOUT_5POINT1),
+ std::make_pair(8UL, AudioChannelLayout::LAYOUT_7POINT1),
+ std::make_pair(16UL, AudioChannelLayout::LAYOUT_9POINT1POINT6),
+ std::make_pair(13UL, AudioChannelLayout::LAYOUT_13POINT_360RA),
+ std::make_pair(24UL, AudioChannelLayout::LAYOUT_22POINT2),
+ std::make_pair(3UL, AudioChannelLayout::LAYOUT_STEREO_HAPTIC_A),
+ std::make_pair(4UL, AudioChannelLayout::LAYOUT_STEREO_HAPTIC_AB)};
+ for (const auto& [expected_count, layout] : kTestLayouts) {
+ const auto l = AudioChannelLayout::make<Tag::layoutMask>(layout);
+ EXPECT_EQ(expected_count, getChannelCount(l)) << l.toString();
+ }
+}
+
+TEST(UtilsTest, ChannelCountForVoiceMask) {
+ using Tag = AudioChannelLayout::Tag;
+ // clang-format off
+ const std::vector<std::pair<size_t, int32_t>> kTestLayouts = {
+ std::make_pair(0UL, 0),
+ std::make_pair(1UL, AudioChannelLayout::VOICE_UPLINK_MONO),
+ std::make_pair(1UL, AudioChannelLayout::VOICE_DNLINK_MONO),
+ std::make_pair(2UL, AudioChannelLayout::VOICE_CALL_MONO)};
+ // clang-format on
+ for (const auto& [expected_count, layout] : kTestLayouts) {
+ const auto l = AudioChannelLayout::make<Tag::voiceMask>(layout);
+ EXPECT_EQ(expected_count, getChannelCount(l)) << l.toString();
+ }
+}
+
+namespace {
+
+AudioChannelLayout make_AudioChannelLayout_Mono() {
+ return AudioChannelLayout::make<AudioChannelLayout::Tag::layoutMask>(
+ AudioChannelLayout::LAYOUT_MONO);
+}
+
+AudioChannelLayout make_AudioChannelLayout_Stereo() {
+ return AudioChannelLayout::make<AudioChannelLayout::Tag::layoutMask>(
+ AudioChannelLayout::LAYOUT_STEREO);
+}
+
+AudioFormatDescription make_AudioFormatDescription(AudioFormatType type) {
+ AudioFormatDescription result;
+ result.type = type;
+ return result;
+}
+
+AudioFormatDescription make_AudioFormatDescription(PcmType pcm) {
+ auto result = make_AudioFormatDescription(AudioFormatType::PCM);
+ result.pcm = pcm;
+ return result;
+}
+
+AudioFormatDescription make_AudioFormatDescription(const std::string& encoding) {
+ AudioFormatDescription result;
+ result.encoding = encoding;
+ return result;
+}
+
+AudioFormatDescription make_AudioFormatDescription(PcmType transport, const std::string& encoding) {
+ auto result = make_AudioFormatDescription(encoding);
+ result.pcm = transport;
+ return result;
+}
+
+} // namespace
+
+TEST(UtilsTest, FrameSize) {
+ EXPECT_EQ(0UL, getFrameSizeInBytes(AudioFormatDescription{}, AudioChannelLayout{}));
+ EXPECT_EQ(sizeof(int16_t), getFrameSizeInBytes(make_AudioFormatDescription(PcmType::INT_16_BIT),
+ make_AudioChannelLayout_Mono()));
+ EXPECT_EQ(2 * sizeof(int16_t),
+ getFrameSizeInBytes(make_AudioFormatDescription(PcmType::INT_16_BIT),
+ make_AudioChannelLayout_Stereo()));
+ EXPECT_EQ(sizeof(int32_t), getFrameSizeInBytes(make_AudioFormatDescription(PcmType::INT_32_BIT),
+ make_AudioChannelLayout_Mono()));
+ EXPECT_EQ(2 * sizeof(int32_t),
+ getFrameSizeInBytes(make_AudioFormatDescription(PcmType::INT_32_BIT),
+ make_AudioChannelLayout_Stereo()));
+ EXPECT_EQ(sizeof(float), getFrameSizeInBytes(make_AudioFormatDescription(PcmType::FLOAT_32_BIT),
+ make_AudioChannelLayout_Mono()));
+ EXPECT_EQ(2 * sizeof(float),
+ getFrameSizeInBytes(make_AudioFormatDescription(PcmType::FLOAT_32_BIT),
+ make_AudioChannelLayout_Stereo()));
+ EXPECT_EQ(sizeof(uint8_t),
+ getFrameSizeInBytes(make_AudioFormatDescription("bitstream"), AudioChannelLayout{}));
+ EXPECT_EQ(sizeof(int16_t),
+ getFrameSizeInBytes(make_AudioFormatDescription(PcmType::INT_16_BIT, "encapsulated"),
+ AudioChannelLayout{}));
+}
+
+TEST(UtilsTest, PcmSampleSize) {
+ EXPECT_EQ(1UL, getPcmSampleSizeInBytes(PcmType{}));
+ EXPECT_EQ(sizeof(uint8_t), getPcmSampleSizeInBytes(PcmType::UINT_8_BIT));
+ EXPECT_EQ(sizeof(int16_t), getPcmSampleSizeInBytes(PcmType::INT_16_BIT));
+ EXPECT_EQ(sizeof(int32_t), getPcmSampleSizeInBytes(PcmType::INT_32_BIT));
+ EXPECT_EQ(sizeof(int32_t), getPcmSampleSizeInBytes(PcmType::FIXED_Q_8_24));
+ EXPECT_EQ(sizeof(float), getPcmSampleSizeInBytes(PcmType::FLOAT_32_BIT));
+ EXPECT_EQ(3UL, getPcmSampleSizeInBytes(PcmType::INT_24_BIT));
+ EXPECT_EQ(0UL, getPcmSampleSizeInBytes(PcmType(-1)));
+ using PcmTypeUnderlyingType = std::underlying_type_t<PcmType>;
+ EXPECT_EQ(0UL,
+ getPcmSampleSizeInBytes(PcmType(std::numeric_limits<PcmTypeUnderlyingType>::min())));
+ EXPECT_EQ(0UL,
+ getPcmSampleSizeInBytes(PcmType(std::numeric_limits<PcmTypeUnderlyingType>::max())));
+}
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
new file mode 100644
index 0000000..53ed908
--- /dev/null
+++ b/audio/aidl/default/Android.bp
@@ -0,0 +1,106 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_defaults {
+ name: "aidlaudioservice_defaults",
+ vendor: true,
+ shared_libs: [
+ "libaudioaidlcommon",
+ "libbase",
+ "libbinder_ndk",
+ "libcutils",
+ "libfmq",
+ "libstagefright_foundation",
+ "libutils",
+ "android.hardware.common-V2-ndk",
+ "android.hardware.common.fmq-V1-ndk",
+ ],
+}
+
+cc_library_static {
+ name: "libaudioserviceexampleimpl",
+ defaults: [
+ "aidlaudioservice_defaults",
+ "latest_android_media_audio_common_types_ndk_shared",
+ "latest_android_hardware_audio_core_ndk_shared",
+ ],
+ export_include_dirs: ["include"],
+ srcs: [
+ "Config.cpp",
+ "Configuration.cpp",
+ "Module.cpp",
+ "Stream.cpp",
+ ],
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
+cc_binary {
+ name: "android.hardware.audio.service-aidl.example",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.audio.service-aidl.example.rc"],
+ vintf_fragments: ["android.hardware.audio.service-aidl.xml"],
+ defaults: [
+ "aidlaudioservice_defaults",
+ "latest_android_media_audio_common_types_ndk_shared",
+ "latest_android_hardware_audio_core_ndk_shared",
+ ],
+ static_libs: [
+ "libaudioserviceexampleimpl",
+ ],
+ srcs: ["main.cpp"],
+}
+
+cc_defaults {
+ name: "aidlaudioeffectservice_defaults",
+ defaults: [
+ "latest_android_media_audio_common_types_ndk_shared",
+ ],
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.audio.effect-V1-ndk",
+ "libequalizer",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wthread-safety",
+ ],
+}
+
+cc_library_static {
+ name: "libaudioeffectserviceexampleimpl",
+ defaults: ["aidlaudioeffectservice_defaults"],
+ export_include_dirs: [
+ "include",
+ "include/equalizer-impl/",
+ ],
+ srcs: [
+ "EffectFactory.cpp",
+ ],
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
+cc_binary {
+ name: "android.hardware.audio.effect.service-aidl.example",
+ relative_install_path: "hw",
+ init_rc: ["android.hardware.audio.effect.service-aidl.example.rc"],
+ vintf_fragments: ["android.hardware.audio.effect.service-aidl.xml"],
+ defaults: ["aidlaudioeffectservice_defaults"],
+ static_libs: [
+ "libaudioeffectserviceexampleimpl",
+ ],
+ srcs: ["EffectMain.cpp"],
+}
diff --git a/audio/aidl/default/Config.cpp b/audio/aidl/default/Config.cpp
new file mode 100644
index 0000000..3f7a3d3
--- /dev/null
+++ b/audio/aidl/default/Config.cpp
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+#include "core-impl/Config.h"
+
+namespace aidl::android::hardware::audio::core {} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp
new file mode 100644
index 0000000..a3e5ff7
--- /dev/null
+++ b/audio/aidl/default/Configuration.cpp
@@ -0,0 +1,292 @@
+/*
+ * 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.
+ */
+
+#include <Utils.h>
+#include <aidl/android/media/audio/common/AudioChannelLayout.h>
+#include <aidl/android/media/audio/common/AudioDeviceType.h>
+#include <aidl/android/media/audio/common/AudioFormatDescription.h>
+#include <aidl/android/media/audio/common/AudioFormatType.h>
+#include <aidl/android/media/audio/common/AudioIoFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "core-impl/Configuration.h"
+
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+using aidl::android::media::audio::common::AudioDeviceType;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioGainConfig;
+using aidl::android::media::audio::common::AudioIoFlags;
+using aidl::android::media::audio::common::AudioOutputFlags;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortDeviceExt;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::AudioPortMixExt;
+using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::Int;
+using aidl::android::media::audio::common::PcmType;
+using android::hardware::audio::common::makeBitPositionFlagMask;
+
+namespace aidl::android::hardware::audio::core::internal {
+
+static void fillProfile(AudioProfile* profile, const std::vector<int32_t>& channelLayouts,
+ const std::vector<int32_t>& sampleRates) {
+ for (auto layout : channelLayouts) {
+ profile->channelMasks.push_back(
+ AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout));
+ }
+ profile->sampleRates.insert(profile->sampleRates.end(), sampleRates.begin(), sampleRates.end());
+}
+
+static AudioProfile createProfile(PcmType pcmType, const std::vector<int32_t>& channelLayouts,
+ const std::vector<int32_t>& sampleRates) {
+ AudioProfile profile;
+ profile.format.type = AudioFormatType::PCM;
+ profile.format.pcm = pcmType;
+ fillProfile(&profile, channelLayouts, sampleRates);
+ return profile;
+}
+
+static AudioProfile createProfile(const std::string& encodingType,
+ const std::vector<int32_t>& channelLayouts,
+ const std::vector<int32_t>& sampleRates) {
+ AudioProfile profile;
+ profile.format.encoding = encodingType;
+ fillProfile(&profile, channelLayouts, sampleRates);
+ return profile;
+}
+
+static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags,
+ std::string connection = "") {
+ AudioPortDeviceExt deviceExt;
+ deviceExt.device.type.type = devType;
+ deviceExt.device.type.connection = std::move(connection);
+ deviceExt.flags = flags;
+ return AudioPortExt::make<AudioPortExt::Tag::device>(deviceExt);
+}
+
+static AudioPortExt createPortMixExt(int32_t maxOpenStreamCount, int32_t maxActiveStreamCount) {
+ AudioPortMixExt mixExt;
+ mixExt.maxOpenStreamCount = maxOpenStreamCount;
+ mixExt.maxActiveStreamCount = maxActiveStreamCount;
+ return AudioPortExt::make<AudioPortExt::Tag::mix>(mixExt);
+}
+
+static AudioPort createPort(int32_t id, const std::string& name, int32_t flags, bool isInput,
+ const AudioPortExt& ext) {
+ AudioPort port;
+ port.id = id;
+ port.name = name;
+ port.flags = isInput ? AudioIoFlags::make<AudioIoFlags::Tag::input>(flags)
+ : AudioIoFlags::make<AudioIoFlags::Tag::output>(flags);
+ port.ext = ext;
+ return port;
+}
+
+static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout,
+ int32_t sampleRate, int32_t flags, bool isInput,
+ const AudioPortExt& ext) {
+ AudioPortConfig config;
+ config.id = id;
+ config.portId = portId;
+ config.sampleRate = Int{.value = sampleRate};
+ config.channelMask = AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout);
+ config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType};
+ config.gain = AudioGainConfig();
+ config.flags = isInput ? AudioIoFlags::make<AudioIoFlags::Tag::input>(flags)
+ : AudioIoFlags::make<AudioIoFlags::Tag::output>(flags);
+ config.ext = ext;
+ return config;
+}
+
+static AudioRoute createRoute(const std::vector<int32_t>& sources, int32_t sink) {
+ AudioRoute route;
+ route.sinkPortId = sink;
+ route.sourcePortIds.insert(route.sourcePortIds.end(), sources.begin(), sources.end());
+ return route;
+}
+
+// Configuration:
+//
+// Device ports:
+// * "Null", OUT_SPEAKER, default
+// - no profiles specified
+// * "Loopback Out", OUT_SUBMIX
+// - profile PCM 24-bit; STEREO; 48000
+// * "USB Out", OUT_DEVICE, CONNECTION_USB
+// - no profiles specified
+// * "Zero", IN_MICROPHONE, default
+// - no profiles specified
+// * "Loopback In", IN_SUBMIX
+// - profile PCM 24-bit; STEREO; 48000
+// * "USB In", IN_DEVICE, CONNECTION_USB
+// - no profiles specified
+//
+// Mix ports:
+// * "primary output", PRIMARY, 1 max open, 1 max active stream
+// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+// * "compressed offload", DIRECT|COMPRESS_OFFLOAD|NON_BLOCKING, 1 max open, 1 max active stream
+// - profile MP3; MONO, STEREO; 44100, 48000
+// * "loopback output", stream count unlimited
+// - profile PCM 24-bit; STEREO; 48000
+// * "primary input", 2 max open, 2 max active streams
+// - profile PCM 16-bit; MONO, STEREO, FRONT_BACK;
+// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO, FRONT_BACK;
+// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+// * "loopback input", stream count unlimited
+// - profile PCM 24-bit; STEREO; 48000
+//
+// Routes:
+// "primary out", "compressed offload" -> "Null"
+// "primary out", "compressed offload" -> "USB Out"
+// "loopback out" -> "Loopback Out"
+// "Zero", "USB In" -> "primary input"
+// "Loopback In" -> "loopback input"
+//
+// Initial port configs:
+// * "Null" device port: PCM 24-bit; STEREO; 48000
+// * "Zero" device port: PCM 24-bit; MONO; 48000
+//
+// Profiles for device port connected state:
+// * USB Out":
+// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+// * USB In":
+// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+//
+Configuration& getNullPrimaryConfiguration() {
+ static Configuration configuration = []() {
+ const std::vector<AudioProfile> standardPcmAudioProfiles = {
+ createProfile(PcmType::INT_16_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000}),
+ createProfile(PcmType::INT_24_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000})};
+ Configuration c;
+
+ AudioPort nullOutDevice =
+ createPort(c.nextPortId++, "Null", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_SPEAKER,
+ 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
+ c.ports.push_back(nullOutDevice);
+ c.initialConfigs.push_back(
+ createPortConfig(nullOutDevice.id, nullOutDevice.id, PcmType::INT_24_BIT,
+ AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false,
+ createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0)));
+
+ AudioPort primaryOutMix = createPort(c.nextPortId++, "primary output",
+ makeBitPositionFlagMask(AudioOutputFlags::PRIMARY),
+ false, createPortMixExt(1, 1));
+ primaryOutMix.profiles.insert(primaryOutMix.profiles.begin(),
+ standardPcmAudioProfiles.begin(),
+ standardPcmAudioProfiles.end());
+ c.ports.push_back(primaryOutMix);
+
+ AudioPort compressedOffloadOutMix =
+ createPort(c.nextPortId++, "compressed offload",
+ makeBitPositionFlagMask({AudioOutputFlags::DIRECT,
+ AudioOutputFlags::COMPRESS_OFFLOAD,
+ AudioOutputFlags::NON_BLOCKING}),
+ false, createPortMixExt(1, 1));
+ compressedOffloadOutMix.profiles.push_back(
+ createProfile(::android::MEDIA_MIMETYPE_AUDIO_MPEG,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000}));
+ c.ports.push_back(compressedOffloadOutMix);
+
+ AudioPort loopOutDevice = createPort(c.nextPortId++, "Loopback Out", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0));
+ loopOutDevice.profiles.push_back(
+ createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+ c.ports.push_back(loopOutDevice);
+
+ AudioPort loopOutMix =
+ createPort(c.nextPortId++, "loopback output", 0, false, createPortMixExt(0, 0));
+ loopOutMix.profiles.push_back(
+ createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+ c.ports.push_back(loopOutMix);
+
+ AudioPort usbOutDevice =
+ createPort(c.nextPortId++, "USB Out", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_DEVICE, 0,
+ AudioDeviceDescription::CONNECTION_USB));
+ c.ports.push_back(usbOutDevice);
+ c.connectedProfiles[usbOutDevice.id] = standardPcmAudioProfiles;
+
+ AudioPort zeroInDevice =
+ createPort(c.nextPortId++, "Zero", 0, true,
+ createDeviceExt(AudioDeviceType::IN_MICROPHONE,
+ 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
+ c.ports.push_back(zeroInDevice);
+ c.initialConfigs.push_back(
+ createPortConfig(zeroInDevice.id, zeroInDevice.id, PcmType::INT_24_BIT,
+ AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
+ createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
+
+ AudioPort primaryInMix =
+ createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(2, 2));
+ primaryInMix.profiles.push_back(
+ createProfile(PcmType::INT_16_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO,
+ AudioChannelLayout::LAYOUT_FRONT_BACK},
+ {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}));
+ primaryInMix.profiles.push_back(
+ createProfile(PcmType::INT_24_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO,
+ AudioChannelLayout::LAYOUT_FRONT_BACK},
+ {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}));
+ c.ports.push_back(primaryInMix);
+
+ AudioPort loopInDevice = createPort(c.nextPortId++, "Loopback In", 0, true,
+ createDeviceExt(AudioDeviceType::IN_SUBMIX, 0));
+ loopInDevice.profiles.push_back(
+ createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+ c.ports.push_back(loopInDevice);
+
+ AudioPort loopInMix =
+ createPort(c.nextPortId++, "loopback input", 0, true, createPortMixExt(0, 0));
+ loopInMix.profiles.push_back(
+ createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+ c.ports.push_back(loopInMix);
+
+ AudioPort usbInDevice = createPort(c.nextPortId++, "USB In", 0, true,
+ createDeviceExt(AudioDeviceType::IN_DEVICE, 0,
+ AudioDeviceDescription::CONNECTION_USB));
+ c.ports.push_back(usbInDevice);
+ c.connectedProfiles[usbInDevice.id] = standardPcmAudioProfiles;
+
+ c.routes.push_back(
+ createRoute({primaryOutMix.id, compressedOffloadOutMix.id}, nullOutDevice.id));
+ c.routes.push_back(
+ createRoute({primaryOutMix.id, compressedOffloadOutMix.id}, usbOutDevice.id));
+ c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
+ c.routes.push_back(createRoute({zeroInDevice.id, usbInDevice.id}, primaryInMix.id));
+ c.routes.push_back(createRoute({loopInDevice.id}, loopInMix.id));
+
+ c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end());
+ return c;
+ }();
+ return configuration;
+}
+
+} // namespace aidl::android::hardware::audio::core::internal
diff --git a/audio/aidl/default/EffectFactory.cpp b/audio/aidl/default/EffectFactory.cpp
new file mode 100644
index 0000000..a9848fd
--- /dev/null
+++ b/audio/aidl/default/EffectFactory.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AHAL_EffectFactory"
+#include <android-base/logging.h>
+
+#include "effectFactory-impl/EffectFactory.h"
+#include "equalizer-impl/Equalizer.h"
+#include "visualizer-impl/Visualizer.h"
+
+using aidl::android::media::audio::common::AudioUuid;
+
+namespace aidl::android::hardware::audio::effect {
+
+Factory::Factory() {
+ // TODO: implement this with xml parser on audio_effect.xml, and filter with optional
+ // parameters.
+ Descriptor::Identity id;
+ id.type = EqualizerTypeUUID;
+ id.uuid = EqualizerSwImplUUID;
+ mIdentityList.push_back(id);
+}
+
+ndk::ScopedAStatus Factory::queryEffects(const std::optional<AudioUuid>& in_type,
+ const std::optional<AudioUuid>& in_instance,
+ std::vector<Descriptor::Identity>* _aidl_return) {
+ std::copy_if(mIdentityList.begin(), mIdentityList.end(), std::back_inserter(*_aidl_return),
+ [&](auto& desc) {
+ return (!in_type.has_value() || in_type.value() == desc.type) &&
+ (!in_instance.has_value() || in_instance.value() == desc.uuid);
+ });
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Factory::createEffect(
+ const AudioUuid& in_impl_uuid,
+ std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>* _aidl_return) {
+ LOG(DEBUG) << __func__ << ": UUID " << in_impl_uuid.toString();
+ if (in_impl_uuid == EqualizerSwImplUUID) {
+ *_aidl_return = ndk::SharedRefBase::make<Equalizer>();
+ } else {
+ LOG(ERROR) << __func__ << ": UUID "
+ << " not supported";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Factory::destroyEffect(
+ const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_handle) {
+ if (in_handle) {
+ // TODO: b/245393900 need check the instance state with IEffect.getState before destroy.
+ return ndk::ScopedAStatus::ok();
+ } else {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+}
+
+} // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/EffectMain.cpp b/audio/aidl/default/EffectMain.cpp
new file mode 100644
index 0000000..3219dd6
--- /dev/null
+++ b/audio/aidl/default/EffectMain.cpp
@@ -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.
+ */
+
+#include "effectFactory-impl/EffectFactory.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+int main() {
+ // This is a debug implementation, always enable debug logging.
+ android::base::SetMinimumLogSeverity(::android::base::DEBUG);
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+ auto effectFactory =
+ ndk::SharedRefBase::make<aidl::android::hardware::audio::effect::Factory>();
+
+ std::string serviceName = std::string() + effectFactory->descriptor + "/default";
+ binder_status_t status =
+ AServiceManager_addService(effectFactory->asBinder().get(), serviceName.c_str());
+ CHECK_EQ(STATUS_OK, status);
+ LOG(DEBUG) << __func__ << ": effectFactoryName:" << serviceName;
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
new file mode 100644
index 0000000..deaca49
--- /dev/null
+++ b/audio/aidl/default/Module.cpp
@@ -0,0 +1,782 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+#include <set>
+
+#define LOG_TAG "AHAL_Module"
+#include <android-base/logging.h>
+
+#include <Utils.h>
+#include <aidl/android/media/audio/common/AudioInputFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+
+#include "core-impl/Module.h"
+#include "core-impl/utils.h"
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioInputFlags;
+using aidl::android::media::audio::common::AudioIoFlags;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::AudioOutputFlags;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::Int;
+using aidl::android::media::audio::common::PcmType;
+using android::hardware::audio::common::getFrameSizeInBytes;
+using android::hardware::audio::common::isBitPositionFlagSet;
+
+namespace aidl::android::hardware::audio::core {
+
+namespace {
+
+bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) {
+ *config = {};
+ config->portId = port.id;
+ if (port.profiles.empty()) {
+ LOG(ERROR) << __func__ << ": port " << port.id << " has no profiles";
+ return false;
+ }
+ const auto& profile = port.profiles.begin();
+ config->format = profile->format;
+ if (profile->channelMasks.empty()) {
+ LOG(ERROR) << __func__ << ": the first profile in port " << port.id
+ << " has no channel masks";
+ return false;
+ }
+ config->channelMask = *profile->channelMasks.begin();
+ if (profile->sampleRates.empty()) {
+ LOG(ERROR) << __func__ << ": the first profile in port " << port.id
+ << " has no sample rates";
+ return false;
+ }
+ Int sampleRate;
+ sampleRate.value = *profile->sampleRates.begin();
+ config->sampleRate = sampleRate;
+ config->flags = port.flags;
+ config->ext = port.ext;
+ return true;
+}
+
+bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format,
+ AudioProfile* profile) {
+ if (auto profilesIt =
+ find_if(port.profiles.begin(), port.profiles.end(),
+ [&format](const auto& profile) { return profile.format == format; });
+ profilesIt != port.profiles.end()) {
+ *profile = *profilesIt;
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+void Module::cleanUpPatch(int32_t patchId) {
+ erase_all_values(mPatches, std::set<int32_t>{patchId});
+}
+
+ndk::ScopedAStatus Module::createStreamContext(int32_t in_portConfigId, int64_t in_bufferSizeFrames,
+ StreamContext* out_context) {
+ if (in_bufferSizeFrames <= 0) {
+ LOG(ERROR) << __func__ << ": non-positive buffer size " << in_bufferSizeFrames;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (in_bufferSizeFrames < kMinimumStreamBufferSizeFrames) {
+ LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
+ << ", must be at least " << kMinimumStreamBufferSizeFrames;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& configs = getConfig().portConfigs;
+ auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
+ // Since this is a private method, it is assumed that
+ // validity of the portConfigId has already been checked.
+ const size_t frameSize =
+ getFrameSizeInBytes(portConfigIt->format.value(), portConfigIt->channelMask.value());
+ if (frameSize == 0) {
+ LOG(ERROR) << __func__ << ": could not calculate frame size for port config "
+ << portConfigIt->toString();
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ LOG(DEBUG) << __func__ << ": frame size " << frameSize << " bytes";
+ if (frameSize > kMaximumStreamBufferSizeBytes / in_bufferSizeFrames) {
+ LOG(ERROR) << __func__ << ": buffer size " << in_bufferSizeFrames
+ << " frames is too large, maximum size is "
+ << kMaximumStreamBufferSizeBytes / frameSize;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ const auto& flags = portConfigIt->flags.value();
+ if ((flags.getTag() == AudioIoFlags::Tag::input &&
+ !isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::input>(),
+ AudioInputFlags::MMAP_NOIRQ)) ||
+ (flags.getTag() == AudioIoFlags::Tag::output &&
+ !isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::MMAP_NOIRQ))) {
+ StreamContext temp(
+ std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
+ std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
+ frameSize,
+ std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames));
+ if (temp.isValid()) {
+ *out_context = std::move(temp);
+ } else {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ } else {
+ // TODO: Implement simulation of MMAP buffer allocation
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::findPortIdForNewStream(int32_t in_portConfigId, AudioPort** port) {
+ auto& configs = getConfig().portConfigs;
+ auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
+ if (portConfigIt == configs.end()) {
+ LOG(ERROR) << __func__ << ": existing port config id " << in_portConfigId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ const int32_t portId = portConfigIt->portId;
+ // In our implementation, configs of mix ports always have unique IDs.
+ CHECK(portId != in_portConfigId);
+ auto& ports = getConfig().ports;
+ auto portIt = findById<AudioPort>(ports, portId);
+ if (portIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << portId << " used by port config id "
+ << in_portConfigId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (mStreams.count(in_portConfigId) != 0) {
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+ << " already has a stream opened on it";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ if (portIt->ext.getTag() != AudioPortExt::Tag::mix) {
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+ << " does not correspond to a mix port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
+ if (maxOpenStreamCount != 0 && mStreams.count(portId) >= maxOpenStreamCount) {
+ LOG(ERROR) << __func__ << ": port id " << portId
+ << " has already reached maximum allowed opened stream count: "
+ << maxOpenStreamCount;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ *port = &(*portIt);
+ return ndk::ScopedAStatus::ok();
+}
+
+internal::Configuration& Module::getConfig() {
+ if (!mConfig) {
+ mConfig.reset(new internal::Configuration(internal::getNullPrimaryConfiguration()));
+ }
+ return *mConfig;
+}
+
+void Module::registerPatch(const AudioPatch& patch) {
+ auto& configs = getConfig().portConfigs;
+ auto do_insert = [&](const std::vector<int32_t>& portConfigIds) {
+ for (auto portConfigId : portConfigIds) {
+ auto configIt = findById<AudioPortConfig>(configs, portConfigId);
+ if (configIt != configs.end()) {
+ mPatches.insert(std::pair{portConfigId, patch.id});
+ if (configIt->portId != portConfigId) {
+ mPatches.insert(std::pair{configIt->portId, patch.id});
+ }
+ }
+ };
+ };
+ do_insert(patch.sourcePortConfigIds);
+ do_insert(patch.sinkPortConfigIds);
+}
+
+void Module::updateStreamsConnectedState(const AudioPatch& oldPatch, const AudioPatch& newPatch) {
+ // Streams from the old patch need to be disconnected, streams from the new
+ // patch need to be connected. If the stream belongs to both patches, no need
+ // to update it.
+ std::set<int32_t> idsToDisconnect, idsToConnect;
+ idsToDisconnect.insert(oldPatch.sourcePortConfigIds.begin(),
+ oldPatch.sourcePortConfigIds.end());
+ idsToDisconnect.insert(oldPatch.sinkPortConfigIds.begin(), oldPatch.sinkPortConfigIds.end());
+ idsToConnect.insert(newPatch.sourcePortConfigIds.begin(), newPatch.sourcePortConfigIds.end());
+ idsToConnect.insert(newPatch.sinkPortConfigIds.begin(), newPatch.sinkPortConfigIds.end());
+ std::for_each(idsToDisconnect.begin(), idsToDisconnect.end(), [&](const auto& portConfigId) {
+ if (idsToConnect.count(portConfigId) == 0) {
+ mStreams.setStreamIsConnected(portConfigId, false);
+ }
+ });
+ std::for_each(idsToConnect.begin(), idsToConnect.end(), [&](const auto& portConfigId) {
+ if (idsToDisconnect.count(portConfigId) == 0) {
+ mStreams.setStreamIsConnected(portConfigId, true);
+ }
+ });
+}
+
+ndk::ScopedAStatus Module::setModuleDebug(
+ const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) {
+ LOG(DEBUG) << __func__ << ": old flags:" << mDebug.toString()
+ << ", new flags: " << in_debug.toString();
+ if (mDebug.simulateDeviceConnections != in_debug.simulateDeviceConnections &&
+ !mConnectedDevicePorts.empty()) {
+ LOG(ERROR) << __func__ << ": attempting to change device connections simulation "
+ << "while having external devices connected";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ mDebug = in_debug;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdAndAdditionalData,
+ AudioPort* _aidl_return) {
+ const int32_t templateId = in_templateIdAndAdditionalData.id;
+ auto& ports = getConfig().ports;
+ AudioPort connectedPort;
+ { // Scope the template port so that we don't accidentally modify it.
+ auto templateIt = findById<AudioPort>(ports, templateId);
+ if (templateIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << templateId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (templateIt->ext.getTag() != AudioPortExt::Tag::device) {
+ LOG(ERROR) << __func__ << ": port id " << templateId << " is not a device port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (!templateIt->profiles.empty()) {
+ LOG(ERROR) << __func__ << ": port id " << templateId
+ << " does not have dynamic profiles";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& templateDevicePort = templateIt->ext.get<AudioPortExt::Tag::device>();
+ if (templateDevicePort.device.type.connection.empty()) {
+ LOG(ERROR) << __func__ << ": port id " << templateId << " is permanently attached";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ // Postpone id allocation until we ensure that there are no client errors.
+ connectedPort = *templateIt;
+ connectedPort.extraAudioDescriptors = in_templateIdAndAdditionalData.extraAudioDescriptors;
+ const auto& inputDevicePort =
+ in_templateIdAndAdditionalData.ext.get<AudioPortExt::Tag::device>();
+ auto& connectedDevicePort = connectedPort.ext.get<AudioPortExt::Tag::device>();
+ connectedDevicePort.device.address = inputDevicePort.device.address;
+ LOG(DEBUG) << __func__ << ": device port " << connectedPort.id << " device set to "
+ << connectedDevicePort.device.toString();
+ // Check if there is already a connected port with for the same external device.
+ for (auto connectedPortId : mConnectedDevicePorts) {
+ auto connectedPortIt = findById<AudioPort>(ports, connectedPortId);
+ if (connectedPortIt->ext.get<AudioPortExt::Tag::device>().device ==
+ connectedDevicePort.device) {
+ LOG(ERROR) << __func__ << ": device " << connectedDevicePort.device.toString()
+ << " is already connected at the device port id " << connectedPortId;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ }
+ }
+
+ if (!mDebug.simulateDeviceConnections) {
+ // In a real HAL here we would attempt querying the profiles from the device.
+ LOG(ERROR) << __func__ << ": failed to query supported device profiles";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ connectedPort.id = ++getConfig().nextPortId;
+ mConnectedDevicePorts.insert(connectedPort.id);
+ LOG(DEBUG) << __func__ << ": template port " << templateId << " external device connected, "
+ << "connected port ID " << connectedPort.id;
+ auto& connectedProfiles = getConfig().connectedProfiles;
+ if (auto connectedProfilesIt = connectedProfiles.find(templateId);
+ connectedProfilesIt != connectedProfiles.end()) {
+ connectedPort.profiles = connectedProfilesIt->second;
+ }
+ ports.push_back(connectedPort);
+ *_aidl_return = std::move(connectedPort);
+
+ std::vector<AudioRoute> newRoutes;
+ auto& routes = getConfig().routes;
+ for (auto& r : routes) {
+ if (r.sinkPortId == templateId) {
+ AudioRoute newRoute;
+ newRoute.sourcePortIds = r.sourcePortIds;
+ newRoute.sinkPortId = connectedPort.id;
+ newRoute.isExclusive = r.isExclusive;
+ newRoutes.push_back(std::move(newRoute));
+ } else {
+ auto& srcs = r.sourcePortIds;
+ if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
+ srcs.push_back(connectedPort.id);
+ }
+ }
+ }
+ routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
+ auto& ports = getConfig().ports;
+ auto portIt = findById<AudioPort>(ports, in_portId);
+ if (portIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (portIt->ext.getTag() != AudioPortExt::Tag::device) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a device port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (mConnectedDevicePorts.count(in_portId) == 0) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a connected device port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& configs = getConfig().portConfigs;
+ auto& initials = getConfig().initialConfigs;
+ auto configIt = std::find_if(configs.begin(), configs.end(), [&](const auto& config) {
+ if (config.portId == in_portId) {
+ // Check if the configuration was provided by the client.
+ const auto& initialIt = findById<AudioPortConfig>(initials, config.id);
+ return initialIt == initials.end() || config != *initialIt;
+ }
+ return false;
+ });
+ if (configIt != configs.end()) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " has a non-default config with id "
+ << configIt->id;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ ports.erase(portIt);
+ mConnectedDevicePorts.erase(in_portId);
+ LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
+
+ auto& routes = getConfig().routes;
+ for (auto routesIt = routes.begin(); routesIt != routes.end();) {
+ if (routesIt->sinkPortId == in_portId) {
+ routesIt = routes.erase(routesIt);
+ } else {
+ // Note: the list of sourcePortIds can't become empty because there must
+ // be the id of the template port in the route.
+ erase_if(routesIt->sourcePortIds, [in_portId](auto src) { return src == in_portId; });
+ ++routesIt;
+ }
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioPatches(std::vector<AudioPatch>* _aidl_return) {
+ *_aidl_return = getConfig().patches;
+ LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " patches";
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioPort(int32_t in_portId, AudioPort* _aidl_return) {
+ auto& ports = getConfig().ports;
+ auto portIt = findById<AudioPort>(ports, in_portId);
+ if (portIt != ports.end()) {
+ *_aidl_return = *portIt;
+ LOG(DEBUG) << __func__ << ": returning port by id " << in_portId;
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+ndk::ScopedAStatus Module::getAudioPortConfigs(std::vector<AudioPortConfig>* _aidl_return) {
+ *_aidl_return = getConfig().portConfigs;
+ LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " port configs";
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioPorts(std::vector<AudioPort>* _aidl_return) {
+ *_aidl_return = getConfig().ports;
+ LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " ports";
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioRoutes(std::vector<AudioRoute>* _aidl_return) {
+ *_aidl_return = getConfig().routes;
+ LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " routes";
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
+ std::vector<AudioRoute>* _aidl_return) {
+ auto& ports = getConfig().ports;
+ if (auto portIt = findById<AudioPort>(ports, in_portId); portIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& routes = getConfig().routes;
+ std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
+ [&](const auto& r) {
+ const auto& srcs = r.sourcePortIds;
+ return r.sinkPortId == in_portId ||
+ std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end();
+ });
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::openInputStream(const OpenInputStreamArguments& in_args,
+ OpenInputStreamReturn* _aidl_return) {
+ LOG(DEBUG) << __func__ << ": port config id " << in_args.portConfigId << ", buffer size "
+ << in_args.bufferSizeFrames << " frames";
+ AudioPort* port = nullptr;
+ if (auto status = findPortIdForNewStream(in_args.portConfigId, &port); !status.isOk()) {
+ return status;
+ }
+ if (port->flags.getTag() != AudioIoFlags::Tag::input) {
+ LOG(ERROR) << __func__ << ": port config id " << in_args.portConfigId
+ << " does not correspond to an input mix port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ StreamContext context;
+ if (auto status = createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames, &context);
+ !status.isOk()) {
+ return status;
+ }
+ context.fillDescriptor(&_aidl_return->desc);
+ auto stream = ndk::SharedRefBase::make<StreamIn>(in_args.sinkMetadata, std::move(context));
+ if (auto status = stream->init(); !status.isOk()) {
+ return status;
+ }
+ StreamWrapper streamWrapper(stream);
+ auto patchIt = mPatches.find(in_args.portConfigId);
+ if (patchIt != mPatches.end()) {
+ streamWrapper.setStreamIsConnected(true);
+ }
+ mStreams.insert(port->id, in_args.portConfigId, std::move(streamWrapper));
+ _aidl_return->stream = std::move(stream);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::openOutputStream(const OpenOutputStreamArguments& in_args,
+ OpenOutputStreamReturn* _aidl_return) {
+ LOG(DEBUG) << __func__ << ": port config id " << in_args.portConfigId << ", has offload info? "
+ << (in_args.offloadInfo.has_value()) << ", buffer size " << in_args.bufferSizeFrames
+ << " frames";
+ AudioPort* port = nullptr;
+ if (auto status = findPortIdForNewStream(in_args.portConfigId, &port); !status.isOk()) {
+ return status;
+ }
+ if (port->flags.getTag() != AudioIoFlags::Tag::output) {
+ LOG(ERROR) << __func__ << ": port config id " << in_args.portConfigId
+ << " does not correspond to an output mix port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ const bool isOffload = isBitPositionFlagSet(port->flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::COMPRESS_OFFLOAD);
+ if (isOffload && !in_args.offloadInfo.has_value()) {
+ LOG(ERROR) << __func__ << ": port id " << port->id
+ << " has COMPRESS_OFFLOAD flag set, requires offload info";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ StreamContext context;
+ if (auto status = createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames, &context);
+ !status.isOk()) {
+ return status;
+ }
+ context.fillDescriptor(&_aidl_return->desc);
+ auto stream = ndk::SharedRefBase::make<StreamOut>(in_args.sourceMetadata, std::move(context),
+ in_args.offloadInfo);
+ if (auto status = stream->init(); !status.isOk()) {
+ return status;
+ }
+ StreamWrapper streamWrapper(stream);
+ auto patchIt = mPatches.find(in_args.portConfigId);
+ if (patchIt != mPatches.end()) {
+ streamWrapper.setStreamIsConnected(true);
+ }
+ mStreams.insert(port->id, in_args.portConfigId, std::move(streamWrapper));
+ _aidl_return->stream = std::move(stream);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPatch* _aidl_return) {
+ LOG(DEBUG) << __func__ << ": requested patch " << in_requested.toString();
+ if (in_requested.sourcePortConfigIds.empty()) {
+ LOG(ERROR) << __func__ << ": requested patch has empty sources list";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (!all_unique<int32_t>(in_requested.sourcePortConfigIds)) {
+ LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sources list";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (in_requested.sinkPortConfigIds.empty()) {
+ LOG(ERROR) << __func__ << ": requested patch has empty sinks list";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (!all_unique<int32_t>(in_requested.sinkPortConfigIds)) {
+ LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sinks list";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ auto& configs = getConfig().portConfigs;
+ std::vector<int32_t> missingIds;
+ auto sources =
+ selectByIds<AudioPortConfig>(configs, in_requested.sourcePortConfigIds, &missingIds);
+ if (!missingIds.empty()) {
+ LOG(ERROR) << __func__ << ": following source port config ids not found: "
+ << ::android::internal::ToString(missingIds);
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto sinks = selectByIds<AudioPortConfig>(configs, in_requested.sinkPortConfigIds, &missingIds);
+ if (!missingIds.empty()) {
+ LOG(ERROR) << __func__ << ": following sink port config ids not found: "
+ << ::android::internal::ToString(missingIds);
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ // bool indicates whether a non-exclusive route is available.
+ // If only an exclusive route is available, that means the patch can not be
+ // established if there is any other patch which currently uses the sink port.
+ std::map<int32_t, bool> allowedSinkPorts;
+ auto& routes = getConfig().routes;
+ for (auto src : sources) {
+ for (const auto& r : routes) {
+ const auto& srcs = r.sourcePortIds;
+ if (std::find(srcs.begin(), srcs.end(), src->portId) != srcs.end()) {
+ if (!allowedSinkPorts[r.sinkPortId]) { // prefer non-exclusive
+ allowedSinkPorts[r.sinkPortId] = !r.isExclusive;
+ }
+ }
+ }
+ }
+ for (auto sink : sinks) {
+ if (allowedSinkPorts.count(sink->portId) == 0) {
+ LOG(ERROR) << __func__ << ": there is no route to the sink port id " << sink->portId;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ }
+
+ auto& patches = getConfig().patches;
+ auto existing = patches.end();
+ std::optional<decltype(mPatches)> patchesBackup;
+ if (in_requested.id != 0) {
+ existing = findById<AudioPatch>(patches, in_requested.id);
+ if (existing != patches.end()) {
+ patchesBackup = mPatches;
+ cleanUpPatch(existing->id);
+ } else {
+ LOG(ERROR) << __func__ << ": not found existing patch id " << in_requested.id;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ }
+ // Validate the requested patch.
+ for (const auto& [sinkPortId, nonExclusive] : allowedSinkPorts) {
+ if (!nonExclusive && mPatches.count(sinkPortId) != 0) {
+ LOG(ERROR) << __func__ << ": sink port id " << sinkPortId
+ << "is exclusive and is already used by some other patch";
+ if (patchesBackup.has_value()) {
+ mPatches = std::move(*patchesBackup);
+ }
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ }
+ *_aidl_return = in_requested;
+ _aidl_return->minimumStreamBufferSizeFrames = kMinimumStreamBufferSizeFrames;
+ _aidl_return->latenciesMs.clear();
+ _aidl_return->latenciesMs.insert(_aidl_return->latenciesMs.end(),
+ _aidl_return->sinkPortConfigIds.size(), kLatencyMs);
+ AudioPatch oldPatch{};
+ if (existing == patches.end()) {
+ _aidl_return->id = getConfig().nextPatchId++;
+ patches.push_back(*_aidl_return);
+ existing = patches.begin() + (patches.size() - 1);
+ } else {
+ oldPatch = *existing;
+ *existing = *_aidl_return;
+ }
+ registerPatch(*existing);
+ updateStreamsConnectedState(oldPatch, *_aidl_return);
+
+ LOG(DEBUG) << __func__ << ": " << (oldPatch.id == 0 ? "created" : "updated") << " patch "
+ << _aidl_return->toString();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requested,
+ AudioPortConfig* out_suggested, bool* _aidl_return) {
+ LOG(DEBUG) << __func__ << ": requested " << in_requested.toString();
+ auto& configs = getConfig().portConfigs;
+ auto existing = configs.end();
+ if (in_requested.id != 0) {
+ if (existing = findById<AudioPortConfig>(configs, in_requested.id);
+ existing == configs.end()) {
+ LOG(ERROR) << __func__ << ": existing port config id " << in_requested.id
+ << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ }
+
+ const int portId = existing != configs.end() ? existing->portId : in_requested.portId;
+ if (portId == 0) {
+ LOG(ERROR) << __func__ << ": input port config does not specify portId";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& ports = getConfig().ports;
+ auto portIt = findById<AudioPort>(ports, portId);
+ if (portIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": input port config points to non-existent portId " << portId;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (existing != configs.end()) {
+ *out_suggested = *existing;
+ } else {
+ AudioPortConfig newConfig;
+ if (generateDefaultPortConfig(*portIt, &newConfig)) {
+ *out_suggested = newConfig;
+ } else {
+ LOG(ERROR) << __func__ << ": unable generate a default config for port " << portId;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ }
+ // From this moment, 'out_suggested' is either an existing port config,
+ // or a new generated config. Now attempt to update it according to the specified
+ // fields of 'in_requested'.
+
+ bool requestedIsValid = true, requestedIsFullySpecified = true;
+
+ AudioIoFlags portFlags = portIt->flags;
+ if (in_requested.flags.has_value()) {
+ if (in_requested.flags.value() != portFlags) {
+ LOG(WARNING) << __func__ << ": requested flags "
+ << in_requested.flags.value().toString() << " do not match port's "
+ << portId << " flags " << portFlags.toString();
+ requestedIsValid = false;
+ }
+ } else {
+ requestedIsFullySpecified = false;
+ }
+
+ AudioProfile portProfile;
+ if (in_requested.format.has_value()) {
+ const auto& format = in_requested.format.value();
+ if (findAudioProfile(*portIt, format, &portProfile)) {
+ out_suggested->format = format;
+ } else {
+ LOG(WARNING) << __func__ << ": requested format " << format.toString()
+ << " is not found in port's " << portId << " profiles";
+ requestedIsValid = false;
+ }
+ } else {
+ requestedIsFullySpecified = false;
+ }
+ if (!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) {
+ LOG(ERROR) << __func__ << ": port " << portId << " does not support format "
+ << out_suggested->format.value().toString() << " anymore";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ if (in_requested.channelMask.has_value()) {
+ const auto& channelMask = in_requested.channelMask.value();
+ if (find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) !=
+ portProfile.channelMasks.end()) {
+ out_suggested->channelMask = channelMask;
+ } else {
+ LOG(WARNING) << __func__ << ": requested channel mask " << channelMask.toString()
+ << " is not supported for the format " << portProfile.format.toString()
+ << " by the port " << portId;
+ requestedIsValid = false;
+ }
+ } else {
+ requestedIsFullySpecified = false;
+ }
+
+ if (in_requested.sampleRate.has_value()) {
+ const auto& sampleRate = in_requested.sampleRate.value();
+ if (find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(),
+ sampleRate.value) != portProfile.sampleRates.end()) {
+ out_suggested->sampleRate = sampleRate;
+ } else {
+ LOG(WARNING) << __func__ << ": requested sample rate " << sampleRate.value
+ << " is not supported for the format " << portProfile.format.toString()
+ << " by the port " << portId;
+ requestedIsValid = false;
+ }
+ } else {
+ requestedIsFullySpecified = false;
+ }
+
+ if (in_requested.gain.has_value()) {
+ // Let's pretend that gain can always be applied.
+ out_suggested->gain = in_requested.gain.value();
+ }
+
+ if (existing == configs.end() && requestedIsValid && requestedIsFullySpecified) {
+ out_suggested->id = getConfig().nextPortId++;
+ configs.push_back(*out_suggested);
+ *_aidl_return = true;
+ LOG(DEBUG) << __func__ << ": created new port config " << out_suggested->toString();
+ } else if (existing != configs.end() && requestedIsValid) {
+ *existing = *out_suggested;
+ *_aidl_return = true;
+ LOG(DEBUG) << __func__ << ": updated port config " << out_suggested->toString();
+ } else {
+ LOG(DEBUG) << __func__ << ": not applied; existing config ? " << (existing != configs.end())
+ << "; requested is valid? " << requestedIsValid << ", fully specified? "
+ << requestedIsFullySpecified;
+ *_aidl_return = false;
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) {
+ auto& patches = getConfig().patches;
+ auto patchIt = findById<AudioPatch>(patches, in_patchId);
+ if (patchIt != patches.end()) {
+ cleanUpPatch(patchIt->id);
+ updateStreamsConnectedState(*patchIt, AudioPatch{});
+ patches.erase(patchIt);
+ LOG(DEBUG) << __func__ << ": erased patch " << in_patchId;
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": patch id " << in_patchId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+ndk::ScopedAStatus Module::resetAudioPortConfig(int32_t in_portConfigId) {
+ auto& configs = getConfig().portConfigs;
+ auto configIt = findById<AudioPortConfig>(configs, in_portConfigId);
+ if (configIt != configs.end()) {
+ if (mStreams.count(in_portConfigId) != 0) {
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+ << " has a stream opened on it";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ auto patchIt = mPatches.find(in_portConfigId);
+ if (patchIt != mPatches.end()) {
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+ << " is used by the patch with id " << patchIt->second;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ auto& initials = getConfig().initialConfigs;
+ auto initialIt = findById<AudioPortConfig>(initials, in_portConfigId);
+ if (initialIt == initials.end()) {
+ configs.erase(configIt);
+ LOG(DEBUG) << __func__ << ": erased port config " << in_portConfigId;
+ } else if (*configIt != *initialIt) {
+ *configIt = *initialIt;
+ LOG(DEBUG) << __func__ << ": reset port config " << in_portConfigId;
+ }
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+}
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
new file mode 100644
index 0000000..312df72
--- /dev/null
+++ b/audio/aidl/default/Stream.cpp
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AHAL_Stream"
+#include <android-base/logging.h>
+#include <utils/SystemClock.h>
+
+#include "core-impl/Module.h"
+#include "core-impl/Stream.h"
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+
+namespace aidl::android::hardware::audio::core {
+
+void StreamContext::fillDescriptor(StreamDescriptor* desc) {
+ if (mCommandMQ) {
+ desc->command = mCommandMQ->dupeDesc();
+ }
+ if (mReplyMQ) {
+ desc->reply = mReplyMQ->dupeDesc();
+ }
+ if (mDataMQ) {
+ desc->frameSizeBytes = mFrameSize;
+ desc->bufferSizeFrames =
+ mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize() / mFrameSize;
+ desc->audio.set<StreamDescriptor::AudioBuffer::Tag::fmq>(mDataMQ->dupeDesc());
+ }
+}
+
+bool StreamContext::isValid() const {
+ if (mCommandMQ && !mCommandMQ->isValid()) {
+ LOG(ERROR) << "command FMQ is invalid";
+ return false;
+ }
+ if (mReplyMQ && !mReplyMQ->isValid()) {
+ LOG(ERROR) << "reply FMQ is invalid";
+ return false;
+ }
+ if (mFrameSize == 0) {
+ LOG(ERROR) << "frame size is not set";
+ return false;
+ }
+ if (mDataMQ && !mDataMQ->isValid()) {
+ LOG(ERROR) << "data FMQ is invalid";
+ return false;
+ }
+ return true;
+}
+
+void StreamContext::reset() {
+ mCommandMQ.reset();
+ mReplyMQ.reset();
+ mDataMQ.reset();
+}
+
+std::string StreamWorkerCommonLogic::init() {
+ if (mCommandMQ == nullptr) return "Command MQ is null";
+ if (mReplyMQ == nullptr) return "Reply MQ is null";
+ if (mDataMQ == nullptr) return "Data MQ is null";
+ if (sizeof(decltype(mDataBuffer)::element_type) != mDataMQ->getQuantumSize()) {
+ return "Unexpected Data MQ quantum size: " + std::to_string(mDataMQ->getQuantumSize());
+ }
+ mDataBufferSize = mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize();
+ mDataBuffer.reset(new (std::nothrow) int8_t[mDataBufferSize]);
+ if (mDataBuffer == nullptr) {
+ return "Failed to allocate data buffer for element count " +
+ std::to_string(mDataMQ->getQuantumCount()) +
+ ", size in bytes: " + std::to_string(mDataBufferSize);
+ }
+ return "";
+}
+
+const std::string StreamInWorkerLogic::kThreadName = "reader";
+
+StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
+ StreamDescriptor::Command command{};
+ if (!mCommandMQ->readBlocking(&command, 1)) {
+ LOG(ERROR) << __func__ << ": reading of command from MQ failed";
+ return Status::ABORT;
+ }
+ StreamDescriptor::Reply reply{};
+ if (command.code == StreamContext::COMMAND_EXIT &&
+ command.fmqByteCount == mInternalCommandCookie) {
+ LOG(DEBUG) << __func__ << ": received EXIT command";
+ // This is an internal command, no need to reply.
+ return Status::EXIT;
+ } else if (command.code == StreamDescriptor::COMMAND_BURST && command.fmqByteCount >= 0) {
+ LOG(DEBUG) << __func__ << ": received BURST read command for " << command.fmqByteCount
+ << " bytes";
+ usleep(3000); // Simulate a blocking call into the driver.
+ const size_t byteCount = std::min({static_cast<size_t>(command.fmqByteCount),
+ mDataMQ->availableToWrite(), mDataBufferSize});
+ const bool isConnected = mIsConnected;
+ // Simulate reading of data, or provide zeroes if the stream is not connected.
+ for (size_t i = 0; i < byteCount; ++i) {
+ using buffer_type = decltype(mDataBuffer)::element_type;
+ constexpr int kBufferValueRange = std::numeric_limits<buffer_type>::max() -
+ std::numeric_limits<buffer_type>::min() + 1;
+ mDataBuffer[i] = isConnected ? (std::rand() % kBufferValueRange) +
+ std::numeric_limits<buffer_type>::min()
+ : 0;
+ }
+ bool success = byteCount > 0 ? mDataMQ->write(&mDataBuffer[0], byteCount) : true;
+ if (success) {
+ LOG(DEBUG) << __func__ << ": writing of " << byteCount << " bytes into data MQ"
+ << " succeeded; connected? " << isConnected;
+ // Frames are provided and counted regardless of connection status.
+ reply.fmqByteCount = byteCount;
+ mFrameCount += byteCount / mFrameSize;
+ if (isConnected) {
+ reply.status = STATUS_OK;
+ reply.observable.frames = mFrameCount;
+ reply.observable.timeNs = ::android::elapsedRealtimeNano();
+ } else {
+ reply.status = STATUS_INVALID_OPERATION;
+ }
+ } else {
+ LOG(WARNING) << __func__ << ": writing of " << byteCount
+ << " bytes of data to MQ failed";
+ reply.status = STATUS_NOT_ENOUGH_DATA;
+ }
+ reply.latencyMs = Module::kLatencyMs;
+ } else {
+ LOG(WARNING) << __func__ << ": invalid command (" << command.toString()
+ << ") or count: " << command.fmqByteCount;
+ reply.status = STATUS_BAD_VALUE;
+ }
+ LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
+ if (!mReplyMQ->writeBlocking(&reply, 1)) {
+ LOG(ERROR) << __func__ << ": writing of reply " << reply.toString() << " to MQ failed";
+ return Status::ABORT;
+ }
+ return Status::CONTINUE;
+}
+
+const std::string StreamOutWorkerLogic::kThreadName = "writer";
+
+StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
+ StreamDescriptor::Command command{};
+ if (!mCommandMQ->readBlocking(&command, 1)) {
+ LOG(ERROR) << __func__ << ": reading of command from MQ failed";
+ return Status::ABORT;
+ }
+ StreamDescriptor::Reply reply{};
+ if (command.code == StreamContext::COMMAND_EXIT &&
+ command.fmqByteCount == mInternalCommandCookie) {
+ LOG(DEBUG) << __func__ << ": received EXIT command";
+ // This is an internal command, no need to reply.
+ return Status::EXIT;
+ } else if (command.code == StreamDescriptor::COMMAND_BURST && command.fmqByteCount >= 0) {
+ LOG(DEBUG) << __func__ << ": received BURST write command for " << command.fmqByteCount
+ << " bytes";
+ const size_t byteCount = std::min({static_cast<size_t>(command.fmqByteCount),
+ mDataMQ->availableToRead(), mDataBufferSize});
+ bool success = byteCount > 0 ? mDataMQ->read(&mDataBuffer[0], byteCount) : true;
+ if (success) {
+ const bool isConnected = mIsConnected;
+ LOG(DEBUG) << __func__ << ": reading of " << byteCount << " bytes from data MQ"
+ << " succeeded; connected? " << isConnected;
+ // Frames are consumed and counted regardless of connection status.
+ reply.fmqByteCount = byteCount;
+ mFrameCount += byteCount / mFrameSize;
+ if (isConnected) {
+ reply.status = STATUS_OK;
+ reply.observable.frames = mFrameCount;
+ reply.observable.timeNs = ::android::elapsedRealtimeNano();
+ } else {
+ reply.status = STATUS_INVALID_OPERATION;
+ }
+ usleep(3000); // Simulate a blocking call into the driver.
+ } else {
+ LOG(WARNING) << __func__ << ": reading of " << byteCount
+ << " bytes of data from MQ failed";
+ reply.status = STATUS_NOT_ENOUGH_DATA;
+ }
+ reply.latencyMs = Module::kLatencyMs;
+ } else {
+ LOG(WARNING) << __func__ << ": invalid command (" << command.toString()
+ << ") or count: " << command.fmqByteCount;
+ reply.status = STATUS_BAD_VALUE;
+ }
+ LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
+ if (!mReplyMQ->writeBlocking(&reply, 1)) {
+ LOG(ERROR) << __func__ << ": writing of reply " << reply.toString() << " to MQ failed";
+ return Status::ABORT;
+ }
+ return Status::CONTINUE;
+}
+
+template <class Metadata, class StreamWorker>
+StreamCommon<Metadata, StreamWorker>::~StreamCommon() {
+ if (!mIsClosed) {
+ LOG(ERROR) << __func__ << ": stream was not closed prior to destruction, resource leak";
+ stopWorker();
+ // The worker and the context should clean up by themselves via destructors.
+ }
+}
+
+template <class Metadata, class StreamWorker>
+ndk::ScopedAStatus StreamCommon<Metadata, StreamWorker>::close() {
+ LOG(DEBUG) << __func__;
+ if (!mIsClosed) {
+ stopWorker();
+ LOG(DEBUG) << __func__ << ": joining the worker thread...";
+ mWorker.stop();
+ LOG(DEBUG) << __func__ << ": worker thread joined";
+ mContext.reset();
+ mIsClosed = true;
+ return ndk::ScopedAStatus::ok();
+ } else {
+ LOG(ERROR) << __func__ << ": stream was already closed";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+}
+
+template <class Metadata, class StreamWorker>
+void StreamCommon<Metadata, StreamWorker>::stopWorker() {
+ if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) {
+ LOG(DEBUG) << __func__ << ": asking the worker to stop...";
+ StreamDescriptor::Command cmd;
+ cmd.code = StreamContext::COMMAND_EXIT;
+ cmd.fmqByteCount = mContext.getInternalCommandCookie();
+ // FIXME: This can block in the case when the client wrote a command
+ // while the stream worker's cycle is not running. Need to revisit
+ // when implementing standby and pause/resume.
+ if (!commandMQ->writeBlocking(&cmd, 1)) {
+ LOG(ERROR) << __func__ << ": failed to write exit command to the MQ";
+ }
+ LOG(DEBUG) << __func__ << ": done";
+ }
+}
+
+template <class Metadata, class StreamWorker>
+ndk::ScopedAStatus StreamCommon<Metadata, StreamWorker>::updateMetadata(const Metadata& metadata) {
+ LOG(DEBUG) << __func__;
+ if (!mIsClosed) {
+ mMetadata = metadata;
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": stream was closed";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+StreamIn::StreamIn(const SinkMetadata& sinkMetadata, StreamContext context)
+ : StreamCommon<SinkMetadata, StreamInWorker>(sinkMetadata, std::move(context)) {
+ LOG(DEBUG) << __func__;
+}
+
+StreamOut::StreamOut(const SourceMetadata& sourceMetadata, StreamContext context,
+ const std::optional<AudioOffloadInfo>& offloadInfo)
+ : StreamCommon<SourceMetadata, StreamOutWorker>(sourceMetadata, std::move(context)),
+ mOffloadInfo(offloadInfo) {
+ LOG(DEBUG) << __func__;
+}
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc b/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc
new file mode 100644
index 0000000..68bbf5b
--- /dev/null
+++ b/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc
@@ -0,0 +1,9 @@
+service vendor.audio-effect-hal-aidl /vendor/bin/hw/android.hardware.audio.effect.service-aidl.example
+ class hal
+ user audioserver
+ # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
+ group audio media
+ capabilities BLOCK_SUSPEND
+ ioprio rt 4
+ task_profiles ProcessCapacityHigh HighPerformance
+ onrestart restart audioserver
diff --git a/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml b/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml
new file mode 100644
index 0000000..fdc53a3
--- /dev/null
+++ b/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.audio.effect</name>
+ <version>1</version>
+ <fqname>IFactory/default</fqname>
+ </hal>
+</manifest>
diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.example.rc b/audio/aidl/default/android.hardware.audio.service-aidl.example.rc
new file mode 100644
index 0000000..02a9c37
--- /dev/null
+++ b/audio/aidl/default/android.hardware.audio.service-aidl.example.rc
@@ -0,0 +1,9 @@
+service vendor.audio-hal-aidl /vendor/bin/hw/android.hardware.audio.service-aidl.example
+ class hal
+ user audioserver
+ # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
+ group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
+ capabilities BLOCK_SUSPEND
+ ioprio rt 4
+ task_profiles ProcessCapacityHigh HighPerformance
+ onrestart restart audioserver
diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.xml b/audio/aidl/default/android.hardware.audio.service-aidl.xml
new file mode 100644
index 0000000..bb4b01a
--- /dev/null
+++ b/audio/aidl/default/android.hardware.audio.service-aidl.xml
@@ -0,0 +1,12 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.audio.core</name>
+ <version>1</version>
+ <fqname>IModule/default</fqname>
+ </hal>
+ <hal format="aidl">
+ <name>android.hardware.audio.core</name>
+ <version>1</version>
+ <fqname>IConfig/default</fqname>
+ </hal>
+</manifest>
diff --git a/audio/aidl/default/equalizer/Android.bp b/audio/aidl/default/equalizer/Android.bp
new file mode 100644
index 0000000..b842149
--- /dev/null
+++ b/audio/aidl/default/equalizer/Android.bp
@@ -0,0 +1,45 @@
+/*
+ * 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_library_shared {
+ name: "libequalizer",
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libstagefright_foundation",
+ ],
+ defaults: [
+ "latest_android_media_audio_common_types_ndk_shared",
+ "latest_android_hardware_audio_effect_ndk_shared",
+ ],
+ include_dirs: ["hardware/interfaces/audio/aidl/default/include/equalizer-impl"],
+ srcs: [
+ "Equalizer.cpp",
+ ],
+ visibility: [
+ "//hardware/interfaces/audio/aidl/default",
+ ],
+}
diff --git a/audio/aidl/default/equalizer/Equalizer.cpp b/audio/aidl/default/equalizer/Equalizer.cpp
new file mode 100644
index 0000000..8b157fa
--- /dev/null
+++ b/audio/aidl/default/equalizer/Equalizer.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AHAL_Equalizer"
+#include <android-base/logging.h>
+
+#include "Equalizer.h"
+
+namespace aidl::android::hardware::audio::effect {
+
+ndk::ScopedAStatus Equalizer::open() {
+ LOG(DEBUG) << __func__;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Equalizer::close() {
+ LOG(DEBUG) << __func__;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Equalizer::getDescriptor(Descriptor* _aidl_return) {
+ LOG(DEBUG) << __func__ << "descriptor " << mDesc.toString();
+ *_aidl_return = mDesc;
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/include/core-impl/Config.h b/audio/aidl/default/include/core-impl/Config.h
new file mode 100644
index 0000000..b62a14b
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Config.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/audio/core/BnConfig.h>
+
+namespace aidl::android::hardware::audio::core {
+
+class Config : public BnConfig {};
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Configuration.h b/audio/aidl/default/include/core-impl/Configuration.h
new file mode 100644
index 0000000..d5cd30b
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include <aidl/android/hardware/audio/core/AudioPatch.h>
+#include <aidl/android/hardware/audio/core/AudioRoute.h>
+#include <aidl/android/media/audio/common/AudioPort.h>
+#include <aidl/android/media/audio/common/AudioPortConfig.h>
+
+namespace aidl::android::hardware::audio::core::internal {
+
+struct Configuration {
+ std::vector<::aidl::android::media::audio::common::AudioPort> ports;
+ std::vector<::aidl::android::media::audio::common::AudioPortConfig> portConfigs;
+ std::vector<::aidl::android::media::audio::common::AudioPortConfig> initialConfigs;
+ // Port id -> List of profiles to use when the device port state is set to 'connected'.
+ std::map<int32_t, std::vector<::aidl::android::media::audio::common::AudioProfile>>
+ connectedProfiles;
+ std::vector<AudioRoute> routes;
+ std::vector<AudioPatch> patches;
+ int32_t nextPortId = 1;
+ int32_t nextPatchId = 1;
+};
+
+Configuration& getNullPrimaryConfiguration();
+
+} // namespace aidl::android::hardware::audio::core::internal
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
new file mode 100644
index 0000000..61516b2
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include <aidl/android/hardware/audio/core/BnModule.h>
+
+#include "core-impl/Configuration.h"
+#include "core-impl/Stream.h"
+
+namespace aidl::android::hardware::audio::core {
+
+class Module : public BnModule {
+ public:
+ // This value is used for all AudioPatches and reported by all streams.
+ static constexpr int32_t kLatencyMs = 10;
+
+ private:
+ ndk::ScopedAStatus setModuleDebug(
+ const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
+ ndk::ScopedAStatus connectExternalDevice(
+ const ::aidl::android::media::audio::common::AudioPort& in_templateIdAndAdditionalData,
+ ::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
+ ndk::ScopedAStatus disconnectExternalDevice(int32_t in_portId) override;
+ ndk::ScopedAStatus getAudioPatches(std::vector<AudioPatch>* _aidl_return) override;
+ ndk::ScopedAStatus getAudioPort(
+ int32_t in_portId,
+ ::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
+ ndk::ScopedAStatus getAudioPortConfigs(
+ std::vector<::aidl::android::media::audio::common::AudioPortConfig>* _aidl_return)
+ override;
+ ndk::ScopedAStatus getAudioPorts(
+ std::vector<::aidl::android::media::audio::common::AudioPort>* _aidl_return) override;
+ ndk::ScopedAStatus getAudioRoutes(std::vector<AudioRoute>* _aidl_return) override;
+ ndk::ScopedAStatus getAudioRoutesForAudioPort(
+ int32_t in_portId,
+ std::vector<::aidl::android::hardware::audio::core::AudioRoute>* _aidl_return) override;
+ ndk::ScopedAStatus openInputStream(
+ const ::aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments&
+ in_args,
+ ::aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn* _aidl_return)
+ override;
+ ndk::ScopedAStatus openOutputStream(
+ const ::aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments&
+ in_args,
+ ::aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn* _aidl_return)
+ override;
+ ndk::ScopedAStatus setAudioPatch(const AudioPatch& in_requested,
+ AudioPatch* _aidl_return) override;
+ ndk::ScopedAStatus setAudioPortConfig(
+ const ::aidl::android::media::audio::common::AudioPortConfig& in_requested,
+ ::aidl::android::media::audio::common::AudioPortConfig* out_suggested,
+ bool* _aidl_return) override;
+ ndk::ScopedAStatus resetAudioPatch(int32_t in_patchId) override;
+ ndk::ScopedAStatus resetAudioPortConfig(int32_t in_portConfigId) override;
+
+ void cleanUpPatch(int32_t patchId);
+ ndk::ScopedAStatus createStreamContext(
+ int32_t in_portConfigId, int64_t in_bufferSizeFrames,
+ ::aidl::android::hardware::audio::core::StreamContext* out_context);
+ ndk::ScopedAStatus findPortIdForNewStream(
+ int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port);
+ internal::Configuration& getConfig();
+ void registerPatch(const AudioPatch& patch);
+ void updateStreamsConnectedState(const AudioPatch& oldPatch, const AudioPatch& newPatch);
+
+ // This value is used for all AudioPatches.
+ static constexpr int32_t kMinimumStreamBufferSizeFrames = 16;
+ // The maximum stream buffer size is 1 GiB = 2 ** 30 bytes;
+ static constexpr int32_t kMaximumStreamBufferSizeBytes = 1 << 30;
+
+ std::unique_ptr<internal::Configuration> mConfig;
+ ModuleDebug mDebug;
+ // ids of ports created at runtime via 'connectExternalDevice'.
+ std::set<int32_t> mConnectedDevicePorts;
+ Streams mStreams;
+ // Maps port ids and port config ids to patch ids.
+ // Multimap because both ports and configs can be used by multiple patches.
+ std::multimap<int32_t, int32_t> mPatches;
+};
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
new file mode 100644
index 0000000..488edf1
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <cstdlib>
+#include <map>
+#include <memory>
+#include <optional>
+#include <variant>
+
+#include <StreamWorker.h>
+#include <aidl/android/hardware/audio/common/SinkMetadata.h>
+#include <aidl/android/hardware/audio/common/SourceMetadata.h>
+#include <aidl/android/hardware/audio/core/BnStreamIn.h>
+#include <aidl/android/hardware/audio/core/BnStreamOut.h>
+#include <aidl/android/hardware/audio/core/StreamDescriptor.h>
+#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
+#include <fmq/AidlMessageQueue.h>
+#include <system/thread_defs.h>
+
+#include "core-impl/utils.h"
+
+namespace aidl::android::hardware::audio::core {
+
+// This class is similar to StreamDescriptor, but unlike
+// the descriptor, it actually owns the objects implementing
+// data exchange: FMQs etc, whereas StreamDescriptor only
+// contains their descriptors.
+class StreamContext {
+ public:
+ typedef ::android::AidlMessageQueue<
+ StreamDescriptor::Command,
+ ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
+ CommandMQ;
+ typedef ::android::AidlMessageQueue<
+ StreamDescriptor::Reply, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
+ ReplyMQ;
+ typedef ::android::AidlMessageQueue<
+ int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
+ DataMQ;
+
+ // Ensure that this value is not used by any of StreamDescriptor.COMMAND_*
+ static constexpr int COMMAND_EXIT = -1;
+
+ StreamContext() = default;
+ StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
+ size_t frameSize, std::unique_ptr<DataMQ> dataMQ)
+ : mCommandMQ(std::move(commandMQ)),
+ mInternalCommandCookie(std::rand()),
+ mReplyMQ(std::move(replyMQ)),
+ mFrameSize(frameSize),
+ mDataMQ(std::move(dataMQ)) {}
+ StreamContext(StreamContext&& other)
+ : mCommandMQ(std::move(other.mCommandMQ)),
+ mInternalCommandCookie(other.mInternalCommandCookie),
+ mReplyMQ(std::move(other.mReplyMQ)),
+ mFrameSize(other.mFrameSize),
+ mDataMQ(std::move(other.mDataMQ)) {}
+ StreamContext& operator=(StreamContext&& other) {
+ mCommandMQ = std::move(other.mCommandMQ);
+ mInternalCommandCookie = other.mInternalCommandCookie;
+ mReplyMQ = std::move(other.mReplyMQ);
+ mFrameSize = other.mFrameSize;
+ mDataMQ = std::move(other.mDataMQ);
+ return *this;
+ }
+
+ void fillDescriptor(StreamDescriptor* desc);
+ CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
+ DataMQ* getDataMQ() const { return mDataMQ.get(); }
+ size_t getFrameSize() const { return mFrameSize; }
+ int getInternalCommandCookie() const { return mInternalCommandCookie; }
+ ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
+ bool isValid() const;
+ void reset();
+
+ private:
+ std::unique_ptr<CommandMQ> mCommandMQ;
+ int mInternalCommandCookie; // The value used to confirm that the command was posted internally
+ std::unique_ptr<ReplyMQ> mReplyMQ;
+ size_t mFrameSize;
+ std::unique_ptr<DataMQ> mDataMQ;
+};
+
+class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
+ public:
+ void setIsConnected(bool connected) { mIsConnected = connected; }
+
+ protected:
+ explicit StreamWorkerCommonLogic(const StreamContext& context)
+ : mInternalCommandCookie(context.getInternalCommandCookie()),
+ mFrameSize(context.getFrameSize()),
+ mCommandMQ(context.getCommandMQ()),
+ mReplyMQ(context.getReplyMQ()),
+ mDataMQ(context.getDataMQ()) {}
+ std::string init() override;
+
+ // Used both by the main and worker threads.
+ std::atomic<bool> mIsConnected = false;
+ // All fields are used on the worker thread only.
+ const int mInternalCommandCookie;
+ const size_t mFrameSize;
+ StreamContext::CommandMQ* mCommandMQ;
+ StreamContext::ReplyMQ* mReplyMQ;
+ StreamContext::DataMQ* mDataMQ;
+ // We use an array and the "size" field instead of a vector to be able to detect
+ // memory allocation issues.
+ std::unique_ptr<int8_t[]> mDataBuffer;
+ size_t mDataBufferSize;
+ long mFrameCount = 0;
+};
+
+class StreamInWorkerLogic : public StreamWorkerCommonLogic {
+ public:
+ static const std::string kThreadName;
+ explicit StreamInWorkerLogic(const StreamContext& context) : StreamWorkerCommonLogic(context) {}
+
+ protected:
+ Status cycle() override;
+};
+using StreamInWorker = ::android::hardware::audio::common::StreamWorker<StreamInWorkerLogic>;
+
+class StreamOutWorkerLogic : public StreamWorkerCommonLogic {
+ public:
+ static const std::string kThreadName;
+ explicit StreamOutWorkerLogic(const StreamContext& context)
+ : StreamWorkerCommonLogic(context) {}
+
+ protected:
+ Status cycle() override;
+};
+using StreamOutWorker = ::android::hardware::audio::common::StreamWorker<StreamOutWorkerLogic>;
+
+template <class Metadata, class StreamWorker>
+class StreamCommon {
+ public:
+ ndk::ScopedAStatus close();
+ ndk::ScopedAStatus init() {
+ return mWorker.start(StreamWorker::kThreadName, ANDROID_PRIORITY_AUDIO)
+ ? ndk::ScopedAStatus::ok()
+ : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ bool isClosed() const { return mIsClosed; }
+ void setIsConnected(bool connected) { mWorker.setIsConnected(connected); }
+ ndk::ScopedAStatus updateMetadata(const Metadata& metadata);
+
+ protected:
+ StreamCommon(const Metadata& metadata, StreamContext context)
+ : mMetadata(metadata), mContext(std::move(context)), mWorker(mContext) {}
+ ~StreamCommon();
+ void stopWorker();
+
+ Metadata mMetadata;
+ StreamContext mContext;
+ StreamWorker mWorker;
+ // This variable is checked in the destructor which can be called on an arbitrary Binder thread,
+ // thus we need to ensure that any changes made by other threads are sequentially consistent.
+ std::atomic<bool> mIsClosed = false;
+};
+
+class StreamIn
+ : public StreamCommon<::aidl::android::hardware::audio::common::SinkMetadata, StreamInWorker>,
+ public BnStreamIn {
+ ndk::ScopedAStatus close() override {
+ return StreamCommon<::aidl::android::hardware::audio::common::SinkMetadata,
+ StreamInWorker>::close();
+ }
+ ndk::ScopedAStatus updateMetadata(const ::aidl::android::hardware::audio::common::SinkMetadata&
+ in_sinkMetadata) override {
+ return StreamCommon<::aidl::android::hardware::audio::common::SinkMetadata,
+ StreamInWorker>::updateMetadata(in_sinkMetadata);
+ }
+
+ public:
+ StreamIn(const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
+ StreamContext context);
+};
+
+class StreamOut : public StreamCommon<::aidl::android::hardware::audio::common::SourceMetadata,
+ StreamOutWorker>,
+ public BnStreamOut {
+ ndk::ScopedAStatus close() override {
+ return StreamCommon<::aidl::android::hardware::audio::common::SourceMetadata,
+ StreamOutWorker>::close();
+ }
+ ndk::ScopedAStatus updateMetadata(
+ const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata)
+ override {
+ return StreamCommon<::aidl::android::hardware::audio::common::SourceMetadata,
+ StreamOutWorker>::updateMetadata(in_sourceMetadata);
+ }
+
+ public:
+ StreamOut(const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
+ StreamContext context,
+ const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+ offloadInfo);
+
+ private:
+ std::optional<::aidl::android::media::audio::common::AudioOffloadInfo> mOffloadInfo;
+};
+
+class StreamWrapper {
+ public:
+ explicit StreamWrapper(std::shared_ptr<StreamIn> streamIn) : mStream(streamIn) {}
+ explicit StreamWrapper(std::shared_ptr<StreamOut> streamOut) : mStream(streamOut) {}
+ bool isStreamOpen() const {
+ return std::visit(
+ [](auto&& ws) -> bool {
+ auto s = ws.lock();
+ return s && !s->isClosed();
+ },
+ mStream);
+ }
+ void setStreamIsConnected(bool connected) {
+ std::visit(
+ [&](auto&& ws) {
+ auto s = ws.lock();
+ if (s) s->setIsConnected(connected);
+ },
+ mStream);
+ }
+
+ private:
+ std::variant<std::weak_ptr<StreamIn>, std::weak_ptr<StreamOut>> mStream;
+};
+
+class Streams {
+ public:
+ Streams() = default;
+ Streams(const Streams&) = delete;
+ Streams& operator=(const Streams&) = delete;
+ size_t count(int32_t id) {
+ // Streams do not remove themselves from the collection on close.
+ erase_if(mStreams, [](const auto& pair) { return !pair.second.isStreamOpen(); });
+ return mStreams.count(id);
+ }
+ void insert(int32_t portId, int32_t portConfigId, StreamWrapper sw) {
+ mStreams.insert(std::pair{portConfigId, sw});
+ mStreams.insert(std::pair{portId, std::move(sw)});
+ }
+ void setStreamIsConnected(int32_t portConfigId, bool connected) {
+ if (auto it = mStreams.find(portConfigId); it != mStreams.end()) {
+ it->second.setStreamIsConnected(connected);
+ }
+ }
+
+ private:
+ // Maps port ids and port config ids to streams. Multimap because a port
+ // (not port config) can have multiple streams opened on it.
+ std::multimap<int32_t, StreamWrapper> mStreams;
+};
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/utils.h b/audio/aidl/default/include/core-impl/utils.h
new file mode 100644
index 0000000..9d06f08
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/utils.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <set>
+#include <vector>
+
+namespace aidl::android::hardware::audio::core {
+
+// Return whether all the elements in the vector are unique.
+template <typename T>
+bool all_unique(const std::vector<T>& v) {
+ return std::set<T>(v.begin(), v.end()).size() == v.size();
+}
+
+// Erase all the specified elements from a map.
+template <typename C, typename V>
+auto erase_all(C& c, const V& keys) {
+ auto oldSize = c.size();
+ for (auto& k : keys) {
+ c.erase(k);
+ }
+ return oldSize - c.size();
+}
+
+// Erase all the elements in the container that satisfy the provided predicate.
+template <typename C, typename P>
+auto erase_if(C& c, P pred) {
+ auto oldSize = c.size();
+ for (auto it = c.begin(); it != c.end();) {
+ if (pred(*it)) {
+ it = c.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return oldSize - c.size();
+}
+
+// Erase all the elements in the map that have specified values.
+template <typename C, typename V>
+auto erase_all_values(C& c, const V& values) {
+ return erase_if(c, [values](const auto& pair) { return values.count(pair.second) != 0; });
+}
+
+// Return non-zero count of elements for any of the provided keys.
+template <typename M, typename V>
+size_t count_any(const M& m, const V& keys) {
+ for (auto& k : keys) {
+ if (size_t c = m.count(k); c != 0) return c;
+ }
+ return 0;
+}
+
+// Assuming that M is a map whose values have an 'id' field,
+// find an element with the specified id.
+template <typename M>
+auto findById(M& m, int32_t id) {
+ return std::find_if(m.begin(), m.end(), [&](const auto& p) { return p.second.id == id; });
+}
+
+// Assuming that the vector contains elements with an 'id' field,
+// find an element with the specified id.
+template <typename T>
+auto findById(std::vector<T>& v, int32_t id) {
+ return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
+}
+
+// Return elements from the vector that have specified ids, also
+// optionally return which ids were not found.
+template <typename T>
+std::vector<T*> selectByIds(std::vector<T>& v, const std::vector<int32_t>& ids,
+ std::vector<int32_t>* missingIds = nullptr) {
+ std::vector<T*> result;
+ std::set<int32_t> idsSet(ids.begin(), ids.end());
+ for (size_t i = 0; i < v.size(); ++i) {
+ T& e = v[i];
+ if (idsSet.count(e.id) != 0) {
+ result.push_back(&v[i]);
+ idsSet.erase(e.id);
+ }
+ }
+ if (missingIds) {
+ *missingIds = std::vector(idsSet.begin(), idsSet.end());
+ }
+ return result;
+}
+
+} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/effectFactory-impl/EffectFactory.h b/audio/aidl/default/include/effectFactory-impl/EffectFactory.h
new file mode 100644
index 0000000..8da5525
--- /dev/null
+++ b/audio/aidl/default/include/effectFactory-impl/EffectFactory.h
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <vector>
+
+#include <aidl/android/hardware/audio/effect/BnFactory.h>
+
+namespace aidl::android::hardware::audio::effect {
+
+class Factory : public BnFactory {
+ public:
+ Factory();
+ /**
+ * @brief Get identity of all effects supported by the device, with the optional filter by type
+ * and/or by instance UUID.
+ *
+ * @param in_type Type UUID.
+ * @param in_instance Instance UUID.
+ * @param out_descriptor List of identities .
+ * @return ndk::ScopedAStatus
+ */
+ ndk::ScopedAStatus queryEffects(
+ const std::optional<::aidl::android::media::audio::common::AudioUuid>& in_type,
+ const std::optional<::aidl::android::media::audio::common::AudioUuid>& in_instance,
+ std::vector<Descriptor::Identity>* out_descriptor) override;
+
+ /**
+ * @brief Create an effect instance for a certain implementation (identified by UUID).
+ *
+ * @param in_impl_uuid Effect implementation UUID.
+ * @param _aidl_return A pointer to created effect instance.
+ * @return ndk::ScopedAStatus
+ */
+ ndk::ScopedAStatus createEffect(
+ const ::aidl::android::media::audio::common::AudioUuid& in_impl_uuid,
+ std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>* _aidl_return)
+ override;
+
+ /**
+ * @brief Destroy an effect instance.
+ *
+ * @param in_handle Effect instance handle.
+ * @return ndk::ScopedAStatus
+ */
+ ndk::ScopedAStatus destroyEffect(
+ const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_handle)
+ override;
+
+ private:
+ // List of effect descriptors supported by the devices.
+ std::vector<Descriptor::Identity> mIdentityList;
+};
+} // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/include/equalizer-impl/Equalizer.h b/audio/aidl/default/include/equalizer-impl/Equalizer.h
new file mode 100644
index 0000000..ea16cb9
--- /dev/null
+++ b/audio/aidl/default/include/equalizer-impl/Equalizer.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/audio/effect/BnEffect.h>
+#include <cstdlib>
+
+namespace aidl::android::hardware::audio::effect {
+
+// Equalizer type UUID.
+static const ::aidl::android::media::audio::common::AudioUuid EqualizerTypeUUID = {
+ static_cast<int32_t>(0x0bed4300),
+ 0xddd6,
+ 0x11db,
+ 0x8f34,
+ {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
+
+// Equalizer implementation UUID.
+static const ::aidl::android::media::audio::common::AudioUuid EqualizerSwImplUUID = {
+ static_cast<int32_t>(0x0bed4300),
+ 0x847d,
+ 0x11df,
+ 0xbb17,
+ {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
+
+class Equalizer : public BnEffect {
+ public:
+ Equalizer() = default;
+ ndk::ScopedAStatus open() override;
+ ndk::ScopedAStatus close() override;
+ ndk::ScopedAStatus getDescriptor(Descriptor* _aidl_return) override;
+
+ private:
+ // Effect descriptor.
+ Descriptor mDesc = {.common = {.id = {.type = EqualizerTypeUUID, .uuid = EqualizerSwImplUUID}}};
+};
+} // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/include/visualizer-impl/Visualizer.h b/audio/aidl/default/include/visualizer-impl/Visualizer.h
new file mode 100644
index 0000000..4b82dd0
--- /dev/null
+++ b/audio/aidl/default/include/visualizer-impl/Visualizer.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdlib>
+
+namespace aidl::android::hardware::audio::effect {
+
+// Visualizer implementation UUID.
+static const ::aidl::android::media::audio::common::AudioUuid VisualizerUUID = {
+ static_cast<int32_t>(0x1d4033c0),
+ 0x8557,
+ 0x11df,
+ 0x9f2d,
+ {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
+
+} // namespace aidl::android::hardware::audio::effect
\ No newline at end of file
diff --git a/audio/aidl/default/main.cpp b/audio/aidl/default/main.cpp
new file mode 100644
index 0000000..15874a0
--- /dev/null
+++ b/audio/aidl/default/main.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#include <cstdlib>
+#include <ctime>
+
+#include "core-impl/Config.h"
+#include "core-impl/Module.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using aidl::android::hardware::audio::core::Config;
+using aidl::android::hardware::audio::core::Module;
+
+int main() {
+ // Random values are used in the implementation.
+ std::srand(std::time(nullptr));
+
+ // This is a debug implementation, always enable debug logging.
+ android::base::SetMinimumLogSeverity(::android::base::DEBUG);
+ ABinderProcess_setThreadPoolMaxThreadCount(16);
+
+ // Make the default config service
+ auto config = ndk::SharedRefBase::make<Config>();
+ const std::string configName = std::string() + Config::descriptor + "/default";
+ binder_status_t status =
+ AServiceManager_addService(config->asBinder().get(), configName.c_str());
+ CHECK_EQ(STATUS_OK, status);
+
+ // Make the default module
+ auto moduleDefault = ndk::SharedRefBase::make<Module>();
+ const std::string moduleDefaultName = std::string() + Module::descriptor + "/default";
+ status = AServiceManager_addService(moduleDefault->asBinder().get(), moduleDefaultName.c_str());
+ CHECK_EQ(STATUS_OK, status);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
new file mode 100644
index 0000000..6ea7cef
--- /dev/null
+++ b/audio/aidl/vts/Android.bp
@@ -0,0 +1,71 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+ name: "VtsHalAudioCoreTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ "latest_android_hardware_audio_common_ndk_static",
+ "latest_android_hardware_audio_core_ndk_static",
+ "latest_android_media_audio_common_types_ndk_static",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcutils",
+ "libfmq",
+ ],
+ static_libs: [
+ "android.hardware.common-V2-ndk",
+ "android.hardware.common.fmq-V1-ndk",
+ "libaudioaidlcommon",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "ModuleConfig.cpp",
+ "VtsHalAudioCoreTargetTest.cpp",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
+
+cc_test {
+ name: "VtsHalAudioEffectTargetTest",
+ defaults: [
+ "latest_android_media_audio_common_types_ndk_static",
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsHalAudioEffectTargetTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.audio.effect-V1-ndk",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wthread-safety",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/audio/aidl/vts/AudioHalBinderServiceUtil.h b/audio/aidl/vts/AudioHalBinderServiceUtil.h
new file mode 100644
index 0000000..e928286
--- /dev/null
+++ b/audio/aidl/vts/AudioHalBinderServiceUtil.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+#include <android-base/properties.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <android-base/logging.h>
+
+class AudioHalBinderServiceUtil {
+ public:
+ ndk::SpAIBinder connectToService(const std::string& serviceName) {
+ mServiceName = serviceName;
+ mBinder = ndk::SpAIBinder(AServiceManager_getService(serviceName.c_str()));
+ if (mBinder == nullptr) {
+ LOG(ERROR) << "Failed to get service " << serviceName;
+ } else {
+ LOG(DEBUG) << "succeed to get service " << serviceName;
+ }
+ return mBinder;
+ }
+
+ ndk::SpAIBinder restartService(
+ std::chrono::milliseconds timeoutMs = std::chrono::milliseconds(3000)) {
+ mDeathHandler.reset(new AidlDeathRecipient(mBinder));
+ if (STATUS_OK != mDeathHandler->linkToDeath()) {
+ LOG(ERROR) << "linkToDeath failed";
+ return nullptr;
+ }
+ if (!android::base::SetProperty("sys.audio.restart.hal", "1")) {
+ LOG(ERROR) << "SetProperty failed";
+ return nullptr;
+ }
+ if (!mDeathHandler->waitForFired(timeoutMs)) {
+ LOG(ERROR) << "Timeout wait for death";
+ return nullptr;
+ }
+ mDeathHandler.reset();
+ return connectToService(mServiceName);
+ }
+
+ private:
+ class AidlDeathRecipient {
+ public:
+ explicit AidlDeathRecipient(const ndk::SpAIBinder& binder)
+ : binder(binder), recipient(AIBinder_DeathRecipient_new(&binderDiedCallbackAidl)) {}
+
+ binder_status_t linkToDeath() {
+ return AIBinder_linkToDeath(binder.get(), recipient.get(), this);
+ }
+
+ bool waitForFired(std::chrono::milliseconds timeoutMs) {
+ std::unique_lock<std::mutex> lock(mutex);
+ condition.wait_for(lock, timeoutMs, [this]() { return fired; });
+ return fired;
+ }
+
+ private:
+ const ndk::SpAIBinder binder;
+ const ndk::ScopedAIBinder_DeathRecipient recipient;
+ std::mutex mutex;
+ std::condition_variable condition;
+ bool fired = false;
+
+ void binderDied() {
+ std::unique_lock<std::mutex> lock(mutex);
+ fired = true;
+ condition.notify_one();
+ };
+
+ static void binderDiedCallbackAidl(void* cookie) {
+ AidlDeathRecipient* self = static_cast<AidlDeathRecipient*>(cookie);
+ self->binderDied();
+ }
+ };
+
+ std::string mServiceName;
+ ndk::SpAIBinder mBinder;
+ std::unique_ptr<AidlDeathRecipient> mDeathHandler;
+};
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
new file mode 100644
index 0000000..33c5b72
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -0,0 +1,390 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+#include <chrono>
+
+#include <Utils.h>
+#include <aidl/android/media/audio/common/AudioIoFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+using namespace std::chrono_literals;
+
+using aidl::android::hardware::audio::core::IModule;
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioEncapsulationMode;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioIoFlags;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::AudioOutputFlags;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::AudioUsage;
+using aidl::android::media::audio::common::Int;
+using android::hardware::audio::common::isBitPositionFlagSet;
+
+// static
+std::optional<AudioOffloadInfo> ModuleConfig::generateOffloadInfoIfNeeded(
+ const AudioPortConfig& portConfig) {
+ if (portConfig.flags.has_value() &&
+ portConfig.flags.value().getTag() == AudioIoFlags::Tag::output &&
+ isBitPositionFlagSet(portConfig.flags.value().get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::COMPRESS_OFFLOAD)) {
+ AudioOffloadInfo offloadInfo;
+ offloadInfo.base.sampleRate = portConfig.sampleRate.value().value;
+ offloadInfo.base.channelMask = portConfig.channelMask.value();
+ offloadInfo.base.format = portConfig.format.value();
+ offloadInfo.bitRatePerSecond = 256; // Arbitrary value.
+ offloadInfo.durationUs = std::chrono::microseconds(1min).count(); // Arbitrary value.
+ offloadInfo.usage = AudioUsage::MEDIA;
+ offloadInfo.encapsulationMode = AudioEncapsulationMode::NONE;
+ return offloadInfo;
+ }
+ return {};
+}
+
+template <typename T>
+auto findById(const std::vector<T>& v, int32_t id) {
+ return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; });
+}
+
+ModuleConfig::ModuleConfig(IModule* module) {
+ mStatus = module->getAudioPorts(&mPorts);
+ if (!mStatus.isOk()) return;
+ for (const auto& port : mPorts) {
+ if (port.ext.getTag() != AudioPortExt::Tag::device) continue;
+ const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+ if (devicePort.device.type.connection.empty()) {
+ const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
+ // Permanently attached device.
+ if (isInput) {
+ mAttachedSourceDevicePorts.insert(port.id);
+ } else {
+ mAttachedSinkDevicePorts.insert(port.id);
+ }
+ } else if (port.profiles.empty()) {
+ mExternalDevicePorts.insert(port.id);
+ }
+ }
+ if (!mStatus.isOk()) return;
+ mStatus = module->getAudioRoutes(&mRoutes);
+ if (!mStatus.isOk()) return;
+ mStatus = module->getAudioPortConfigs(&mInitialConfigs);
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedDevicePorts() const {
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+ return mAttachedSinkDevicePorts.count(port.id) != 0 ||
+ mAttachedSourceDevicePorts.count(port.id) != 0;
+ });
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getExternalDevicePorts() const {
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result),
+ [&](const auto& port) { return mExternalDevicePorts.count(port.id) != 0; });
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getInputMixPorts() const {
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) {
+ return port.ext.getTag() == AudioPortExt::Tag::mix &&
+ port.flags.getTag() == AudioIoFlags::Tag::input;
+ });
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getOutputMixPorts() const {
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) {
+ return port.ext.getTag() == AudioPortExt::Tag::mix &&
+ port.flags.getTag() == AudioIoFlags::Tag::output;
+ });
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getOffloadMixPorts(bool attachedOnly, bool singlePort) const {
+ std::vector<AudioPort> result;
+ const auto mixPorts = getMixPorts(false /*isInput*/);
+ auto offloadPortIt = mixPorts.begin();
+ while (offloadPortIt != mixPorts.end()) {
+ offloadPortIt = std::find_if(offloadPortIt, mixPorts.end(), [&](const AudioPort& port) {
+ return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::COMPRESS_OFFLOAD) &&
+ (!attachedOnly || !getAttachedSinkDevicesPortsForMixPort(port).empty());
+ });
+ if (offloadPortIt == mixPorts.end()) break;
+ result.push_back(*offloadPortIt++);
+ if (singlePort) break;
+ }
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedDevicesPortsForMixPort(
+ bool isInput, const AudioPortConfig& mixPortConfig) const {
+ const auto mixPortIt = findById<AudioPort>(mPorts, mixPortConfig.portId);
+ if (mixPortIt != mPorts.end()) {
+ return getAttachedDevicesPortsForMixPort(isInput, *mixPortIt);
+ }
+ return {};
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedSinkDevicesPortsForMixPort(
+ const AudioPort& mixPort) const {
+ std::vector<AudioPort> result;
+ for (const auto& route : mRoutes) {
+ if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0 &&
+ std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(), mixPort.id) !=
+ route.sourcePortIds.end()) {
+ const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+ if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt);
+ }
+ }
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getAttachedSourceDevicesPortsForMixPort(
+ const AudioPort& mixPort) const {
+ std::vector<AudioPort> result;
+ for (const auto& route : mRoutes) {
+ if (route.sinkPortId == mixPort.id) {
+ for (const auto srcId : route.sourcePortIds) {
+ if (mAttachedSourceDevicePorts.count(srcId) != 0) {
+ const auto devicePortIt = findById<AudioPort>(mPorts, srcId);
+ if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt);
+ }
+ }
+ }
+ }
+ return result;
+}
+
+std::optional<AudioPort> ModuleConfig::getSourceMixPortForAttachedDevice() const {
+ for (const auto& route : mRoutes) {
+ if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0) {
+ const auto mixPortIt = findById<AudioPort>(mPorts, route.sourcePortIds[0]);
+ if (mixPortIt != mPorts.end()) return *mixPortIt;
+ }
+ }
+ return {};
+}
+
+std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getNonRoutableSrcSinkPair(
+ bool isInput) const {
+ const auto mixPorts = getMixPorts(isInput);
+ std::set<std::pair<int32_t, int32_t>> allowedRoutes;
+ for (const auto& route : mRoutes) {
+ for (const auto srcPortId : route.sourcePortIds) {
+ allowedRoutes.emplace(std::make_pair(srcPortId, route.sinkPortId));
+ }
+ }
+ auto make_pair = [isInput](auto& device, auto& mix) {
+ return isInput ? std::make_pair(device, mix) : std::make_pair(mix, device);
+ };
+ for (const auto portId : isInput ? mAttachedSourceDevicePorts : mAttachedSinkDevicePorts) {
+ const auto devicePortIt = findById<AudioPort>(mPorts, portId);
+ if (devicePortIt == mPorts.end()) continue;
+ auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+ for (const auto& mixPort : mixPorts) {
+ if (std::find(allowedRoutes.begin(), allowedRoutes.end(),
+ make_pair(portId, mixPort.id)) == allowedRoutes.end()) {
+ auto mixPortConfig = getSingleConfigForMixPort(isInput, mixPort);
+ if (mixPortConfig.has_value()) {
+ return make_pair(devicePortConfig, mixPortConfig.value());
+ }
+ }
+ }
+ }
+ return {};
+}
+
+std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getRoutableSrcSinkPair(bool isInput) const {
+ if (isInput) {
+ for (const auto& route : mRoutes) {
+ auto srcPortIdIt = std::find_if(
+ route.sourcePortIds.begin(), route.sourcePortIds.end(),
+ [&](const auto& portId) { return mAttachedSourceDevicePorts.count(portId); });
+ if (srcPortIdIt == route.sourcePortIds.end()) continue;
+ const auto devicePortIt = findById<AudioPort>(mPorts, *srcPortIdIt);
+ const auto mixPortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+ if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue;
+ auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+ auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+ if (!mixPortConfig.has_value()) continue;
+ return std::make_pair(devicePortConfig, mixPortConfig.value());
+ }
+ } else {
+ for (const auto& route : mRoutes) {
+ if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
+ const auto mixPortIt = findById<AudioPort>(mPorts, route.sourcePortIds[0]);
+ const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+ if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue;
+ auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+ auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+ if (!mixPortConfig.has_value()) continue;
+ return std::make_pair(mixPortConfig.value(), devicePortConfig);
+ }
+ }
+ return {};
+}
+
+std::vector<ModuleConfig::SrcSinkGroup> ModuleConfig::getRoutableSrcSinkGroups(bool isInput) const {
+ std::vector<SrcSinkGroup> result;
+ if (isInput) {
+ for (const auto& route : mRoutes) {
+ std::vector<int32_t> srcPortIds;
+ std::copy_if(route.sourcePortIds.begin(), route.sourcePortIds.end(),
+ std::back_inserter(srcPortIds), [&](const auto& portId) {
+ return mAttachedSourceDevicePorts.count(portId);
+ });
+ if (srcPortIds.empty()) continue;
+ const auto mixPortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+ if (mixPortIt == mPorts.end()) continue;
+ auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+ if (!mixPortConfig.has_value()) continue;
+ std::vector<SrcSinkPair> pairs;
+ for (const auto srcPortId : srcPortIds) {
+ const auto devicePortIt = findById<AudioPort>(mPorts, srcPortId);
+ if (devicePortIt == mPorts.end()) continue;
+ // Using all configs for every source would be too much.
+ auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+ pairs.emplace_back(devicePortConfig, mixPortConfig.value());
+ }
+ if (!pairs.empty()) {
+ result.emplace_back(route, std::move(pairs));
+ }
+ }
+ } else {
+ for (const auto& route : mRoutes) {
+ if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
+ const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
+ if (devicePortIt == mPorts.end()) continue;
+ auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
+ std::vector<SrcSinkPair> pairs;
+ for (const auto srcPortId : route.sourcePortIds) {
+ const auto mixPortIt = findById<AudioPort>(mPorts, srcPortId);
+ if (mixPortIt == mPorts.end()) continue;
+ // Using all configs for every source would be too much.
+ auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt);
+ if (mixPortConfig.has_value()) {
+ pairs.emplace_back(mixPortConfig.value(), devicePortConfig);
+ }
+ }
+ if (!pairs.empty()) {
+ result.emplace_back(route, std::move(pairs));
+ }
+ }
+ }
+ return result;
+}
+
+std::string ModuleConfig::toString() const {
+ std::string result;
+ result.append("Ports: ");
+ result.append(android::internal::ToString(mPorts));
+ result.append("\nInitial configs: ");
+ result.append(android::internal::ToString(mInitialConfigs));
+ result.append("\nAttached sink device ports: ");
+ result.append(android::internal::ToString(mAttachedSinkDevicePorts));
+ result.append("\nAttached source device ports: ");
+ result.append(android::internal::ToString(mAttachedSourceDevicePorts));
+ result.append("\nExternal device ports: ");
+ result.append(android::internal::ToString(mExternalDevicePorts));
+ result.append("\nRoutes: ");
+ result.append(android::internal::ToString(mRoutes));
+ return result;
+}
+
+static size_t combineAudioConfigs(const AudioPort& port, const AudioProfile& profile,
+ std::vector<AudioPortConfig>* result) {
+ const size_t newConfigCount = profile.channelMasks.size() * profile.sampleRates.size();
+ result->reserve(result->capacity() + newConfigCount);
+ for (auto channelMask : profile.channelMasks) {
+ for (auto sampleRate : profile.sampleRates) {
+ AudioPortConfig config{};
+ config.portId = port.id;
+ Int sr;
+ sr.value = sampleRate;
+ config.sampleRate = sr;
+ config.channelMask = channelMask;
+ config.format = profile.format;
+ config.flags = port.flags;
+ config.ext = port.ext;
+ result->push_back(std::move(config));
+ }
+ }
+ return newConfigCount;
+}
+
+static bool isDynamicProfile(const AudioProfile& profile) {
+ return (profile.format.type == AudioFormatType::DEFAULT && profile.format.encoding.empty()) ||
+ profile.sampleRates.empty() || profile.channelMasks.empty();
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
+ const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
+ std::vector<AudioPortConfig> result;
+ for (const auto& mixPort : ports) {
+ if (getAttachedDevicesPortsForMixPort(isInput, mixPort).empty()) {
+ continue;
+ }
+ for (const auto& profile : mixPort.profiles) {
+ if (isDynamicProfile(profile)) continue;
+ combineAudioConfigs(mixPort, profile, &result);
+ if (singleProfile && !result.empty()) {
+ result.resize(1);
+ return result;
+ }
+ }
+ }
+ return result;
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateAudioDevicePortConfigs(
+ const std::vector<AudioPort>& ports, bool singleProfile) const {
+ std::vector<AudioPortConfig> result;
+ for (const auto& devicePort : ports) {
+ const size_t resultSizeBefore = result.size();
+ for (const auto& profile : devicePort.profiles) {
+ combineAudioConfigs(devicePort, profile, &result);
+ if (singleProfile && !result.empty()) {
+ result.resize(1);
+ return result;
+ }
+ }
+ if (resultSizeBefore == result.size()) {
+ std::copy_if(mInitialConfigs.begin(), mInitialConfigs.end(), std::back_inserter(result),
+ [&](const auto& config) { return config.portId == devicePort.id; });
+ if (resultSizeBefore == result.size()) {
+ AudioPortConfig empty;
+ empty.portId = devicePort.id;
+ empty.ext = devicePort.ext;
+ result.push_back(empty);
+ }
+ }
+ if (singleProfile) return result;
+ }
+ return result;
+}
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
new file mode 100644
index 0000000..dc109a7
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <aidl/android/hardware/audio/core/AudioRoute.h>
+#include <aidl/android/hardware/audio/core/IModule.h>
+#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
+#include <aidl/android/media/audio/common/AudioPort.h>
+
+class ModuleConfig {
+ public:
+ using SrcSinkPair = std::pair<aidl::android::media::audio::common::AudioPortConfig,
+ aidl::android::media::audio::common::AudioPortConfig>;
+ using SrcSinkGroup =
+ std::pair<aidl::android::hardware::audio::core::AudioRoute, std::vector<SrcSinkPair>>;
+
+ static std::optional<aidl::android::media::audio::common::AudioOffloadInfo>
+ generateOffloadInfoIfNeeded(
+ const aidl::android::media::audio::common::AudioPortConfig& portConfig);
+
+ explicit ModuleConfig(aidl::android::hardware::audio::core::IModule* module);
+ const ndk::ScopedAStatus& getStatus() const { return mStatus; }
+ std::string getError() const { return mStatus.getMessage(); }
+
+ std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getExternalDevicePorts() const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getInputMixPorts() const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getOutputMixPorts() const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getMixPorts(bool isInput) const {
+ return isInput ? getInputMixPorts() : getOutputMixPorts();
+ }
+ std::vector<aidl::android::media::audio::common::AudioPort> getOffloadMixPorts(
+ bool attachedOnly, bool singlePort) const;
+
+ std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+ bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const {
+ return isInput ? getAttachedSourceDevicesPortsForMixPort(mixPort)
+ : getAttachedSinkDevicesPortsForMixPort(mixPort);
+ }
+ std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+ bool isInput,
+ const aidl::android::media::audio::common::AudioPortConfig& mixPortConfig) const;
+ std::vector<aidl::android::media::audio::common::AudioPort>
+ getAttachedSinkDevicesPortsForMixPort(
+ const aidl::android::media::audio::common::AudioPort& mixPort) const;
+ std::vector<aidl::android::media::audio::common::AudioPort>
+ getAttachedSourceDevicesPortsForMixPort(
+ const aidl::android::media::audio::common::AudioPort& mixPort) const;
+ std::optional<aidl::android::media::audio::common::AudioPort>
+ getSourceMixPortForAttachedDevice() const;
+
+ std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
+ std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
+ std::vector<SrcSinkGroup> getRoutableSrcSinkGroups(bool isInput) const;
+
+ std::vector<aidl::android::media::audio::common::AudioPortConfig>
+ getPortConfigsForAttachedDevicePorts() const {
+ return generateAudioDevicePortConfigs(getAttachedDevicePorts(), false);
+ }
+ std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts()
+ const {
+ auto inputs = generateAudioMixPortConfigs(getInputMixPorts(), true, false);
+ auto outputs = generateAudioMixPortConfigs(getOutputMixPorts(), false, false);
+ inputs.insert(inputs.end(), outputs.begin(), outputs.end());
+ return inputs;
+ }
+ std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+ bool isInput) const {
+ return generateAudioMixPortConfigs(getMixPorts(isInput), isInput, false);
+ }
+ std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+ bool isInput, const aidl::android::media::audio::common::AudioPort& port) const {
+ return generateAudioMixPortConfigs({port}, isInput, false);
+ }
+ std::optional<aidl::android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+ bool isInput) const {
+ const auto config = generateAudioMixPortConfigs(getMixPorts(isInput), isInput, true);
+ if (!config.empty()) {
+ return *config.begin();
+ }
+ return {};
+ }
+ std::optional<aidl::android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+ bool isInput, const aidl::android::media::audio::common::AudioPort& port) const {
+ const auto config = generateAudioMixPortConfigs({port}, isInput, true);
+ if (!config.empty()) {
+ return *config.begin();
+ }
+ return {};
+ }
+
+ std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForDevicePort(
+ const aidl::android::media::audio::common::AudioPort& port) const {
+ return generateAudioDevicePortConfigs({port}, false);
+ }
+ aidl::android::media::audio::common::AudioPortConfig getSingleConfigForDevicePort(
+ const aidl::android::media::audio::common::AudioPort& port) const {
+ const auto config = generateAudioDevicePortConfigs({port}, true);
+ return *config.begin();
+ }
+
+ std::string toString() const;
+
+ private:
+ std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
+ const std::vector<aidl::android::media::audio::common::AudioPort>& ports, bool isInput,
+ bool singleProfile) const;
+
+ // Unlike MixPorts, the generator for DevicePorts always returns a non-empty
+ // vector for a non-empty input port list. If there are no profiles in the
+ // port, its initial configs are looked up, if there are none,
+ // then an empty config is used, assuming further negotiation via setAudioPortConfig.
+ std::vector<aidl::android::media::audio::common::AudioPortConfig>
+ generateAudioDevicePortConfigs(
+ const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
+ bool singleProfile) const;
+
+ ndk::ScopedAStatus mStatus = ndk::ScopedAStatus::ok();
+ std::vector<aidl::android::media::audio::common::AudioPort> mPorts;
+ std::vector<aidl::android::media::audio::common::AudioPortConfig> mInitialConfigs;
+ std::set<int32_t> mAttachedSinkDevicePorts;
+ std::set<int32_t> mAttachedSourceDevicePorts;
+ std::set<int32_t> mExternalDevicePorts;
+ std::vector<aidl::android::hardware::audio::core::AudioRoute> mRoutes;
+};
diff --git a/audio/aidl/vts/TestUtils.h b/audio/aidl/vts/TestUtils.h
new file mode 100644
index 0000000..2fc109a
--- /dev/null
+++ b/audio/aidl/vts/TestUtils.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <iostream>
+
+#include <android/binder_auto_utils.h>
+#include <gtest/gtest_pred_impl.h>
+
+namespace ndk {
+
+std::ostream& operator<<(std::ostream& str, const ScopedAStatus& status) {
+ str << status.getDescription();
+ return str;
+}
+
+} // namespace ndk
+
+namespace android::hardware::audio::common::testing {
+
+namespace detail {
+
+inline ::testing::AssertionResult assertIsOk(const char* expr, const ::ndk::ScopedAStatus& status) {
+ if (status.isOk()) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure()
+ << "Expected the transaction \'" << expr << "\' to succeed\n"
+ << " but it has failed with: " << status;
+}
+
+inline ::testing::AssertionResult assertResult(const char* exp_expr, const char* act_expr,
+ int32_t expected,
+ const ::ndk::ScopedAStatus& status) {
+ if (status.getExceptionCode() == expected) {
+ return ::testing::AssertionSuccess();
+ }
+ return ::testing::AssertionFailure()
+ << "Expected the transaction \'" << act_expr << "\' to fail with " << exp_expr
+ << "\n but is has completed with: " << status;
+}
+
+} // namespace detail
+
+} // namespace android::hardware::audio::common::testing
+
+// Test that the transaction status 'isOk'
+#define ASSERT_IS_OK(ret) \
+ ASSERT_PRED_FORMAT1(::android::hardware::audio::common::testing::detail::assertIsOk, ret)
+#define EXPECT_IS_OK(ret) \
+ EXPECT_PRED_FORMAT1(::android::hardware::audio::common::testing::detail::assertIsOk, ret)
+
+// Test that the transaction status is as expected.
+#define ASSERT_STATUS(expected, ret) \
+ ASSERT_PRED_FORMAT2(::android::hardware::audio::common::testing::detail::assertResult, \
+ expected, ret)
+#define EXPECT_STATUS(expected, ret) \
+ EXPECT_PRED_FORMAT2(::android::hardware::audio::common::testing::detail::assertResult, \
+ expected, ret)
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
new file mode 100644
index 0000000..2381200
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -0,0 +1,1734 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <set>
+#include <string>
+#include <vector>
+
+#define LOG_TAG "VtsHalAudioCore"
+#include <android-base/logging.h>
+
+#include <StreamWorker.h>
+#include <Utils.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/audio/core/IConfig.h>
+#include <aidl/android/hardware/audio/core/IModule.h>
+#include <aidl/android/media/audio/common/AudioIoFlags.h>
+#include <aidl/android/media/audio/common/AudioOutputFlags.h>
+#include <android-base/chrono_utils.h>
+#include <fmq/AidlMessageQueue.h>
+
+#include "AudioHalBinderServiceUtil.h"
+#include "ModuleConfig.h"
+#include "TestUtils.h"
+
+using namespace android;
+using aidl::android::hardware::audio::common::PlaybackTrackMetadata;
+using aidl::android::hardware::audio::common::RecordTrackMetadata;
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::audio::core::AudioPatch;
+using aidl::android::hardware::audio::core::AudioRoute;
+using aidl::android::hardware::audio::core::IModule;
+using aidl::android::hardware::audio::core::IStreamIn;
+using aidl::android::hardware::audio::core::IStreamOut;
+using aidl::android::hardware::audio::core::ModuleDebug;
+using aidl::android::hardware::audio::core::StreamDescriptor;
+using aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using aidl::android::media::audio::common::AudioContentType;
+using aidl::android::media::audio::common::AudioDevice;
+using aidl::android::media::audio::common::AudioDeviceAddress;
+using aidl::android::media::audio::common::AudioDeviceType;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioIoFlags;
+using aidl::android::media::audio::common::AudioOutputFlags;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortDeviceExt;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::AudioSource;
+using aidl::android::media::audio::common::AudioUsage;
+using android::hardware::audio::common::isBitPositionFlagSet;
+using android::hardware::audio::common::StreamLogic;
+using android::hardware::audio::common::StreamWorker;
+using ndk::ScopedAStatus;
+
+template <typename T>
+auto findById(std::vector<T>& v, int32_t id) {
+ return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
+}
+
+template <typename C>
+std::vector<int32_t> GetNonExistentIds(const C& allIds) {
+ if (allIds.empty()) {
+ return std::vector<int32_t>{-1, 0, 1};
+ }
+ std::vector<int32_t> nonExistentIds;
+ nonExistentIds.push_back(*std::min_element(allIds.begin(), allIds.end()) - 1);
+ nonExistentIds.push_back(*std::max_element(allIds.begin(), allIds.end()) + 1);
+ return nonExistentIds;
+}
+
+AudioDeviceAddress GenerateUniqueDeviceAddress() {
+ static int nextId = 1;
+ // TODO: Use connection-specific ID.
+ return AudioDeviceAddress::make<AudioDeviceAddress::Tag::id>(std::to_string(++nextId));
+}
+
+// All 'With*' classes are move-only because they are associated with some
+// resource or state of a HAL module.
+class WithDebugFlags {
+ public:
+ static WithDebugFlags createNested(const WithDebugFlags& parent) {
+ return WithDebugFlags(parent.mFlags);
+ }
+
+ WithDebugFlags() {}
+ explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
+ WithDebugFlags(const WithDebugFlags&) = delete;
+ WithDebugFlags& operator=(const WithDebugFlags&) = delete;
+ ~WithDebugFlags() {
+ if (mModule != nullptr) {
+ EXPECT_IS_OK(mModule->setModuleDebug(mInitial));
+ }
+ }
+ void SetUp(IModule* module) { ASSERT_IS_OK(module->setModuleDebug(mFlags)); }
+ ModuleDebug& flags() { return mFlags; }
+
+ private:
+ ModuleDebug mInitial;
+ ModuleDebug mFlags;
+ IModule* mModule = nullptr;
+};
+
+// For consistency, WithAudioPortConfig can start both with a non-existent
+// port config, and with an existing one. Existence is determined by the
+// id of the provided config. If it's not 0, then WithAudioPortConfig is
+// essentially a no-op wrapper.
+class WithAudioPortConfig {
+ public:
+ WithAudioPortConfig() {}
+ explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {}
+ WithAudioPortConfig(const WithAudioPortConfig&) = delete;
+ WithAudioPortConfig& operator=(const WithAudioPortConfig&) = delete;
+ ~WithAudioPortConfig() {
+ if (mModule != nullptr) {
+ EXPECT_IS_OK(mModule->resetAudioPortConfig(getId())) << "port config id " << getId();
+ }
+ }
+ void SetUp(IModule* module) {
+ ASSERT_NE(AudioPortExt::Tag::unspecified, mInitialConfig.ext.getTag())
+ << "config: " << mInitialConfig.toString();
+ // Negotiation is allowed for device ports because the HAL module is
+ // allowed to provide an empty profiles list for attached devices.
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpImpl(module, mInitialConfig.ext.getTag() == AudioPortExt::Tag::device));
+ }
+ int32_t getId() const { return mConfig.id; }
+ const AudioPortConfig& get() const { return mConfig; }
+
+ private:
+ void SetUpImpl(IModule* module, bool negotiate) {
+ if (mInitialConfig.id == 0) {
+ AudioPortConfig suggested;
+ bool applied = false;
+ ASSERT_IS_OK(module->setAudioPortConfig(mInitialConfig, &suggested, &applied))
+ << "Config: " << mInitialConfig.toString();
+ if (!applied && negotiate) {
+ mInitialConfig = suggested;
+ ASSERT_NO_FATAL_FAILURE(SetUpImpl(module, false))
+ << " while applying suggested config: " << suggested.toString();
+ } else {
+ ASSERT_TRUE(applied) << "Suggested: " << suggested.toString();
+ mConfig = suggested;
+ mModule = module;
+ }
+ } else {
+ mConfig = mInitialConfig;
+ }
+ }
+
+ AudioPortConfig mInitialConfig;
+ IModule* mModule = nullptr;
+ AudioPortConfig mConfig;
+};
+
+class AudioCoreModule : public testing::TestWithParam<std::string> {
+ public:
+ // The default buffer size is used mostly for negative tests.
+ static constexpr int kDefaultBufferSizeFrames = 256;
+
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(ConnectToService());
+ debug.flags().simulateDeviceConnections = true;
+ ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
+ }
+
+ void TearDown() override {
+ if (module != nullptr) {
+ EXPECT_IS_OK(module->setModuleDebug(ModuleDebug{}));
+ }
+ }
+
+ void ConnectToService() {
+ module = IModule::fromBinder(binderUtil.connectToService(GetParam()));
+ ASSERT_NE(module, nullptr);
+ }
+
+ void RestartService() {
+ ASSERT_NE(module, nullptr);
+ moduleConfig.reset();
+ module = IModule::fromBinder(binderUtil.restartService());
+ ASSERT_NE(module, nullptr);
+ }
+
+ void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
+ for (const auto& config : configs) {
+ ASSERT_NE(0, config.portId);
+ WithAudioPortConfig portConfig(config);
+ ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get())); // calls setAudioPortConfig
+ EXPECT_EQ(config.portId, portConfig.get().portId);
+ std::vector<AudioPortConfig> retrievedPortConfigs;
+ ASSERT_IS_OK(module->getAudioPortConfigs(&retrievedPortConfigs));
+ const int32_t portConfigId = portConfig.getId();
+ auto configIt = std::find_if(
+ retrievedPortConfigs.begin(), retrievedPortConfigs.end(),
+ [&portConfigId](const auto& retrConf) { return retrConf.id == portConfigId; });
+ EXPECT_NE(configIt, retrievedPortConfigs.end())
+ << "Port config id returned by setAudioPortConfig: " << portConfigId
+ << " is not found in the list returned by getAudioPortConfigs";
+ if (configIt != retrievedPortConfigs.end()) {
+ EXPECT_EQ(portConfig.get(), *configIt)
+ << "Applied port config returned by setAudioPortConfig: "
+ << portConfig.get().toString()
+ << " is not the same as retrieved via getAudioPortConfigs: "
+ << configIt->toString();
+ }
+ }
+ }
+
+ template <typename Entity>
+ void GetAllEntityIds(std::set<int32_t>* entityIds,
+ ScopedAStatus (IModule::*getter)(std::vector<Entity>*),
+ const std::string& errorMessage) {
+ std::vector<Entity> entities;
+ { ASSERT_IS_OK((module.get()->*getter)(&entities)); }
+ std::transform(entities.begin(), entities.end(),
+ std::inserter(*entityIds, entityIds->begin()),
+ [](const auto& entity) { return entity.id; });
+ EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
+ }
+
+ void GetAllPatchIds(std::set<int32_t>* patchIds) {
+ return GetAllEntityIds<AudioPatch>(
+ patchIds, &IModule::getAudioPatches,
+ "IDs of audio patches returned by IModule.getAudioPatches are not unique");
+ }
+
+ void GetAllPortIds(std::set<int32_t>* portIds) {
+ return GetAllEntityIds<AudioPort>(
+ portIds, &IModule::getAudioPorts,
+ "IDs of audio ports returned by IModule.getAudioPorts are not unique");
+ }
+
+ void GetAllPortConfigIds(std::set<int32_t>* portConfigIds) {
+ return GetAllEntityIds<AudioPortConfig>(
+ portConfigIds, &IModule::getAudioPortConfigs,
+ "IDs of audio port configs returned by IModule.getAudioPortConfigs are not unique");
+ }
+
+ void SetUpModuleConfig() {
+ if (moduleConfig == nullptr) {
+ moduleConfig = std::make_unique<ModuleConfig>(module.get());
+ ASSERT_EQ(EX_NONE, moduleConfig->getStatus().getExceptionCode())
+ << "ModuleConfig init error: " << moduleConfig->getError();
+ }
+ }
+
+ std::shared_ptr<IModule> module;
+ std::unique_ptr<ModuleConfig> moduleConfig;
+ AudioHalBinderServiceUtil binderUtil;
+ WithDebugFlags debug;
+};
+
+class WithDevicePortConnectedState {
+ public:
+ explicit WithDevicePortConnectedState(const AudioPort& idAndData) : mIdAndData(idAndData) {}
+ WithDevicePortConnectedState(const AudioPort& id, const AudioDeviceAddress& address)
+ : mIdAndData(setAudioPortAddress(id, address)) {}
+ WithDevicePortConnectedState(const WithDevicePortConnectedState&) = delete;
+ WithDevicePortConnectedState& operator=(const WithDevicePortConnectedState&) = delete;
+ ~WithDevicePortConnectedState() {
+ if (mModule != nullptr) {
+ EXPECT_IS_OK(mModule->disconnectExternalDevice(getId()))
+ << "when disconnecting device port ID " << getId();
+ }
+ }
+ void SetUp(IModule* module) {
+ ASSERT_IS_OK(module->connectExternalDevice(mIdAndData, &mConnectedPort))
+ << "when connecting device port ID & data " << mIdAndData.toString();
+ ASSERT_NE(mIdAndData.id, getId())
+ << "ID of the connected port must not be the same as the ID of the template port";
+ mModule = module;
+ }
+ int32_t getId() const { return mConnectedPort.id; }
+ const AudioPort& get() { return mConnectedPort; }
+
+ private:
+ static AudioPort setAudioPortAddress(const AudioPort& id, const AudioDeviceAddress& address) {
+ AudioPort result = id;
+ result.ext.get<AudioPortExt::Tag::device>().device.address = address;
+ return result;
+ }
+
+ const AudioPort mIdAndData;
+ IModule* mModule = nullptr;
+ AudioPort mConnectedPort;
+};
+
+class StreamContext {
+ public:
+ typedef AidlMessageQueue<StreamDescriptor::Command, SynchronizedReadWrite> CommandMQ;
+ typedef AidlMessageQueue<StreamDescriptor::Reply, SynchronizedReadWrite> ReplyMQ;
+ typedef AidlMessageQueue<int8_t, SynchronizedReadWrite> DataMQ;
+
+ explicit StreamContext(const StreamDescriptor& descriptor)
+ : mFrameSizeBytes(descriptor.frameSizeBytes),
+ mCommandMQ(new CommandMQ(descriptor.command)),
+ mReplyMQ(new ReplyMQ(descriptor.reply)),
+ mBufferSizeFrames(descriptor.bufferSizeFrames),
+ mDataMQ(maybeCreateDataMQ(descriptor)) {}
+ void checkIsValid() const {
+ EXPECT_NE(0UL, mFrameSizeBytes);
+ ASSERT_NE(nullptr, mCommandMQ);
+ EXPECT_TRUE(mCommandMQ->isValid());
+ ASSERT_NE(nullptr, mReplyMQ);
+ EXPECT_TRUE(mReplyMQ->isValid());
+ if (mDataMQ != nullptr) {
+ EXPECT_TRUE(mDataMQ->isValid());
+ EXPECT_GE(mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize(),
+ mFrameSizeBytes * mBufferSizeFrames)
+ << "Data MQ actual buffer size is "
+ "less than the buffer size as specified by the descriptor";
+ }
+ }
+ size_t getBufferSizeBytes() const { return mFrameSizeBytes * mBufferSizeFrames; }
+ size_t getBufferSizeFrames() const { return mBufferSizeFrames; }
+ CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
+ DataMQ* getDataMQ() const { return mDataMQ.get(); }
+ ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
+
+ private:
+ static std::unique_ptr<DataMQ> maybeCreateDataMQ(const StreamDescriptor& descriptor) {
+ using Tag = StreamDescriptor::AudioBuffer::Tag;
+ if (descriptor.audio.getTag() == Tag::fmq) {
+ return std::make_unique<DataMQ>(descriptor.audio.get<Tag::fmq>());
+ }
+ return nullptr;
+ }
+
+ const size_t mFrameSizeBytes;
+ std::unique_ptr<CommandMQ> mCommandMQ;
+ std::unique_ptr<ReplyMQ> mReplyMQ;
+ const size_t mBufferSizeFrames;
+ std::unique_ptr<DataMQ> mDataMQ;
+};
+
+class StreamCommonLogic : public StreamLogic {
+ public:
+ StreamDescriptor::Position getLastObservablePosition() {
+ std::lock_guard<std::mutex> lock(mLock);
+ return mLastReply.observable;
+ }
+
+ protected:
+ explicit StreamCommonLogic(const StreamContext& context)
+ : mCommandMQ(context.getCommandMQ()),
+ mReplyMQ(context.getReplyMQ()),
+ mDataMQ(context.getDataMQ()),
+ mData(context.getBufferSizeBytes()) {}
+ StreamContext::CommandMQ* getCommandMQ() const { return mCommandMQ; }
+ StreamContext::ReplyMQ* getReplyMQ() const { return mReplyMQ; }
+
+ std::string init() override { return ""; }
+
+ StreamContext::CommandMQ* mCommandMQ;
+ StreamContext::ReplyMQ* mReplyMQ;
+ StreamContext::DataMQ* mDataMQ;
+ std::vector<int8_t> mData;
+ std::mutex mLock;
+ StreamDescriptor::Reply mLastReply GUARDED_BY(mLock);
+};
+
+class StreamReaderLogic : public StreamCommonLogic {
+ public:
+ explicit StreamReaderLogic(const StreamContext& context) : StreamCommonLogic(context) {}
+
+ protected:
+ Status cycle() override {
+ StreamDescriptor::Command command{};
+ command.code = StreamDescriptor::COMMAND_BURST;
+ command.fmqByteCount = mData.size();
+ if (!mCommandMQ->writeBlocking(&command, 1)) {
+ LOG(ERROR) << __func__ << ": writing of command into MQ failed";
+ return Status::ABORT;
+ }
+ StreamDescriptor::Reply reply{};
+ if (!mReplyMQ->readBlocking(&reply, 1)) {
+ LOG(ERROR) << __func__ << ": reading of reply from MQ failed";
+ return Status::ABORT;
+ }
+ if (reply.status != STATUS_OK) {
+ LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status);
+ return Status::ABORT;
+ }
+ if (reply.fmqByteCount < 0 || reply.fmqByteCount > command.fmqByteCount) {
+ LOG(ERROR) << __func__
+ << ": received invalid byte count in the reply: " << reply.fmqByteCount;
+ return Status::ABORT;
+ }
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ mLastReply = reply;
+ }
+ const size_t readCount = std::min({mDataMQ->availableToRead(),
+ static_cast<size_t>(reply.fmqByteCount), mData.size()});
+ if (readCount == 0 || mDataMQ->read(mData.data(), readCount)) {
+ return Status::CONTINUE;
+ }
+ LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes from MQ failed";
+ return Status::ABORT;
+ }
+};
+using StreamReader = StreamWorker<StreamReaderLogic>;
+
+class StreamWriterLogic : public StreamCommonLogic {
+ public:
+ explicit StreamWriterLogic(const StreamContext& context) : StreamCommonLogic(context) {}
+
+ protected:
+ Status cycle() override {
+ if (!mDataMQ->write(mData.data(), mData.size())) {
+ LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to MQ failed";
+ return Status::ABORT;
+ }
+ StreamDescriptor::Command command{};
+ command.code = StreamDescriptor::COMMAND_BURST;
+ command.fmqByteCount = mData.size();
+ if (!mCommandMQ->writeBlocking(&command, 1)) {
+ LOG(ERROR) << __func__ << ": writing of command into MQ failed";
+ return Status::ABORT;
+ }
+ StreamDescriptor::Reply reply{};
+ if (!mReplyMQ->readBlocking(&reply, 1)) {
+ LOG(ERROR) << __func__ << ": reading of reply from MQ failed";
+ return Status::ABORT;
+ }
+ if (reply.status != STATUS_OK) {
+ LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status);
+ return Status::ABORT;
+ }
+ if (reply.fmqByteCount < 0 || reply.fmqByteCount > command.fmqByteCount) {
+ LOG(ERROR) << __func__
+ << ": received invalid byte count in the reply: " << reply.fmqByteCount;
+ return Status::ABORT;
+ }
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ mLastReply = reply;
+ }
+ return Status::CONTINUE;
+ }
+};
+using StreamWriter = StreamWorker<StreamWriterLogic>;
+
+template <typename T>
+struct IOTraits {
+ static constexpr bool is_input = std::is_same_v<T, IStreamIn>;
+ using Worker = std::conditional_t<is_input, StreamReader, StreamWriter>;
+};
+
+// A dedicated version to test replies to invalid commands.
+class StreamInvalidCommandLogic : public StreamCommonLogic {
+ public:
+ StreamInvalidCommandLogic(const StreamContext& context,
+ const std::vector<StreamDescriptor::Command>& commands)
+ : StreamCommonLogic(context), mCommands(commands) {}
+
+ std::vector<std::string> getUnexpectedStatuses() {
+ std::lock_guard<std::mutex> lock(mLock);
+ return mUnexpectedStatuses;
+ }
+
+ protected:
+ Status cycle() override {
+ // Send all commands in one cycle to simplify testing.
+ // Extra logging helps to sort out issues with unexpected HAL behavior.
+ for (const auto& command : mCommands) {
+ LOG(INFO) << __func__ << ": writing command " << command.toString() << " into MQ...";
+ if (!getCommandMQ()->writeBlocking(&command, 1)) {
+ LOG(ERROR) << __func__ << ": writing of command into MQ failed";
+ return Status::ABORT;
+ }
+ StreamDescriptor::Reply reply{};
+ LOG(INFO) << __func__ << ": reading reply for command " << command.toString() << "...";
+ if (!getReplyMQ()->readBlocking(&reply, 1)) {
+ LOG(ERROR) << __func__ << ": reading of reply from MQ failed";
+ return Status::ABORT;
+ }
+ LOG(INFO) << __func__ << ": received status " << statusToString(reply.status)
+ << " for command " << command.toString();
+ if (reply.status != STATUS_BAD_VALUE) {
+ std::string s = command.toString();
+ s.append(", ").append(statusToString(reply.status));
+ std::lock_guard<std::mutex> lock(mLock);
+ mUnexpectedStatuses.push_back(std::move(s));
+ }
+ };
+ return Status::EXIT;
+ }
+
+ private:
+ const std::vector<StreamDescriptor::Command> mCommands;
+ std::mutex mLock;
+ std::vector<std::string> mUnexpectedStatuses GUARDED_BY(mLock);
+};
+
+template <typename Stream>
+class WithStream {
+ public:
+ WithStream() {}
+ explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
+ WithStream(const WithStream&) = delete;
+ WithStream& operator=(const WithStream&) = delete;
+ ~WithStream() {
+ if (mStream != nullptr) {
+ mContext.reset();
+ EXPECT_IS_OK(mStream->close()) << "port config id " << getPortId();
+ }
+ }
+ void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); }
+ ScopedAStatus SetUpNoChecks(IModule* module, long bufferSizeFrames) {
+ return SetUpNoChecks(module, mPortConfig.get(), bufferSizeFrames);
+ }
+ ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig,
+ long bufferSizeFrames);
+ void SetUp(IModule* module, long bufferSizeFrames) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+ ASSERT_IS_OK(SetUpNoChecks(module, bufferSizeFrames)) << "port config id " << getPortId();
+ ASSERT_NE(nullptr, mStream) << "port config id " << getPortId();
+ EXPECT_GE(mDescriptor.bufferSizeFrames, bufferSizeFrames)
+ << "actual buffer size must be no less than requested";
+ mContext.emplace(mDescriptor);
+ ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
+ }
+ Stream* get() const { return mStream.get(); }
+ const StreamContext* getContext() const { return mContext ? &(mContext.value()) : nullptr; }
+ std::shared_ptr<Stream> getSharedPointer() const { return mStream; }
+ const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
+ int32_t getPortId() const { return mPortConfig.getId(); }
+
+ private:
+ WithAudioPortConfig mPortConfig;
+ std::shared_ptr<Stream> mStream;
+ StreamDescriptor mDescriptor;
+ std::optional<StreamContext> mContext;
+};
+
+SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) {
+ RecordTrackMetadata trackMeta;
+ trackMeta.source = AudioSource::MIC;
+ trackMeta.gain = 1.0;
+ trackMeta.channelMask = portConfig.channelMask.value();
+ SinkMetadata metadata;
+ metadata.tracks.push_back(trackMeta);
+ return metadata;
+}
+
+template <>
+ScopedAStatus WithStream<IStreamIn>::SetUpNoChecks(IModule* module,
+ const AudioPortConfig& portConfig,
+ long bufferSizeFrames) {
+ aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
+ args.portConfigId = portConfig.id;
+ args.sinkMetadata = GenerateSinkMetadata(portConfig);
+ args.bufferSizeFrames = bufferSizeFrames;
+ aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
+ ScopedAStatus status = module->openInputStream(args, &ret);
+ if (status.isOk()) {
+ mStream = std::move(ret.stream);
+ mDescriptor = std::move(ret.desc);
+ }
+ return status;
+}
+
+SourceMetadata GenerateSourceMetadata(const AudioPortConfig& portConfig) {
+ PlaybackTrackMetadata trackMeta;
+ trackMeta.usage = AudioUsage::MEDIA;
+ trackMeta.contentType = AudioContentType::MUSIC;
+ trackMeta.gain = 1.0;
+ trackMeta.channelMask = portConfig.channelMask.value();
+ SourceMetadata metadata;
+ metadata.tracks.push_back(trackMeta);
+ return metadata;
+}
+
+template <>
+ScopedAStatus WithStream<IStreamOut>::SetUpNoChecks(IModule* module,
+ const AudioPortConfig& portConfig,
+ long bufferSizeFrames) {
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
+ args.portConfigId = portConfig.id;
+ args.sourceMetadata = GenerateSourceMetadata(portConfig);
+ args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
+ args.bufferSizeFrames = bufferSizeFrames;
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
+ ScopedAStatus status = module->openOutputStream(args, &ret);
+ if (status.isOk()) {
+ mStream = std::move(ret.stream);
+ mDescriptor = std::move(ret.desc);
+ }
+ return status;
+}
+
+class WithAudioPatch {
+ public:
+ WithAudioPatch() {}
+ WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
+ : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
+ WithAudioPatch(bool sinkIsCfg1, const AudioPortConfig& portConfig1,
+ const AudioPortConfig& portConfig2)
+ : mSrcPortConfig(sinkIsCfg1 ? portConfig2 : portConfig1),
+ mSinkPortConfig(sinkIsCfg1 ? portConfig1 : portConfig2) {}
+ WithAudioPatch(const WithAudioPatch&) = delete;
+ WithAudioPatch& operator=(const WithAudioPatch&) = delete;
+ ~WithAudioPatch() {
+ if (mModule != nullptr && mPatch.id != 0) {
+ EXPECT_IS_OK(mModule->resetAudioPatch(mPatch.id)) << "patch id " << getId();
+ }
+ }
+ void SetUpPortConfigs(IModule* module) {
+ ASSERT_NO_FATAL_FAILURE(mSrcPortConfig.SetUp(module));
+ ASSERT_NO_FATAL_FAILURE(mSinkPortConfig.SetUp(module));
+ }
+ ScopedAStatus SetUpNoChecks(IModule* module) {
+ mModule = module;
+ mPatch.sourcePortConfigIds = std::vector<int32_t>{mSrcPortConfig.getId()};
+ mPatch.sinkPortConfigIds = std::vector<int32_t>{mSinkPortConfig.getId()};
+ return mModule->setAudioPatch(mPatch, &mPatch);
+ }
+ void SetUp(IModule* module) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfigs(module));
+ ASSERT_IS_OK(SetUpNoChecks(module)) << "source port config id " << mSrcPortConfig.getId()
+ << "; sink port config id " << mSinkPortConfig.getId();
+ EXPECT_GT(mPatch.minimumStreamBufferSizeFrames, 0) << "patch id " << getId();
+ for (auto latencyMs : mPatch.latenciesMs) {
+ EXPECT_GT(latencyMs, 0) << "patch id " << getId();
+ }
+ }
+ int32_t getId() const { return mPatch.id; }
+ const AudioPatch& get() const { return mPatch; }
+ const AudioPortConfig& getSinkPortConfig() const { return mSinkPortConfig.get(); }
+ const AudioPortConfig& getSrcPortConfig() const { return mSrcPortConfig.get(); }
+ const AudioPortConfig& getPortConfig(bool getSink) const {
+ return getSink ? getSinkPortConfig() : getSrcPortConfig();
+ }
+
+ private:
+ WithAudioPortConfig mSrcPortConfig;
+ WithAudioPortConfig mSinkPortConfig;
+ IModule* mModule = nullptr;
+ AudioPatch mPatch;
+};
+
+TEST_P(AudioCoreModule, Published) {
+ // SetUp must complete with no failures.
+}
+
+TEST_P(AudioCoreModule, CanBeRestarted) {
+ ASSERT_NO_FATAL_FAILURE(RestartService());
+}
+
+TEST_P(AudioCoreModule, PortIdsAreUnique) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+}
+
+TEST_P(AudioCoreModule, GetAudioPortsIsStable) {
+ std::vector<AudioPort> ports1;
+ ASSERT_IS_OK(module->getAudioPorts(&ports1));
+ std::vector<AudioPort> ports2;
+ ASSERT_IS_OK(module->getAudioPorts(&ports2));
+ ASSERT_EQ(ports1.size(), ports2.size())
+ << "Sizes of audio port arrays do not match across consequent calls to getAudioPorts";
+ std::sort(ports1.begin(), ports1.end());
+ std::sort(ports2.begin(), ports2.end());
+ EXPECT_EQ(ports1, ports2);
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesIsStable) {
+ std::vector<AudioRoute> routes1;
+ ASSERT_IS_OK(module->getAudioRoutes(&routes1));
+ std::vector<AudioRoute> routes2;
+ ASSERT_IS_OK(module->getAudioRoutes(&routes2));
+ ASSERT_EQ(routes1.size(), routes2.size())
+ << "Sizes of audio route arrays do not match across consequent calls to getAudioRoutes";
+ std::sort(routes1.begin(), routes1.end());
+ std::sort(routes2.begin(), routes2.end());
+ EXPECT_EQ(routes1, routes2);
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesAreValid) {
+ std::vector<AudioRoute> routes;
+ ASSERT_IS_OK(module->getAudioRoutes(&routes));
+ for (const auto& route : routes) {
+ std::set<int32_t> sources(route.sourcePortIds.begin(), route.sourcePortIds.end());
+ EXPECT_NE(0UL, sources.size())
+ << "empty audio port sinks in the audio route: " << route.toString();
+ EXPECT_EQ(sources.size(), route.sourcePortIds.size())
+ << "IDs of audio port sinks are not unique in the audio route: "
+ << route.toString();
+ }
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesPortIdsAreValid) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ std::vector<AudioRoute> routes;
+ ASSERT_IS_OK(module->getAudioRoutes(&routes));
+ for (const auto& route : routes) {
+ EXPECT_EQ(1UL, portIds.count(route.sinkPortId))
+ << route.sinkPortId << " sink port id is unknown";
+ for (const auto& source : route.sourcePortIds) {
+ EXPECT_EQ(1UL, portIds.count(source)) << source << " source port id is unknown";
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesForAudioPort) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ if (portIds.empty()) {
+ GTEST_SKIP() << "No ports in the module.";
+ }
+ for (const auto portId : portIds) {
+ std::vector<AudioRoute> routes;
+ EXPECT_IS_OK(module->getAudioRoutesForAudioPort(portId, &routes));
+ for (const auto& r : routes) {
+ if (r.sinkPortId != portId) {
+ const auto& srcs = r.sourcePortIds;
+ EXPECT_TRUE(std::find(srcs.begin(), srcs.end(), portId) != srcs.end())
+ << " port ID " << portId << " does not used by the route " << r.toString();
+ }
+ }
+ }
+ for (const auto portId : GetNonExistentIds(portIds)) {
+ std::vector<AudioRoute> routes;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->getAudioRoutesForAudioPort(portId, &routes))
+ << "port ID " << portId;
+ }
+}
+
+TEST_P(AudioCoreModule, CheckDevicePorts) {
+ std::vector<AudioPort> ports;
+ ASSERT_IS_OK(module->getAudioPorts(&ports));
+ std::optional<int32_t> defaultOutput, defaultInput;
+ std::set<AudioDevice> inputs, outputs;
+ const int defaultDeviceFlag = 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE;
+ for (const auto& port : ports) {
+ if (port.ext.getTag() != AudioPortExt::Tag::device) continue;
+ const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+ EXPECT_NE(AudioDeviceType::NONE, devicePort.device.type.type);
+ EXPECT_NE(AudioDeviceType::IN_DEFAULT, devicePort.device.type.type);
+ EXPECT_NE(AudioDeviceType::OUT_DEFAULT, devicePort.device.type.type);
+ if (devicePort.device.type.type > AudioDeviceType::IN_DEFAULT &&
+ devicePort.device.type.type < AudioDeviceType::OUT_DEFAULT) {
+ EXPECT_EQ(AudioIoFlags::Tag::input, port.flags.getTag());
+ } else if (devicePort.device.type.type > AudioDeviceType::OUT_DEFAULT) {
+ EXPECT_EQ(AudioIoFlags::Tag::output, port.flags.getTag());
+ }
+ EXPECT_FALSE((devicePort.flags & defaultDeviceFlag) != 0 &&
+ !devicePort.device.type.connection.empty())
+ << "Device port " << port.id
+ << " must be permanently attached to be set as default";
+ if ((devicePort.flags & defaultDeviceFlag) != 0) {
+ if (port.flags.getTag() == AudioIoFlags::Tag::output) {
+ EXPECT_FALSE(defaultOutput.has_value())
+ << "At least two output device ports are declared as default: "
+ << defaultOutput.value() << " and " << port.id;
+ defaultOutput = port.id;
+ EXPECT_EQ(0UL, outputs.count(devicePort.device))
+ << "Non-unique output device: " << devicePort.device.toString();
+ outputs.insert(devicePort.device);
+ } else if (port.flags.getTag() == AudioIoFlags::Tag::input) {
+ EXPECT_FALSE(defaultInput.has_value())
+ << "At least two input device ports are declared as default: "
+ << defaultInput.value() << " and " << port.id;
+ defaultInput = port.id;
+ EXPECT_EQ(0UL, inputs.count(devicePort.device))
+ << "Non-unique input device: " << devicePort.device.toString();
+ inputs.insert(devicePort.device);
+ } else {
+ FAIL() << "Invalid AudioIoFlags Tag: " << toString(port.flags.getTag());
+ }
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, CheckMixPorts) {
+ std::vector<AudioPort> ports;
+ ASSERT_IS_OK(module->getAudioPorts(&ports));
+ std::optional<int32_t> primaryMixPort;
+ for (const auto& port : ports) {
+ if (port.ext.getTag() != AudioPortExt::Tag::mix) continue;
+ const auto& mixPort = port.ext.get<AudioPortExt::Tag::mix>();
+ if (port.flags.getTag() == AudioIoFlags::Tag::output &&
+ isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::PRIMARY)) {
+ EXPECT_FALSE(primaryMixPort.has_value())
+ << "At least two mix ports have PRIMARY flag set: " << primaryMixPort.value()
+ << " and " << port.id;
+ primaryMixPort = port.id;
+ EXPECT_EQ(1, mixPort.maxOpenStreamCount)
+ << "Primary mix port " << port.id << " can not have maxOpenStreamCount "
+ << mixPort.maxOpenStreamCount;
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, GetAudioPort) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ if (portIds.empty()) {
+ GTEST_SKIP() << "No ports in the module.";
+ }
+ for (const auto portId : portIds) {
+ AudioPort port;
+ EXPECT_IS_OK(module->getAudioPort(portId, &port));
+ EXPECT_EQ(portId, port.id);
+ }
+ for (const auto portId : GetNonExistentIds(portIds)) {
+ AudioPort port;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->getAudioPort(portId, &port))
+ << "port ID " << portId;
+ }
+}
+
+TEST_P(AudioCoreModule, SetUpModuleConfig) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ // Send the module config to logcat to facilitate failures investigation.
+ LOG(INFO) << "SetUpModuleConfig: " << moduleConfig->toString();
+}
+
+// Verify that HAL module reports for a connected device port at least one non-dynamic profile,
+// that is, a profile with actual supported configuration.
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ AudioPort portWithData = port;
+ portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+ GenerateUniqueDeviceAddress();
+ WithDevicePortConnectedState portConnected(portWithData);
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ const int32_t connectedPortId = portConnected.getId();
+ ASSERT_NE(portWithData.id, connectedPortId);
+ ASSERT_EQ(portWithData.ext.getTag(), portConnected.get().ext.getTag());
+ EXPECT_EQ(portWithData.ext.get<AudioPortExt::Tag::device>().device,
+ portConnected.get().ext.get<AudioPortExt::Tag::device>().device);
+ // Verify that 'getAudioPort' and 'getAudioPorts' return the same connected port.
+ AudioPort connectedPort;
+ EXPECT_IS_OK(module->getAudioPort(connectedPortId, &connectedPort))
+ << "port ID " << connectedPortId;
+ EXPECT_EQ(portConnected.get(), connectedPort);
+ const auto& portProfiles = connectedPort.profiles;
+ EXPECT_NE(0UL, portProfiles.size())
+ << "Connected port has no profiles: " << connectedPort.toString();
+ const auto dynamicProfileIt =
+ std::find_if(portProfiles.begin(), portProfiles.end(), [](const auto& profile) {
+ return profile.format.type == AudioFormatType::DEFAULT;
+ });
+ EXPECT_EQ(portProfiles.end(), dynamicProfileIt) << "Connected port contains dynamic "
+ << "profiles: " << connectedPort.toString();
+
+ std::vector<AudioPort> allPorts;
+ ASSERT_IS_OK(module->getAudioPorts(&allPorts));
+ const auto allPortsIt = findById(allPorts, connectedPortId);
+ EXPECT_NE(allPorts.end(), allPortsIt);
+ if (allPortsIt != allPorts.end()) {
+ EXPECT_EQ(portConnected.get(), *allPortsIt);
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
+ std::set<int32_t> portConfigIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+ for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
+ {
+ aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
+ args.portConfigId = portConfigId;
+ args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openInputStream(args, &ret))
+ << "port config ID " << portConfigId;
+ EXPECT_EQ(nullptr, ret.stream);
+ }
+ {
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
+ args.portConfigId = portConfigId;
+ args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
+ << "port config ID " << portConfigId;
+ EXPECT_EQ(nullptr, ret.stream);
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, PortConfigIdsAreUnique) {
+ std::set<int32_t> portConfigIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+}
+
+TEST_P(AudioCoreModule, PortConfigPortIdsAreValid) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ std::vector<AudioPortConfig> portConfigs;
+ ASSERT_IS_OK(module->getAudioPortConfigs(&portConfigs));
+ for (const auto& config : portConfigs) {
+ EXPECT_EQ(1UL, portIds.count(config.portId))
+ << config.portId << " port id is unknown, config id " << config.id;
+ }
+}
+
+TEST_P(AudioCoreModule, ResetAudioPortConfigInvalidId) {
+ std::set<int32_t> portConfigIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+ for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->resetAudioPortConfig(portConfigId))
+ << "port config ID " << portConfigId;
+ }
+}
+
+// Verify that for the audio port configs provided by the HAL after init, resetting
+// the config does not delete it, but brings it back to the initial config.
+TEST_P(AudioCoreModule, ResetAudioPortConfigToInitialValue) {
+ std::vector<AudioPortConfig> portConfigsBefore;
+ ASSERT_IS_OK(module->getAudioPortConfigs(&portConfigsBefore));
+ // TODO: Change port configs according to port profiles.
+ for (const auto& c : portConfigsBefore) {
+ EXPECT_IS_OK(module->resetAudioPortConfig(c.id)) << "port config ID " << c.id;
+ }
+ std::vector<AudioPortConfig> portConfigsAfter;
+ ASSERT_IS_OK(module->getAudioPortConfigs(&portConfigsAfter));
+ for (const auto& c : portConfigsBefore) {
+ auto afterIt = findById<AudioPortConfig>(portConfigsAfter, c.id);
+ EXPECT_NE(portConfigsAfter.end(), afterIt)
+ << " port config ID " << c.id << " was removed by reset";
+ if (afterIt != portConfigsAfter.end()) {
+ EXPECT_EQ(c, *afterIt);
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, SetAudioPortConfigSuggestedConfig) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ auto srcMixPort = moduleConfig->getSourceMixPortForAttachedDevice();
+ if (!srcMixPort.has_value()) {
+ GTEST_SKIP() << "No mix port for attached output devices";
+ }
+ AudioPortConfig portConfig;
+ AudioPortConfig suggestedConfig;
+ portConfig.portId = srcMixPort.value().id;
+ {
+ bool applied = true;
+ ASSERT_IS_OK(module->setAudioPortConfig(portConfig, &suggestedConfig, &applied))
+ << "Config: " << portConfig.toString();
+ EXPECT_FALSE(applied);
+ }
+ EXPECT_EQ(0, suggestedConfig.id);
+ EXPECT_TRUE(suggestedConfig.sampleRate.has_value());
+ EXPECT_TRUE(suggestedConfig.channelMask.has_value());
+ EXPECT_TRUE(suggestedConfig.format.has_value());
+ EXPECT_TRUE(suggestedConfig.flags.has_value());
+ WithAudioPortConfig applied(suggestedConfig);
+ ASSERT_NO_FATAL_FAILURE(applied.SetUp(module.get()));
+ const AudioPortConfig& appliedConfig = applied.get();
+ EXPECT_NE(0, appliedConfig.id);
+ EXPECT_TRUE(appliedConfig.sampleRate.has_value());
+ EXPECT_EQ(suggestedConfig.sampleRate.value(), appliedConfig.sampleRate.value());
+ EXPECT_TRUE(appliedConfig.channelMask.has_value());
+ EXPECT_EQ(suggestedConfig.channelMask.value(), appliedConfig.channelMask.value());
+ EXPECT_TRUE(appliedConfig.format.has_value());
+ EXPECT_EQ(suggestedConfig.format.value(), appliedConfig.format.value());
+ EXPECT_TRUE(appliedConfig.flags.has_value());
+ EXPECT_EQ(suggestedConfig.flags.value(), appliedConfig.flags.value());
+}
+
+TEST_P(AudioCoreModule, SetAllAttachedDevicePortConfigs) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForAttachedDevicePorts()));
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, SetAllExternalDevicePortConfigs) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ ASSERT_NO_FATAL_FAILURE(
+ ApplyEveryConfig(moduleConfig->getPortConfigsForDevicePort(portConnected.get())));
+ }
+}
+
+TEST_P(AudioCoreModule, SetAllStaticAudioPortConfigs) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForMixPorts()));
+}
+
+TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ for (const auto portId : GetNonExistentIds(portIds)) {
+ AudioPortConfig portConfig, suggestedConfig;
+ bool applied;
+ portConfig.portId = portId;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
+ module->setAudioPortConfig(portConfig, &suggestedConfig, &applied))
+ << "port ID " << portId;
+ EXPECT_FALSE(suggestedConfig.format.has_value());
+ EXPECT_FALSE(suggestedConfig.channelMask.has_value());
+ EXPECT_FALSE(suggestedConfig.sampleRate.has_value());
+ }
+}
+
+TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) {
+ std::set<int32_t> portConfigIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+ for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
+ AudioPortConfig portConfig, suggestedConfig;
+ bool applied;
+ portConfig.id = portConfigId;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
+ module->setAudioPortConfig(portConfig, &suggestedConfig, &applied))
+ << "port config ID " << portConfigId;
+ EXPECT_FALSE(suggestedConfig.format.has_value());
+ EXPECT_FALSE(suggestedConfig.channelMask.has_value());
+ EXPECT_FALSE(suggestedConfig.sampleRate.has_value());
+ }
+}
+
+TEST_P(AudioCoreModule, TryConnectMissingDevice) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ AudioPort ignored;
+ WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(debug);
+ doNotSimulateConnections.flags().simulateDeviceConnections = false;
+ ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
+ for (const auto& port : ports) {
+ AudioPort portWithData = port;
+ portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+ GenerateUniqueDeviceAddress();
+ EXPECT_STATUS(EX_ILLEGAL_STATE, module->connectExternalDevice(portWithData, &ignored))
+ << "static port " << portWithData.toString();
+ }
+}
+
+TEST_P(AudioCoreModule, TryChangingConnectionSimulationMidway) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ ModuleDebug midwayDebugChange = debug.flags();
+ midwayDebugChange.simulateDeviceConnections = false;
+ EXPECT_STATUS(EX_ILLEGAL_STATE, module->setModuleDebug(midwayDebugChange))
+ << "when trying to disable connections simulation while having a connected device";
+}
+
+TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceInvalidPorts) {
+ AudioPort ignored;
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ for (const auto portId : GetNonExistentIds(portIds)) {
+ AudioPort invalidPort;
+ invalidPort.id = portId;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(invalidPort, &ignored))
+ << "port ID " << portId << ", when setting CONNECTED state";
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(portId))
+ << "port ID " << portId << ", when setting DISCONNECTED state";
+ }
+
+ std::vector<AudioPort> ports;
+ ASSERT_IS_OK(module->getAudioPorts(&ports));
+ for (const auto& port : ports) {
+ if (port.ext.getTag() != AudioPortExt::Tag::device) {
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(port, &ignored))
+ << "non-device port ID " << port.id << " when setting CONNECTED state";
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(port.id))
+ << "non-device port ID " << port.id << " when setting DISCONNECTED state";
+ } else {
+ const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+ if (devicePort.device.type.connection.empty()) {
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(port, &ignored))
+ << "for a permanently attached device port ID " << port.id
+ << " when setting CONNECTED state";
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(port.id))
+ << "for a permanently attached device port ID " << port.id
+ << " when setting DISCONNECTED state";
+ }
+ }
+ }
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceTwice) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ AudioPort ignored;
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(port.id))
+ << "when disconnecting already disconnected device port ID " << port.id;
+ AudioPort portWithData = port;
+ portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+ GenerateUniqueDeviceAddress();
+ WithDevicePortConnectedState portConnected(portWithData);
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
+ module->connectExternalDevice(portConnected.get(), &ignored))
+ << "when trying to connect a connected device port "
+ << portConnected.get().toString();
+ EXPECT_STATUS(EX_ILLEGAL_STATE, module->connectExternalDevice(portWithData, &ignored))
+ << "when connecting again the external device "
+ << portWithData.ext.get<AudioPortExt::Tag::device>().device.toString()
+ << "; Returned connected port " << ignored.toString() << " for template "
+ << portWithData.toString();
+ }
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, DisconnectExternalDeviceNonResetPortConfig) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ const auto portConfig = moduleConfig->getSingleConfigForDevicePort(portConnected.get());
+ {
+ WithAudioPortConfig config(portConfig);
+ // Note: if SetUp fails, check the status of 'GetAudioPortWithExternalDevices' test.
+ // Our test assumes that 'getAudioPort' returns at least one profile, and it
+ // is not a dynamic profile.
+ ASSERT_NO_FATAL_FAILURE(config.SetUp(module.get()));
+ EXPECT_STATUS(EX_ILLEGAL_STATE, module->disconnectExternalDevice(portConnected.getId()))
+ << "when trying to disconnect device port ID " << port.id
+ << " with active configuration " << config.getId();
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, ExternalDevicePortRoutes) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ std::vector<AudioRoute> routesBefore;
+ ASSERT_IS_OK(module->getAudioRoutes(&routesBefore));
+
+ int32_t connectedPortId;
+ {
+ WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ connectedPortId = portConnected.getId();
+ std::vector<AudioRoute> connectedPortRoutes;
+ ASSERT_IS_OK(module->getAudioRoutesForAudioPort(connectedPortId, &connectedPortRoutes))
+ << "when retrieving routes for connected port id " << connectedPortId;
+ // There must be routes for the port to be useful.
+ if (connectedPortRoutes.empty()) {
+ std::vector<AudioRoute> allRoutes;
+ ASSERT_IS_OK(module->getAudioRoutes(&allRoutes));
+ ADD_FAILURE() << " no routes returned for the connected port "
+ << portConnected.get().toString()
+ << "; all routes: " << android::internal::ToString(allRoutes);
+ }
+ }
+ std::vector<AudioRoute> ignored;
+ ASSERT_STATUS(EX_ILLEGAL_ARGUMENT,
+ module->getAudioRoutesForAudioPort(connectedPortId, &ignored))
+ << "when retrieving routes for released connected port id " << connectedPortId;
+
+ std::vector<AudioRoute> routesAfter;
+ ASSERT_IS_OK(module->getAudioRoutes(&routesAfter));
+ ASSERT_EQ(routesBefore.size(), routesAfter.size())
+ << "Sizes of audio route arrays do not match after creating and "
+ << "releasing a connected port";
+ std::sort(routesBefore.begin(), routesBefore.end());
+ std::sort(routesAfter.begin(), routesAfter.end());
+ EXPECT_EQ(routesBefore, routesAfter);
+ }
+}
+
+template <typename Stream>
+class AudioStream : public AudioCoreModule {
+ public:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ }
+
+ void CloseTwice() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ std::shared_ptr<Stream> heldStream;
+ {
+ WithStream<Stream> stream(portConfig.value());
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ heldStream = stream.getSharedPointer();
+ }
+ EXPECT_STATUS(EX_ILLEGAL_STATE, heldStream->close()) << "when closing the stream twice";
+ }
+
+ void OpenAllConfigs() {
+ const auto allPortConfigs =
+ moduleConfig->getPortConfigsForMixPorts(IOTraits<Stream>::is_input);
+ for (const auto& portConfig : allPortConfigs) {
+ WithStream<Stream> stream(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ }
+ }
+
+ void OpenInvalidBufferSize() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ WithStream<Stream> stream(portConfig.value());
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
+ // The buffer size of 1 frame should be impractically small, and thus
+ // less than any minimum buffer size suggested by any HAL.
+ for (long bufferSize : std::array<long, 4>{-1, 0, 1, std::numeric_limits<long>::max()}) {
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.SetUpNoChecks(module.get(), bufferSize))
+ << "for the buffer size " << bufferSize;
+ EXPECT_EQ(nullptr, stream.get());
+ }
+ }
+
+ void OpenInvalidDirection() {
+ // Important! The direction of the port config must be reversed.
+ const auto portConfig =
+ moduleConfig->getSingleConfigForMixPort(!IOTraits<Stream>::is_input);
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ WithStream<Stream> stream(portConfig.value());
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
+ stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+ << "port config ID " << stream.getPortId();
+ EXPECT_EQ(nullptr, stream.get());
+ }
+
+ void OpenOverMaxCount() {
+ constexpr bool isInput = IOTraits<Stream>::is_input;
+ auto ports = moduleConfig->getMixPorts(isInput);
+ bool hasSingleRun = false;
+ for (const auto& port : ports) {
+ const size_t maxStreamCount = port.ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
+ if (maxStreamCount == 0 ||
+ moduleConfig->getAttachedDevicesPortsForMixPort(isInput, port).empty()) {
+ // No restrictions or no permanently attached devices.
+ continue;
+ }
+ auto portConfigs = moduleConfig->getPortConfigsForMixPorts(isInput, port);
+ if (portConfigs.size() < maxStreamCount + 1) {
+ // Not able to open a sufficient number of streams for this port.
+ continue;
+ }
+ hasSingleRun = true;
+ std::optional<WithStream<Stream>> streamWraps[maxStreamCount + 1];
+ for (size_t i = 0; i <= maxStreamCount; ++i) {
+ streamWraps[i].emplace(portConfigs[i]);
+ WithStream<Stream>& stream = streamWraps[i].value();
+ if (i < maxStreamCount) {
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
+ EXPECT_STATUS(EX_ILLEGAL_STATE,
+ stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+ << "port config ID " << stream.getPortId() << ", maxOpenStreamCount is "
+ << maxStreamCount;
+ }
+ }
+ }
+ if (!hasSingleRun) {
+ GTEST_SKIP() << "Not enough ports to test max open stream count";
+ }
+ }
+
+ void OpenTwiceSamePortConfig() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value()));
+ }
+
+ void ReadOrWrite(bool useSetupSequence2, bool validateObservablePosition) {
+ const auto allPortConfigs =
+ moduleConfig->getPortConfigsForMixPorts(IOTraits<Stream>::is_input);
+ if (allPortConfigs.empty()) {
+ GTEST_SKIP() << "No mix ports have attached devices";
+ }
+ for (const auto& portConfig : allPortConfigs) {
+ EXPECT_NO_FATAL_FAILURE(
+ ReadOrWriteImpl(portConfig, useSetupSequence2, validateObservablePosition))
+ << portConfig.toString();
+ }
+ }
+
+ void ResetPortConfigWithOpenStream() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ WithStream<Stream> stream(portConfig.value());
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(stream.getPortId()))
+ << "port config ID " << stream.getPortId();
+ }
+
+ void SendInvalidCommand() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ EXPECT_NO_FATAL_FAILURE(SendInvalidCommandImpl(portConfig.value()));
+ }
+
+ void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
+ WithStream<Stream> stream1(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSizeFrames));
+ WithStream<Stream> stream2;
+ EXPECT_STATUS(EX_ILLEGAL_STATE, stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
+ kDefaultBufferSizeFrames))
+ << "when opening a stream twice for the same port config ID "
+ << stream1.getPortId();
+ }
+
+ template <class Worker>
+ void WaitForObservablePositionAdvance(Worker& worker) {
+ static constexpr int kWriteDurationUs = 50 * 1000;
+ static constexpr std::chrono::milliseconds kPositionChangeTimeout{10000};
+ int64_t framesInitial;
+ framesInitial = worker.getLastObservablePosition().frames;
+ ASSERT_FALSE(worker.hasError());
+ bool timedOut = false;
+ int64_t frames = framesInitial;
+ for (android::base::Timer elapsed;
+ frames <= framesInitial && !worker.hasError() &&
+ !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
+ usleep(kWriteDurationUs);
+ frames = worker.getLastObservablePosition().frames;
+ }
+ EXPECT_FALSE(timedOut);
+ EXPECT_FALSE(worker.hasError()) << worker.getError();
+ EXPECT_GT(frames, framesInitial);
+ }
+
+ void ReadOrWriteImpl(const AudioPortConfig& portConfig, bool useSetupSequence2,
+ bool validateObservablePosition) {
+ if (!useSetupSequence2) {
+ ASSERT_NO_FATAL_FAILURE(
+ ReadOrWriteSetupSequence1(portConfig, validateObservablePosition));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(
+ ReadOrWriteSetupSequence2(portConfig, validateObservablePosition));
+ }
+ }
+
+ // Set up a patch first, then open a stream.
+ void ReadOrWriteSetupSequence1(const AudioPortConfig& portConfig,
+ bool validateObservablePosition) {
+ auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort(
+ IOTraits<Stream>::is_input, portConfig);
+ ASSERT_FALSE(devicePorts.empty());
+ auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
+ WithAudioPatch patch(IOTraits<Stream>::is_input, portConfig, devicePortConfig);
+ ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+
+ WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ typename IOTraits<Stream>::Worker worker(*stream.getContext());
+
+ ASSERT_TRUE(worker.start());
+ ASSERT_TRUE(worker.waitForAtLeastOneCycle());
+ if (validateObservablePosition) {
+ ASSERT_NO_FATAL_FAILURE(WaitForObservablePositionAdvance(worker));
+ }
+ }
+
+ // Open a stream, then set up a patch for it.
+ void ReadOrWriteSetupSequence2(const AudioPortConfig& portConfig,
+ bool validateObservablePosition) {
+ WithStream<Stream> stream(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ typename IOTraits<Stream>::Worker worker(*stream.getContext());
+
+ auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort(
+ IOTraits<Stream>::is_input, portConfig);
+ ASSERT_FALSE(devicePorts.empty());
+ auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
+ WithAudioPatch patch(IOTraits<Stream>::is_input, stream.getPortConfig(), devicePortConfig);
+ ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+
+ ASSERT_TRUE(worker.start());
+ ASSERT_TRUE(worker.waitForAtLeastOneCycle());
+ if (validateObservablePosition) {
+ ASSERT_NO_FATAL_FAILURE(WaitForObservablePositionAdvance(worker));
+ }
+ }
+
+ void SendInvalidCommandImpl(const AudioPortConfig& portConfig) {
+ std::vector<StreamDescriptor::Command> commands(6);
+ commands[0].code = -1;
+ commands[1].code = StreamDescriptor::COMMAND_BURST - 1;
+ commands[2].code = std::numeric_limits<int32_t>::min();
+ commands[3].code = std::numeric_limits<int32_t>::max();
+ commands[4].code = StreamDescriptor::COMMAND_BURST;
+ commands[4].fmqByteCount = -1;
+ commands[5].code = StreamDescriptor::COMMAND_BURST;
+ commands[5].fmqByteCount = std::numeric_limits<int32_t>::min();
+ WithStream<Stream> stream(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamWorker<StreamInvalidCommandLogic> writer(*stream.getContext(), commands);
+ ASSERT_TRUE(writer.start());
+ writer.waitForAtLeastOneCycle();
+ auto unexpectedStatuses = writer.getUnexpectedStatuses();
+ EXPECT_EQ(0UL, unexpectedStatuses.size())
+ << "Pairs of (command, actual status): "
+ << android::internal::ToString(unexpectedStatuses);
+ }
+};
+using AudioStreamIn = AudioStream<IStreamIn>;
+using AudioStreamOut = AudioStream<IStreamOut>;
+
+#define TEST_IO_STREAM(method_name) \
+ TEST_P(AudioStreamIn, method_name) { ASSERT_NO_FATAL_FAILURE(method_name()); } \
+ TEST_P(AudioStreamOut, method_name) { ASSERT_NO_FATAL_FAILURE(method_name()); }
+#define TEST_IO_STREAM_2(method_name, arg1, arg2) \
+ TEST_P(AudioStreamIn, method_name##_##arg1##_##arg2) { \
+ ASSERT_NO_FATAL_FAILURE(method_name(arg1, arg2)); \
+ } \
+ TEST_P(AudioStreamOut, method_name##_##arg1##_##arg2) { \
+ ASSERT_NO_FATAL_FAILURE(method_name(arg1, arg2)); \
+ }
+
+TEST_IO_STREAM(CloseTwice);
+TEST_IO_STREAM(OpenAllConfigs);
+TEST_IO_STREAM(OpenInvalidBufferSize);
+TEST_IO_STREAM(OpenInvalidDirection);
+TEST_IO_STREAM(OpenOverMaxCount);
+TEST_IO_STREAM(OpenTwiceSamePortConfig);
+// Use of constants makes comprehensible test names.
+constexpr bool SetupSequence1 = false;
+constexpr bool SetupSequence2 = true;
+constexpr bool SetupOnly = false;
+constexpr bool ValidateObservablePosition = true;
+TEST_IO_STREAM_2(ReadOrWrite, SetupSequence1, SetupOnly);
+TEST_IO_STREAM_2(ReadOrWrite, SetupSequence2, SetupOnly);
+TEST_IO_STREAM_2(ReadOrWrite, SetupSequence1, ValidateObservablePosition);
+TEST_IO_STREAM_2(ReadOrWrite, SetupSequence2, ValidateObservablePosition);
+TEST_IO_STREAM(ResetPortConfigWithOpenStream);
+TEST_IO_STREAM(SendInvalidCommand);
+
+TEST_P(AudioStreamOut, OpenTwicePrimary) {
+ const auto mixPorts = moduleConfig->getMixPorts(false);
+ auto primaryPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [](const AudioPort& port) {
+ return port.flags.getTag() == AudioIoFlags::Tag::output &&
+ isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::PRIMARY);
+ });
+ if (primaryPortIt == mixPorts.end()) {
+ GTEST_SKIP() << "No primary mix port";
+ }
+ if (moduleConfig->getAttachedSinkDevicesPortsForMixPort(*primaryPortIt).empty()) {
+ GTEST_SKIP() << "Primary mix port can not be routed to any of attached devices";
+ }
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *primaryPortIt);
+ ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for the primary mix port";
+ EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value()));
+}
+
+TEST_P(AudioStreamOut, RequireOffloadInfo) {
+ const auto offloadMixPorts =
+ moduleConfig->getOffloadMixPorts(true /*attachedOnly*/, true /*singlePort*/);
+ if (offloadMixPorts.empty()) {
+ GTEST_SKIP()
+ << "No mix port for compressed offload that could be routed to attached devices";
+ }
+ const auto portConfig =
+ moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin());
+ ASSERT_TRUE(portConfig.has_value())
+ << "No profiles specified for the compressed offload mix port";
+ StreamDescriptor descriptor;
+ std::shared_ptr<IStreamOut> ignored;
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
+ args.portConfigId = portConfig.value().id;
+ args.sourceMetadata = GenerateSourceMetadata(portConfig.value());
+ args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
+ << "when no offload info is provided for a compressed offload mix port";
+}
+
+// Tests specific to audio patches. The fixure class is named 'AudioModulePatch'
+// to avoid clashing with 'AudioPatch' class.
+class AudioModulePatch : public AudioCoreModule {
+ public:
+ static std::string direction(bool isInput, bool capitalize) {
+ return isInput ? (capitalize ? "Input" : "input") : (capitalize ? "Output" : "output");
+ }
+
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ }
+
+ void SetInvalidPatchHelper(int32_t expectedException, const std::vector<int32_t>& sources,
+ const std::vector<int32_t>& sinks) {
+ AudioPatch patch;
+ patch.sourcePortConfigIds = sources;
+ patch.sinkPortConfigIds = sinks;
+ ASSERT_STATUS(expectedException, module->setAudioPatch(patch, &patch))
+ << "patch source ids: " << android::internal::ToString(sources)
+ << "; sink ids: " << android::internal::ToString(sinks);
+ }
+
+ void ResetPortConfigUsedByPatch(bool isInput) {
+ auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+ if (srcSinkGroups.empty()) {
+ GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+ }
+ auto srcSinkGroup = *srcSinkGroups.begin();
+ auto srcSink = *srcSinkGroup.second.begin();
+ WithAudioPatch patch(srcSink.first, srcSink.second);
+ ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ std::vector<int32_t> sourceAndSinkPortConfigIds(patch.get().sourcePortConfigIds);
+ sourceAndSinkPortConfigIds.insert(sourceAndSinkPortConfigIds.end(),
+ patch.get().sinkPortConfigIds.begin(),
+ patch.get().sinkPortConfigIds.end());
+ for (const auto portConfigId : sourceAndSinkPortConfigIds) {
+ EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(portConfigId))
+ << "port config ID " << portConfigId;
+ }
+ }
+
+ void SetInvalidPatch(bool isInput) {
+ auto srcSinkPair = moduleConfig->getRoutableSrcSinkPair(isInput);
+ if (!srcSinkPair.has_value()) {
+ GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+ }
+ WithAudioPortConfig srcPortConfig(srcSinkPair.value().first);
+ ASSERT_NO_FATAL_FAILURE(srcPortConfig.SetUp(module.get()));
+ WithAudioPortConfig sinkPortConfig(srcSinkPair.value().second);
+ ASSERT_NO_FATAL_FAILURE(sinkPortConfig.SetUp(module.get()));
+ { // Check that the pair can actually be used for setting up a patch.
+ WithAudioPatch patch(srcPortConfig.get(), sinkPortConfig.get());
+ ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ }
+ EXPECT_NO_FATAL_FAILURE(
+ SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {}, {sinkPortConfig.getId()}));
+ EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(
+ EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId(), srcPortConfig.getId()},
+ {sinkPortConfig.getId()}));
+ EXPECT_NO_FATAL_FAILURE(
+ SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {}));
+ EXPECT_NO_FATAL_FAILURE(
+ SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()},
+ {sinkPortConfig.getId(), sinkPortConfig.getId()}));
+
+ std::set<int32_t> portConfigIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+ for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
+ EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {portConfigId},
+ {sinkPortConfig.getId()}));
+ EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT,
+ {srcPortConfig.getId()}, {portConfigId}));
+ }
+ }
+
+ void SetNonRoutablePatch(bool isInput) {
+ auto srcSinkPair = moduleConfig->getNonRoutableSrcSinkPair(isInput);
+ if (!srcSinkPair.has_value()) {
+ GTEST_SKIP() << "All possible source/sink pairs are routable";
+ }
+ WithAudioPatch patch(srcSinkPair.value().first, srcSinkPair.value().second);
+ ASSERT_NO_FATAL_FAILURE(patch.SetUpPortConfigs(module.get()));
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, patch.SetUpNoChecks(module.get()))
+ << "when setting up a patch from " << srcSinkPair.value().first.toString() << " to "
+ << srcSinkPair.value().second.toString() << " that does not have a route";
+ }
+
+ void SetPatch(bool isInput) {
+ auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+ if (srcSinkGroups.empty()) {
+ GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+ }
+ for (const auto& srcSinkGroup : srcSinkGroups) {
+ const auto& route = srcSinkGroup.first;
+ std::vector<std::unique_ptr<WithAudioPatch>> patches;
+ for (const auto& srcSink : srcSinkGroup.second) {
+ if (!route.isExclusive) {
+ patches.push_back(
+ std::make_unique<WithAudioPatch>(srcSink.first, srcSink.second));
+ EXPECT_NO_FATAL_FAILURE(patches[patches.size() - 1]->SetUp(module.get()));
+ } else {
+ WithAudioPatch patch(srcSink.first, srcSink.second);
+ EXPECT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ }
+ }
+ }
+ }
+
+ void UpdatePatch(bool isInput) {
+ auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+ if (srcSinkGroups.empty()) {
+ GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+ }
+ for (const auto& srcSinkGroup : srcSinkGroups) {
+ for (const auto& srcSink : srcSinkGroup.second) {
+ WithAudioPatch patch(srcSink.first, srcSink.second);
+ ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ AudioPatch ignored;
+ EXPECT_NO_FATAL_FAILURE(module->setAudioPatch(patch.get(), &ignored));
+ }
+ }
+ }
+
+ void UpdateInvalidPatchId(bool isInput) {
+ auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput);
+ if (srcSinkGroups.empty()) {
+ GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices";
+ }
+ // First, set up a patch to ensure that its settings are accepted.
+ auto srcSinkGroup = *srcSinkGroups.begin();
+ auto srcSink = *srcSinkGroup.second.begin();
+ WithAudioPatch patch(srcSink.first, srcSink.second);
+ ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ // Then use the same patch setting, except for having an invalid ID.
+ std::set<int32_t> patchIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
+ for (const auto patchId : GetNonExistentIds(patchIds)) {
+ AudioPatch patchWithNonExistendId = patch.get();
+ patchWithNonExistendId.id = patchId;
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
+ module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId))
+ << "patch ID " << patchId;
+ }
+ }
+};
+
+// Not all tests require both directions, so parametrization would require
+// more abstractions.
+#define TEST_PATCH_BOTH_DIRECTIONS(method_name) \
+ TEST_P(AudioModulePatch, method_name##Input) { ASSERT_NO_FATAL_FAILURE(method_name(true)); } \
+ TEST_P(AudioModulePatch, method_name##Output) { ASSERT_NO_FATAL_FAILURE(method_name(false)); }
+
+TEST_PATCH_BOTH_DIRECTIONS(ResetPortConfigUsedByPatch);
+TEST_PATCH_BOTH_DIRECTIONS(SetInvalidPatch);
+TEST_PATCH_BOTH_DIRECTIONS(SetNonRoutablePatch);
+TEST_PATCH_BOTH_DIRECTIONS(SetPatch);
+TEST_PATCH_BOTH_DIRECTIONS(UpdateInvalidPatchId);
+TEST_PATCH_BOTH_DIRECTIONS(UpdatePatch);
+
+TEST_P(AudioModulePatch, ResetInvalidPatchId) {
+ std::set<int32_t> patchIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
+ for (const auto patchId : GetNonExistentIds(patchIds)) {
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->resetAudioPatch(patchId))
+ << "patch ID " << patchId;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioCoreModuleTest, AudioCoreModule,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+ android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreModule);
+INSTANTIATE_TEST_SUITE_P(AudioStreamInTest, AudioStreamIn,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+ android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIn);
+INSTANTIATE_TEST_SUITE_P(AudioStreamOutTest, AudioStreamOut,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+ android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut);
+INSTANTIATE_TEST_SUITE_P(AudioPatchTest, AudioModulePatch,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+ android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModulePatch);
+
+class TestExecutionTracer : public ::testing::EmptyTestEventListener {
+ public:
+ void OnTestStart(const ::testing::TestInfo& test_info) override {
+ TraceTestState("Started", test_info);
+ }
+
+ void OnTestEnd(const ::testing::TestInfo& test_info) override {
+ TraceTestState("Completed", test_info);
+ }
+
+ private:
+ static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info) {
+ LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name();
+ }
+};
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
new file mode 100644
index 0000000..8b5eb13
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#define LOG_TAG "VtsHalAudioEffect"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <aidl/android/hardware/audio/effect/IFactory.h>
+
+#include "AudioHalBinderServiceUtil.h"
+#include "TestUtils.h"
+
+using namespace android;
+
+using ndk::ScopedAStatus;
+
+using aidl::android::hardware::audio::effect::Descriptor;
+using aidl::android::hardware::audio::effect::IEffect;
+using aidl::android::hardware::audio::effect::IFactory;
+using aidl::android::media::audio::common::AudioUuid;
+
+class EffectFactoryHelper {
+ public:
+ explicit EffectFactoryHelper(const std::string& name) : mServiceName(name) {}
+
+ void ConnectToFactoryService() {
+ mEffectFactory = IFactory::fromBinder(binderUtil.connectToService(mServiceName));
+ ASSERT_NE(mEffectFactory, nullptr);
+ }
+
+ void RestartFactoryService() {
+ ASSERT_NE(mEffectFactory, nullptr);
+ mEffectFactory = IFactory::fromBinder(binderUtil.restartService());
+ ASSERT_NE(mEffectFactory, nullptr);
+ }
+
+ void QueryAllEffects() {
+ EXPECT_NE(mEffectFactory, nullptr);
+ EXPECT_IS_OK(mEffectFactory->queryEffects(std::nullopt, std::nullopt, &mCompleteIds));
+ }
+
+ void QueryEffects(const std::optional<AudioUuid>& in_type,
+ const std::optional<AudioUuid>& in_instance,
+ std::vector<Descriptor::Identity>* _aidl_return) {
+ EXPECT_NE(mEffectFactory, nullptr);
+ EXPECT_IS_OK(mEffectFactory->queryEffects(in_type, in_instance, _aidl_return));
+ mIds = *_aidl_return;
+ }
+
+ void CreateEffects() {
+ EXPECT_NE(mEffectFactory, nullptr);
+ for (const auto& id : mIds) {
+ std::shared_ptr<IEffect> effect;
+ EXPECT_IS_OK(mEffectFactory->createEffect(id.uuid, &effect));
+ EXPECT_NE(effect, nullptr) << id.toString();
+ mEffectIdMap[effect] = id;
+ }
+ }
+
+ void DestroyEffects() {
+ EXPECT_NE(mEffectFactory, nullptr);
+ for (const auto& it : mEffectIdMap) {
+ EXPECT_IS_OK(mEffectFactory->destroyEffect(it.first));
+ }
+ mEffectIdMap.clear();
+ }
+
+ std::shared_ptr<IFactory> GetFactory() { return mEffectFactory; }
+ const std::vector<Descriptor::Identity>& GetEffectIds() { return mIds; }
+ const std::vector<Descriptor::Identity>& GetCompleteEffectIdList() { return mCompleteIds; }
+ const std::unordered_map<std::shared_ptr<IEffect>, Descriptor::Identity>& GetEffectMap() {
+ return mEffectIdMap;
+ }
+
+ private:
+ std::shared_ptr<IFactory> mEffectFactory;
+ std::string mServiceName;
+ AudioHalBinderServiceUtil binderUtil;
+ std::vector<Descriptor::Identity> mIds;
+ std::vector<Descriptor::Identity> mCompleteIds;
+ std::unordered_map<std::shared_ptr<IEffect>, Descriptor::Identity> mEffectIdMap;
+};
+
+/// Effect factory testing.
+class EffectFactoryTest : public testing::TestWithParam<std::string> {
+ public:
+ void SetUp() override { ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService()); }
+
+ void TearDown() override { mFactory.DestroyEffects(); }
+
+ EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam());
+
+ // TODO: these UUID can get from config file
+ // ec7178ec-e5e1-4432-a3f4-4657e6795210
+ const AudioUuid nullUuid = {static_cast<int32_t>(0xec7178ec),
+ 0xe5e1,
+ 0x4432,
+ 0xa3f4,
+ {0x46, 0x57, 0xe6, 0x79, 0x52, 0x10}};
+ const AudioUuid zeroUuid = {
+ static_cast<int32_t>(0x0), 0x0, 0x0, 0x0, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
+};
+
+TEST_P(EffectFactoryTest, SetupAndTearDown) {
+ // Intentionally empty test body.
+}
+
+TEST_P(EffectFactoryTest, CanBeRestarted) {
+ ASSERT_NO_FATAL_FAILURE(mFactory.RestartFactoryService());
+}
+
+TEST_P(EffectFactoryTest, QueriedDescriptorList) {
+ std::vector<Descriptor::Identity> descriptors;
+ mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+ EXPECT_NE(descriptors.size(), 0UL);
+}
+
+TEST_P(EffectFactoryTest, DescriptorUUIDNotNull) {
+ std::vector<Descriptor::Identity> descriptors;
+ mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+ // TODO: Factory eventually need to return the full list of MUST supported AOSP effects.
+ for (auto& desc : descriptors) {
+ EXPECT_NE(desc.type, zeroUuid);
+ EXPECT_NE(desc.uuid, zeroUuid);
+ }
+}
+
+TEST_P(EffectFactoryTest, QueriedDescriptorNotExistType) {
+ std::vector<Descriptor::Identity> descriptors;
+ mFactory.QueryEffects(nullUuid, std::nullopt, &descriptors);
+ EXPECT_EQ(descriptors.size(), 0UL);
+}
+
+TEST_P(EffectFactoryTest, QueriedDescriptorNotExistInstance) {
+ std::vector<Descriptor::Identity> descriptors;
+ mFactory.QueryEffects(std::nullopt, nullUuid, &descriptors);
+ EXPECT_EQ(descriptors.size(), 0UL);
+}
+
+TEST_P(EffectFactoryTest, CreateAndDestroyRepeat) {
+ std::vector<Descriptor::Identity> descriptors;
+ mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+ auto numIds = mFactory.GetEffectIds().size();
+ EXPECT_NE(numIds, 0UL);
+
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+ mFactory.CreateEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), numIds);
+ mFactory.DestroyEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+
+ // Create and destroy again
+ mFactory.CreateEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), numIds);
+ mFactory.DestroyEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+}
+
+TEST_P(EffectFactoryTest, CreateMultipleInstanceOfSameEffect) {
+ std::vector<Descriptor::Identity> descriptors;
+ mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+ auto numIds = mFactory.GetEffectIds().size();
+ EXPECT_NE(numIds, 0UL);
+
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+ mFactory.CreateEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), numIds);
+ // Create effect instances of same implementation
+ mFactory.CreateEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 2 * numIds);
+
+ mFactory.CreateEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 3 * numIds);
+
+ mFactory.DestroyEffects();
+ EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+}
+
+INSTANTIATE_TEST_SUITE_P(EffectFactoryTest, EffectFactoryTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)),
+ android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EffectFactoryTest);
+
+/// Effect testing.
+class AudioEffect : public testing::TestWithParam<std::string> {
+ public:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService());
+ ASSERT_NO_FATAL_FAILURE(mFactory.CreateEffects());
+ }
+
+ void TearDown() override {
+ CloseEffects();
+ ASSERT_NO_FATAL_FAILURE(mFactory.DestroyEffects());
+ }
+
+ void OpenEffects() {
+ auto open = [](const std::shared_ptr<IEffect>& effect) { EXPECT_IS_OK(effect->open()); };
+ EXPECT_NO_FATAL_FAILURE(ForEachEffect(open));
+ }
+
+ void CloseEffects() {
+ auto close = [](const std::shared_ptr<IEffect>& effect) { EXPECT_IS_OK(effect->close()); };
+ EXPECT_NO_FATAL_FAILURE(ForEachEffect(close));
+ }
+
+ void GetEffectDescriptors() {
+ auto get = [](const std::shared_ptr<IEffect>& effect) {
+ Descriptor desc;
+ EXPECT_IS_OK(effect->getDescriptor(&desc));
+ };
+ EXPECT_NO_FATAL_FAILURE(ForEachEffect(get));
+ }
+
+ template <typename Functor>
+ void ForEachEffect(Functor functor) {
+ auto effectMap = mFactory.GetEffectMap();
+ for (const auto& it : effectMap) {
+ SCOPED_TRACE(it.second.toString());
+ functor(it.first);
+ }
+ }
+
+ EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam());
+};
+
+TEST_P(AudioEffect, OpenEffectTest) {
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+}
+
+TEST_P(AudioEffect, OpenAndCloseEffect) {
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+}
+
+TEST_P(AudioEffect, CloseUnopenedEffectTest) {
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+}
+
+TEST_P(AudioEffect, DoubleOpenCloseEffects) {
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+
+ EXPECT_NO_FATAL_FAILURE(OpenEffects());
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+ EXPECT_NO_FATAL_FAILURE(CloseEffects());
+}
+
+TEST_P(AudioEffect, GetDescriptors) {
+ EXPECT_NO_FATAL_FAILURE(GetEffectDescriptors());
+}
+
+TEST_P(AudioEffect, DescriptorIdExistAndUnique) {
+ auto checker = [&](const std::shared_ptr<IEffect>& effect) {
+ Descriptor desc;
+ std::vector<Descriptor::Identity> idList;
+ EXPECT_IS_OK(effect->getDescriptor(&desc));
+ mFactory.QueryEffects(desc.common.id.type, desc.common.id.uuid, &idList);
+ EXPECT_EQ(idList.size(), 1UL);
+ };
+ EXPECT_NO_FATAL_FAILURE(ForEachEffect(checker));
+
+ // Check unique with a set
+ auto stringHash = [](const Descriptor::Identity& id) {
+ return std::hash<std::string>()(id.toString());
+ };
+ auto vec = mFactory.GetCompleteEffectIdList();
+ std::unordered_set<Descriptor::Identity, decltype(stringHash)> idSet(0, stringHash);
+ for (auto it : vec) {
+ EXPECT_EQ(idSet.count(it), 0UL);
+ idSet.insert(it);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioEffectTest, AudioEffect,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)),
+ android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioEffect);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/audio/common/5.0/Android.bp b/audio/common/5.0/Android.bp
index fd8e85f..a6bb331 100644
--- a/audio/common/5.0/Android.bp
+++ b/audio/common/5.0/Android.bp
@@ -22,6 +22,6 @@
gen_java_constants: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
}
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index e5ed844..f718e7a 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -17,7 +17,6 @@
#include <array>
#include <string>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#define LOG_TAG "HidlUtils_Test"
@@ -1101,12 +1100,15 @@
TYPED_TEST_SUITE(FilterTest, FilterTestTypeParams);
TYPED_TEST(FilterTest, FilterOutNonVendorTags) {
- SKIP_WITH_HWASAN; // b/230535046
TypeParam emptyTags;
EXPECT_EQ(emptyTags, HidlUtils::filterOutNonVendorTags(emptyTags));
- TypeParam allVendorTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
- EXPECT_EQ(allVendorTags, HidlUtils::filterOutNonVendorTags(allVendorTags));
+ // b/248421569, allocate two vendor tags at a time can run out of memory
+ // TypeParam allVendorTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+ TypeParam allVendorTags1 = {{"VX_GOOGLE_VR_42"}};
+ EXPECT_EQ(allVendorTags1, HidlUtils::filterOutNonVendorTags(allVendorTags1));
+ TypeParam allVendorTags2 = {{"VX_GOOGLE_1E100"}};
+ EXPECT_EQ(allVendorTags2, HidlUtils::filterOutNonVendorTags(allVendorTags2));
TypeParam oneVendorTag = {{"", "VX_GOOGLE_VR", "random_string"}};
TypeParam oneVendorTagOnly = HidlUtils::filterOutNonVendorTags(oneVendorTag);
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index dfc2386..505c54c 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -14,7 +14,11 @@
* limitations under the License.
*/
+#include <fstream>
+#include <numeric>
+
#include <android-base/chrono_utils.h>
+#include <cutils/properties.h>
#include "Generators.h"
@@ -517,20 +521,10 @@
}
bool canQueryPresentationPosition() const {
- auto maybeSinkAddress =
- getCachedPolicyConfig().getSinkDeviceForMixPort(getDeviceName(), getMixPortName());
- // Returning 'true' when no sink is found so the test can fail later with a more clear
- // problem description.
- return !maybeSinkAddress.has_value() ||
- !xsd::isTelephonyDevice(maybeSinkAddress.value().deviceType);
+ return !xsd::isTelephonyDevice(address.deviceType);
}
void createPatchIfNeeded() {
- auto maybeSinkAddress =
- getCachedPolicyConfig().getSinkDeviceForMixPort(getDeviceName(), getMixPortName());
- ASSERT_TRUE(maybeSinkAddress.has_value())
- << "No sink device found for mix port " << getMixPortName() << " (module "
- << getDeviceName() << ")";
if (areAudioPatchesSupported()) {
AudioPortConfig source;
source.base.format.value(getConfig().base.format);
@@ -540,13 +534,13 @@
source.ext.mix().ioHandle = helper.getIoHandle();
source.ext.mix().useCase.stream({});
AudioPortConfig sink;
- sink.ext.device(maybeSinkAddress.value());
+ sink.ext.device(address);
EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{source},
hidl_vec<AudioPortConfig>{sink},
returnIn(res, mPatchHandle)));
mHasPatch = res == Result::OK;
} else {
- EXPECT_OK(stream->setDevices({maybeSinkAddress.value()}));
+ EXPECT_OK(stream->setDevices({address}));
}
}
@@ -556,10 +550,6 @@
EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle));
mHasPatch = false;
}
- } else {
- if (stream) {
- EXPECT_OK(stream->setDevices({address}));
- }
}
}
@@ -575,16 +565,22 @@
// Sometimes HAL doesn't have enough information until the audio data actually gets
// consumed by the hardware.
bool timedOut = false;
- res = Result::INVALID_STATE;
- for (android::base::Timer elapsed;
- res != Result::OK && !writer.hasError() &&
- !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
- usleep(kWriteDurationUs);
- ASSERT_OK(stream->getPresentationPosition(returnIn(res, framesInitial, ts)));
- ASSERT_RESULT(okOrInvalidState, res);
+ if (!firstPosition || *firstPosition == std::numeric_limits<uint64_t>::max()) {
+ res = Result::INVALID_STATE;
+ for (android::base::Timer elapsed;
+ res != Result::OK && !writer.hasError() &&
+ !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
+ usleep(kWriteDurationUs);
+ ASSERT_OK(stream->getPresentationPosition(returnIn(res, framesInitial, ts)));
+ ASSERT_RESULT(okOrInvalidState, res);
+ }
+ ASSERT_FALSE(writer.hasError());
+ ASSERT_FALSE(timedOut);
+ } else {
+ // Use `firstPosition` instead of querying it from the HAL. This is used when
+ // `waitForPresentationPositionAdvance` is called in a loop.
+ framesInitial = *firstPosition;
}
- ASSERT_FALSE(writer.hasError());
- ASSERT_FALSE(timedOut);
uint64_t frames = framesInitial;
for (android::base::Timer elapsed;
@@ -646,7 +642,7 @@
ASSERT_OK(stream->standby());
writer.resume();
- uint64_t frames;
+ uint64_t frames = std::numeric_limits<uint64_t>::max();
ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, &frames));
EXPECT_GT(frames, framesInitial);
@@ -691,24 +687,12 @@
InputStreamTest::TearDown();
}
- bool canQueryCapturePosition() const {
- auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort(
- getDeviceName(), getMixPortName());
- // Returning 'true' when no source is found so the test can fail later with a more clear
- // problem description.
- return !maybeSourceAddress.has_value() ||
- !xsd::isTelephonyDevice(maybeSourceAddress.value().deviceType);
- }
+ bool canQueryCapturePosition() const { return !xsd::isTelephonyDevice(address.deviceType); }
void createPatchIfNeeded() {
- auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort(
- getDeviceName(), getMixPortName());
- ASSERT_TRUE(maybeSourceAddress.has_value())
- << "No source device found for mix port " << getMixPortName() << " (module "
- << getDeviceName() << ")";
if (areAudioPatchesSupported()) {
AudioPortConfig source;
- source.ext.device(maybeSourceAddress.value());
+ source.ext.device(address);
AudioPortConfig sink;
sink.base.format.value(getConfig().base.format);
sink.base.sampleRateHz.value(getConfig().base.sampleRateHz);
@@ -721,7 +705,7 @@
returnIn(res, mPatchHandle)));
mHasPatch = res == Result::OK;
} else {
- EXPECT_OK(stream->setDevices({maybeSourceAddress.value()}));
+ EXPECT_OK(stream->setDevices({address}));
}
}
@@ -731,10 +715,6 @@
EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle));
mHasPatch = false;
}
- } else {
- if (stream) {
- EXPECT_OK(stream->setDevices({address}));
- }
}
}
@@ -864,14 +844,8 @@
}
ASSERT_OK(res);
- auto maybeSourceAddress =
- getCachedPolicyConfig().getSourceDeviceForMixPort(getDeviceName(), getMixPortName());
- ASSERT_TRUE(maybeSourceAddress.has_value())
- << "No source device found for mix port " << getMixPortName() << " (module "
- << getDeviceName() << ")";
-
for (auto microphone : microphones) {
- if (microphone.deviceAddress == maybeSourceAddress.value()) {
+ if (microphone.deviceAddress == address) {
StreamReader reader(stream.get(), stream->getBufferSize());
ASSERT_TRUE(reader.start());
reader.pause(); // This ensures that at least one read has happened.
@@ -889,3 +863,176 @@
::testing::ValuesIn(getBuiltinMicConfigParameters()),
&DeviceConfigParameterToString);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MicrophoneInfoInputStreamTest);
+
+static const std::vector<DeviceConfigParameter>& getOutputDeviceCompressedConfigParameters(
+ const AudioConfigBase& configToMatch) {
+ static const std::vector<DeviceConfigParameter> parameters = [&] {
+ auto allParams = getOutputDeviceConfigParameters();
+ std::vector<DeviceConfigParameter> compressedParams;
+ std::copy_if(allParams.begin(), allParams.end(), std::back_inserter(compressedParams),
+ [&](auto cfg) {
+ if (std::get<PARAM_CONFIG>(cfg).base != configToMatch) return false;
+ const auto& flags = std::get<PARAM_FLAGS>(cfg);
+ return std::find_if(flags.begin(), flags.end(), [](const auto& flag) {
+ return flag ==
+ toString(xsd::AudioInOutFlag::
+ AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD);
+ }) != flags.end();
+ });
+ return compressedParams;
+ }();
+ return parameters;
+}
+
+class CompressedOffloadOutputStreamTest : public PcmOnlyConfigOutputStreamTest {
+ public:
+ void loadData(const std::string& fileName, std::vector<uint8_t>* data) {
+ std::ifstream is(fileName, std::ios::in | std::ios::binary);
+ ASSERT_TRUE(is.good()) << "Failed to open file " << fileName;
+ is.seekg(0, is.end);
+ data->reserve(data->size() + is.tellg());
+ is.seekg(0, is.beg);
+ data->insert(data->end(), std::istreambuf_iterator<char>(is),
+ std::istreambuf_iterator<char>());
+ ASSERT_TRUE(!is.fail()) << "Failed to read from file " << fileName;
+ }
+};
+
+class OffloadCallbacks : public IStreamOutCallback {
+ public:
+ Return<void> onDrainReady() override {
+ ALOGI("onDrainReady");
+ {
+ std::lock_guard lg(mLock);
+ mOnDrainReady = true;
+ }
+ mCondVar.notify_one();
+ return {};
+ }
+ Return<void> onWriteReady() override { return {}; }
+ Return<void> onError() override {
+ ALOGW("onError");
+ {
+ std::lock_guard lg(mLock);
+ mOnError = true;
+ }
+ mCondVar.notify_one();
+ return {};
+ }
+ bool waitForDrainReadyOrError() {
+ std::unique_lock l(mLock);
+ if (!mOnDrainReady && !mOnError) {
+ mCondVar.wait(l, [&]() { return mOnDrainReady || mOnError; });
+ }
+ const bool success = !mOnError;
+ mOnDrainReady = mOnError = false;
+ return success;
+ }
+
+ private:
+ std::mutex mLock;
+ bool mOnDrainReady = false;
+ bool mOnError = false;
+ std::condition_variable mCondVar;
+};
+
+TEST_P(CompressedOffloadOutputStreamTest, Mp3FormatGaplessOffload) {
+ doc::test("Check that compressed offload mix ports for MP3 implement gapless offload");
+ const auto& flags = getOutputFlags();
+ const bool isNewDeviceLaunchingOnTPlus = property_get_int32("ro.vendor.api_level", 0) >= 33;
+ // See test instantiation, only offload MP3 mix ports are used.
+ if (std::find_if(flags.begin(), flags.end(), [](const auto& flag) {
+ return flag == toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_GAPLESS_OFFLOAD);
+ }) == flags.end()) {
+ if (isNewDeviceLaunchingOnTPlus) {
+ FAIL() << "New devices launching on Android T+ must support gapless offload, "
+ << "see VSR-4.3-001";
+ } else {
+ GTEST_SKIP() << "Compressed offload mix port does not support gapless offload";
+ }
+ }
+ std::vector<uint8_t> offloadData;
+ ASSERT_NO_FATAL_FAILURE(loadData("/data/local/tmp/sine882hz3s.mp3", &offloadData));
+ ASSERT_FALSE(offloadData.empty());
+ ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+ const int presentationeEndPrecisionMs = 1000;
+ const int sampleRate = 44100;
+ const int significantSampleNumber = (presentationeEndPrecisionMs * sampleRate) / 1000;
+ const int delay = 576 + 1000;
+ const int padding = 756 + 1000;
+ const int durationMs = 3000 - 44;
+ auto start = std::chrono::steady_clock::now();
+ auto callbacks = sp<OffloadCallbacks>::make();
+ std::mutex presentationEndLock;
+ std::vector<float> presentationEndTimes;
+ // StreamWriter plays 'offloadData' in a loop, possibly using multiple calls to 'write', this
+ // depends on the relative sizes of 'offloadData' and the HAL buffer. Writer calls 'onDataStart'
+ // each time it starts writing the buffer from the beginning, and 'onDataWrap' callback each
+ // time it wraps around the buffer.
+ StreamWriter writer(
+ stream.get(), stream->getBufferSize(), std::move(offloadData),
+ [&]() /* onDataStart */ { start = std::chrono::steady_clock::now(); },
+ [&]() /* onDataWrap */ {
+ Return<Result> ret(Result::OK);
+ // Decrease the volume since the test plays a loud sine wave.
+ ret = stream->setVolume(0.1, 0.1);
+ if (!ret.isOk() || ret != Result::OK) {
+ ALOGE("%s: setVolume failed: %s", __func__, toString(ret).c_str());
+ return false;
+ }
+ ret = Parameters::set(
+ stream, {{AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES, std::to_string(delay)},
+ {AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES, std::to_string(padding)}});
+ if (!ret.isOk() || ret != Result::OK) {
+ ALOGE("%s: setParameters failed: %s", __func__, toString(ret).c_str());
+ return false;
+ }
+ ret = stream->drain(AudioDrain::EARLY_NOTIFY);
+ if (!ret.isOk() || ret != Result::OK) {
+ ALOGE("%s: drain failed: %s", __func__, toString(ret).c_str());
+ return false;
+ }
+ // FIXME: On some SoCs intermittent errors are possible, ignore them.
+ if (callbacks->waitForDrainReadyOrError()) {
+ const float duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start)
+ .count();
+ std::lock_guard lg(presentationEndLock);
+ presentationEndTimes.push_back(duration);
+ }
+ return true;
+ });
+ ASSERT_OK(stream->setCallback(callbacks));
+ ASSERT_TRUE(writer.start());
+ // How many times to loop the track so that the sum of gapless delay and padding from
+ // the first presentation end to the last is at least 'presentationeEndPrecisionMs'.
+ const int playbackNumber = (int)(significantSampleNumber / ((float)delay + padding) + 1);
+ for (bool done = false; !done;) {
+ usleep(presentationeEndPrecisionMs * 1000);
+ {
+ std::lock_guard lg(presentationEndLock);
+ done = presentationEndTimes.size() >= playbackNumber;
+ }
+ ASSERT_FALSE(writer.hasError()) << "Recent write or drain operation has failed";
+ }
+ const float avgDuration =
+ std::accumulate(presentationEndTimes.begin(), presentationEndTimes.end(), 0.0) /
+ presentationEndTimes.size();
+ std::stringstream observedEndTimes;
+ std::copy(presentationEndTimes.begin(), presentationEndTimes.end(),
+ std::ostream_iterator<float>(observedEndTimes, ", "));
+ EXPECT_NEAR(durationMs, avgDuration, presentationeEndPrecisionMs * 0.1)
+ << "Observed durations: " << observedEndTimes.str();
+ writer.stop();
+ EXPECT_OK(stream->clearCallback());
+ releasePatchIfNeeded();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ CompressedOffloadOutputStream, CompressedOffloadOutputStreamTest,
+ ::testing::ValuesIn(getOutputDeviceCompressedConfigParameters(AudioConfigBase{
+ .format = xsd::toString(xsd::AudioFormat::AUDIO_FORMAT_MP3),
+ .sampleRateHz = 44100,
+ .channelMask = xsd::toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO)})),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CompressedOffloadOutputStreamTest);
diff --git a/audio/core/all-versions/vts/functional/7.0/Generators.cpp b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
index f936d0a..8b955b6 100644
--- a/audio/core/all-versions/vts/functional/7.0/Generators.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
@@ -57,9 +57,6 @@
static std::tuple<std::vector<AudioInOutFlag>, bool> generateOutFlags(
const xsd::MixPorts::MixPort& mixPort) {
- static const std::vector<AudioInOutFlag> offloadFlags = {
- toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
- toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
std::vector<AudioInOutFlag> flags;
bool isOffload = false;
if (mixPort.hasFlags()) {
@@ -67,14 +64,10 @@
isOffload = std::find(xsdFlags.begin(), xsdFlags.end(),
xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
xsdFlags.end();
- if (!isOffload) {
- for (auto flag : xsdFlags) {
- if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
- flags.push_back(toString(flag));
- }
+ for (auto flag : xsdFlags) {
+ if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
+ flags.push_back(toString(flag));
}
- } else {
- flags = offloadFlags;
}
}
return {flags, isOffload};
@@ -85,10 +78,10 @@
.base = base,
.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
- .bitRatePerSecond = 320,
+ .bitRatePerSecond = 192, // as in sine882hz3s.mp3
.durationMicroseconds = -1,
.bitWidth = 16,
- .bufferSize = 256 // arbitrary value
+ .bufferSize = 72000 // 3 seconds at 192 kbps, as in sine882hz3s.mp3
};
}
@@ -100,11 +93,10 @@
if (!module || !module->getFirstMixPorts()) break;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile
- if (getCachedPolicyConfig()
- .getAttachedSinkDeviceForMixPort(moduleName, mixPort.getName())
- .empty()) {
- continue; // no attached device
- }
+ const auto attachedDeviceAddress =
+ getCachedPolicyConfig().getDeviceAddressOfSinkDeviceAttachedToMixPort(
+ moduleName, mixPort.getName());
+ if (!attachedDeviceAddress.has_value()) continue;
auto [flags, isOffload] = generateOutFlags(mixPort);
for (const auto& profile : mixPort.getProfile()) {
if (!profile.hasFormat() || !profile.hasSamplingRates() ||
@@ -118,7 +110,8 @@
if (isOffload) {
config.offloadInfo.info(generateOffloadInfo(config.base));
}
- result.emplace_back(device, mixPort.getName(), config, flags);
+ result.emplace_back(device, mixPort.getName(), attachedDeviceAddress.value(),
+ config, flags);
if (oneProfilePerDevice) break;
}
if (oneProfilePerDevice) break;
@@ -162,13 +155,16 @@
profile.getFormat(),
static_cast<uint32_t>(profile.getSamplingRates()[0]),
toString(profile.getChannelMasks()[0])};
+ DeviceAddress defaultDevice = {
+ toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT), {}};
{
AudioConfig config{.base = validBase};
config.base.channelMask = "random_string";
if (isOffload) {
config.offloadInfo.info(generateOffloadInfo(validBase));
}
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
{
AudioConfig config{.base = validBase};
@@ -176,7 +172,8 @@
if (isOffload) {
config.offloadInfo.info(generateOffloadInfo(validBase));
}
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
if (generateInvalidFlags) {
AudioConfig config{.base = validBase};
@@ -184,32 +181,37 @@
config.offloadInfo.info(generateOffloadInfo(validBase));
}
std::vector<AudioInOutFlag> flags = {"random_string", ""};
- result.emplace_back(device, mixPort.getName(), config, flags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ flags);
}
if (isOffload) {
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().base.channelMask = "random_string";
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().base.format = "random_string";
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().streamType = "random_string";
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
{
AudioConfig config{.base = validBase};
config.offloadInfo.info(generateOffloadInfo(validBase));
config.offloadInfo.info().usage = "random_string";
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
hasOffloadConfig = true;
} else {
@@ -233,11 +235,10 @@
if (!module || !module->getFirstMixPorts()) break;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
- if (getCachedPolicyConfig()
- .getAttachedSourceDeviceForMixPort(moduleName, mixPort.getName())
- .empty()) {
- continue; // no attached device
- }
+ const auto attachedDeviceAddress =
+ getCachedPolicyConfig().getDeviceAddressOfSourceDeviceAttachedToMixPort(
+ moduleName, mixPort.getName());
+ if (!attachedDeviceAddress.has_value()) continue;
std::vector<AudioInOutFlag> flags;
if (mixPort.hasFlags()) {
std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
@@ -250,7 +251,8 @@
auto configs = combineAudioConfig(profile.getChannelMasks(),
profile.getSamplingRates(), profile.getFormat());
for (const auto& config : configs) {
- result.emplace_back(device, mixPort.getName(), config, flags);
+ result.emplace_back(device, mixPort.getName(), attachedDeviceAddress.value(),
+ config, flags);
if (oneProfilePerDevice) break;
}
if (oneProfilePerDevice) break;
@@ -298,20 +300,25 @@
profile.getFormat(),
static_cast<uint32_t>(profile.getSamplingRates()[0]),
toString(profile.getChannelMasks()[0])};
+ DeviceAddress defaultDevice = {
+ toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT), {}};
{
AudioConfig config{.base = validBase};
config.base.channelMask = "random_string";
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
{
AudioConfig config{.base = validBase};
config.base.format = "random_string";
- result.emplace_back(device, mixPort.getName(), config, validFlags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ validFlags);
}
if (generateInvalidFlags) {
AudioConfig config{.base = validBase};
std::vector<AudioInOutFlag> flags = {"random_string", ""};
- result.emplace_back(device, mixPort.getName(), config, flags);
+ result.emplace_back(device, mixPort.getName(), defaultDevice, config,
+ flags);
}
hasConfig = true;
break;
diff --git a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
index 4aea503..c1d5669 100644
--- a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
+++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
@@ -61,6 +61,18 @@
const std::set<std::string>& getModulesWithDevicesNames() const {
return mModulesWithDevicesNames;
}
+ std::optional<DeviceAddress> getDeviceAddressOfSinkDeviceAttachedToMixPort(
+ const std::string& moduleName, const std::string& mixPortName) const {
+ const auto attachedDevicePort = getAttachedSinkDeviceForMixPort(moduleName, mixPortName);
+ if (attachedDevicePort.empty()) return {};
+ return getDeviceAddressOfDevicePort(moduleName, attachedDevicePort);
+ }
+ std::optional<DeviceAddress> getDeviceAddressOfSourceDeviceAttachedToMixPort(
+ const std::string& moduleName, const std::string& mixPortName) const {
+ const auto attachedDevicePort = getAttachedSourceDeviceForMixPort(moduleName, mixPortName);
+ if (attachedDevicePort.empty()) return {};
+ return getDeviceAddressOfDevicePort(moduleName, attachedDevicePort);
+ }
std::string getAttachedSinkDeviceForMixPort(const std::string& moduleName,
const std::string& mixPortName) const {
return findAttachedDevice(getAttachedDevices(moduleName),
@@ -84,8 +96,6 @@
const std::vector<std::string>& getAttachedDevices(const std::string& moduleName) const;
std::optional<DeviceAddress> getDeviceAddressOfDevicePort(
const std::string& moduleName, const std::string& devicePortName) const;
- std::string getDevicePortTagNameFromType(const std::string& moduleName,
- const AudioDevice& deviceType) const;
std::set<std::string> getSinkDevicesForMixPort(const std::string& moduleName,
const std::string& mixPortName) const;
std::set<std::string> getSourceDevicesForMixPort(const std::string& moduleName,
diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp
index c757032..f51a8d0 100644
--- a/audio/core/all-versions/vts/functional/Android.bp
+++ b/audio/core/all-versions/vts/functional/Android.bp
@@ -34,6 +34,7 @@
],
shared_libs: [
"libbinder",
+ "libcutils",
"libfmq",
"libxml2",
],
@@ -48,7 +49,10 @@
cc_test {
name: "VtsHalAudioV2_0TargetTest",
- defaults: ["VtsHalAudioTargetTest_defaults"],
+ defaults: [
+ "VtsHalAudioTargetTest_defaults",
+ "latest_android_media_audio_common_types_cpp_static",
+ ],
tidy_timeout_srcs: [
"2.0/AudioPrimaryHidlHalTest.cpp",
],
@@ -61,7 +65,6 @@
"libmedia_helper",
"android.hardware.audio@2.0",
"android.hardware.audio.common@2.0",
- "android.media.audio.common.types-V1-cpp",
],
cflags: [
"-DMAJOR_VERSION=2",
@@ -78,7 +81,10 @@
cc_test {
name: "VtsHalAudioV4_0TargetTest",
- defaults: ["VtsHalAudioTargetTest_defaults"],
+ defaults: [
+ "VtsHalAudioTargetTest_defaults",
+ "latest_android_media_audio_common_types_cpp_static",
+ ],
tidy_timeout_srcs: [
"4.0/AudioPrimaryHidlHalTest.cpp",
],
@@ -91,7 +97,6 @@
"libmedia_helper",
"android.hardware.audio@4.0",
"android.hardware.audio.common@4.0",
- "android.media.audio.common.types-V1-cpp",
],
cflags: [
"-DMAJOR_VERSION=4",
@@ -108,7 +113,10 @@
cc_test {
name: "VtsHalAudioV5_0TargetTest",
- defaults: ["VtsHalAudioTargetTest_defaults"],
+ defaults: [
+ "VtsHalAudioTargetTest_defaults",
+ "latest_android_media_audio_common_types_cpp_static",
+ ],
srcs: [
"5.0/AudioPrimaryHidlHalTest.cpp",
],
@@ -118,7 +126,6 @@
"libmedia_helper",
"android.hardware.audio@5.0",
"android.hardware.audio.common@5.0",
- "android.media.audio.common.types-V1-cpp",
],
cflags: [
"-DMAJOR_VERSION=5",
@@ -135,7 +142,10 @@
cc_test {
name: "VtsHalAudioV6_0TargetTest",
- defaults: ["VtsHalAudioTargetTest_defaults"],
+ defaults: [
+ "VtsHalAudioTargetTest_defaults",
+ "latest_android_media_audio_common_types_cpp_static",
+ ],
tidy_timeout_srcs: [
"6.0/AudioPrimaryHidlHalTest.cpp",
],
@@ -149,7 +159,6 @@
"libmedia_helper",
"android.hardware.audio@6.0",
"android.hardware.audio.common@6.0",
- "android.media.audio.common.types-V1-cpp",
],
cflags: [
"-DMAJOR_VERSION=6",
@@ -190,6 +199,7 @@
],
data: [
":audio_policy_configuration_V7_0",
+ "data/sine882hz3s.mp3",
],
// Use test_config for vts suite.
// TODO(b/146104851): Add auto-gen rules and remove it.
@@ -223,6 +233,7 @@
],
data: [
":audio_policy_configuration_V7_1",
+ "data/sine882hz3s.mp3",
],
// Use test_config for vts suite.
// TODO(b/146104851): Add auto-gen rules and remove it.
@@ -241,7 +252,10 @@
cc_test {
name: "HalAudioV6_0GeneratorTest",
- defaults: ["VtsHalAudioTargetTest_defaults"],
+ defaults: [
+ "VtsHalAudioTargetTest_defaults",
+ "latest_android_media_audio_common_types_cpp_static",
+ ],
srcs: [
"6.0/Generators.cpp",
"tests/generators_tests.cpp",
@@ -249,7 +263,6 @@
static_libs: [
"android.hardware.audio@6.0",
"android.hardware.audio.common@6.0",
- "android.media.audio.common.types-V1-cpp",
"libaudiofoundation",
"libaudiopolicycomponents",
"libmedia_helper",
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 38e9e5f..e46e5b4 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -617,7 +617,8 @@
std::get<PARAM_FLAGS>(info.param)));
#elif MAJOR_VERSION >= 7
const auto configPart =
- std::to_string(config.base.sampleRateHz) + "_" +
+ ::testing::PrintToString(std::get<PARAM_ATTACHED_DEV_ADDR>(info.param).deviceType) +
+ "_" + std::to_string(config.base.sampleRateHz) + "_" +
// The channel masks and flags are vectors of strings, just need to sanitize them.
SanitizeStringForGTestName(::testing::PrintToString(config.base.channelMask)) + "_" +
SanitizeStringForGTestName(::testing::PrintToString(std::get<PARAM_FLAGS>(info.param)));
@@ -658,6 +659,9 @@
std::get<INDEX_OUTPUT>(std::get<PARAM_FLAGS>(GetParam())));
}
#elif MAJOR_VERSION >= 7
+ DeviceAddress getAttachedDeviceAddress() const {
+ return std::get<PARAM_ATTACHED_DEV_ADDR>(GetParam());
+ }
hidl_vec<AudioInOutFlag> getInputFlags() const { return std::get<PARAM_FLAGS>(GetParam()); }
hidl_vec<AudioInOutFlag> getOutputFlags() const { return std::get<PARAM_FLAGS>(GetParam()); }
#endif
@@ -933,6 +937,15 @@
StreamWriter(IStreamOut* stream, size_t bufferSize)
: mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {}
+ StreamWriter(IStreamOut* stream, size_t bufferSize, std::vector<uint8_t>&& data,
+ std::function<void()> onDataStart, std::function<bool()> onDataWrap)
+ : mStream(stream),
+ mBufferSize(bufferSize),
+ mData(std::move(data)),
+ mOnDataStart(onDataStart),
+ mOnDataWrap(onDataWrap) {
+ ALOGI("StreamWriter data size: %d", (int)mData.size());
+ }
~StreamWriter() {
stop();
if (mEfGroup) {
@@ -998,9 +1011,12 @@
ALOGE("command message queue write failed");
return false;
}
- const size_t dataSize = std::min(mData.size(), mDataMQ->availableToWrite());
- bool success = mDataMQ->write(mData.data(), dataSize);
+ if (mDataPosition == 0) mOnDataStart();
+ const size_t dataSize = std::min(mData.size() - mDataPosition, mDataMQ->availableToWrite());
+ bool success = mDataMQ->write(mData.data() + mDataPosition, dataSize);
ALOGE_IF(!success, "data message queue write failed");
+ mDataPosition += dataSize;
+ if (mDataPosition >= mData.size()) mDataPosition = 0;
mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
uint32_t efState = 0;
@@ -1026,6 +1042,9 @@
ALOGE("bad wait status: %d", ret);
success = false;
}
+ if (success && mDataPosition == 0) {
+ success = mOnDataWrap();
+ }
return success;
}
@@ -1033,6 +1052,9 @@
IStreamOut* const mStream;
const size_t mBufferSize;
std::vector<uint8_t> mData;
+ std::function<void()> mOnDataStart = []() {};
+ std::function<bool()> mOnDataWrap = []() { return true; };
+ size_t mDataPosition = 0;
std::unique_ptr<CommandMQ> mCommandMQ;
std::unique_ptr<DataMQ> mDataMQ;
std::unique_ptr<StatusMQ> mStatusMQ;
@@ -1047,7 +1069,7 @@
#if MAJOR_VERSION <= 6
address.device = AudioDevice::OUT_DEFAULT;
#elif MAJOR_VERSION >= 7
- address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT);
+ address = getAttachedDeviceAddress();
#endif
const AudioConfig& config = getConfig();
auto flags = getOutputFlags();
@@ -1243,16 +1265,11 @@
#if MAJOR_VERSION <= 6
address.device = AudioDevice::IN_DEFAULT;
#elif MAJOR_VERSION >= 7
- auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort(
- getDeviceName(), getMixPortName());
+ address = getAttachedDeviceAddress();
auto& metadata = initMetadata.tracks[0];
- if (maybeSourceAddress.has_value() &&
- !xsd::isTelephonyDevice(maybeSourceAddress.value().deviceType)) {
- address = maybeSourceAddress.value();
+ if (!xsd::isTelephonyDevice(address.deviceType)) {
metadata.source = toString(xsd::AudioSource::AUDIO_SOURCE_UNPROCESSED);
metadata.channelMask = getConfig().base.channelMask;
- } else {
- address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT);
}
#if MAJOR_VERSION == 7 && MINOR_VERSION >= 1
auto flagsIt = std::find(flags.begin(), flags.end(),
diff --git a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
index 802b87b..3de06c3 100644
--- a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
+++ b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
@@ -39,9 +39,10 @@
std::variant<android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::AudioInputFlag,
android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::AudioOutputFlag>>;
#elif MAJOR_VERSION >= 7
-enum { PARAM_DEVICE, PARAM_PORT_NAME, PARAM_CONFIG, PARAM_FLAGS };
+enum { PARAM_DEVICE, PARAM_PORT_NAME, PARAM_ATTACHED_DEV_ADDR, PARAM_CONFIG, PARAM_FLAGS };
using DeviceConfigParameter =
std::tuple<DeviceParameter, std::string,
+ android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::DeviceAddress,
android::hardware::audio::common::COMMON_TYPES_CPP_VERSION::AudioConfig,
std::vector<android::hardware::audio::CORE_TYPES_CPP_VERSION::AudioInOutFlag>>;
#endif
diff --git a/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml b/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml
index f0e2695..8da5744 100644
--- a/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml
+++ b/audio/core/all-versions/vts/functional/VtsHalAudioV7_0TargetTest.xml
@@ -29,6 +29,7 @@
<option name="cleanup" value="true" />
<option name="push" value="VtsHalAudioV7_0TargetTest->/data/local/tmp/VtsHalAudioV7_0TargetTest" />
<option name="push" value="audio_policy_configuration_V7_0.xsd->/data/local/tmp/audio_policy_configuration_V7_0.xsd" />
+ <option name="push" value="sine882hz3s.mp3->/data/local/tmp/sine882hz3s.mp3" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
diff --git a/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml b/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml
index 7ce1477..227df18 100644
--- a/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml
+++ b/audio/core/all-versions/vts/functional/VtsHalAudioV7_1TargetTest.xml
@@ -29,6 +29,8 @@
<option name="cleanup" value="true" />
<option name="push" value="VtsHalAudioV7_1TargetTest->/data/local/tmp/VtsHalAudioV7_1TargetTest" />
<option name="push" value="audio_policy_configuration_V7_1.xsd->/data/local/tmp/audio_policy_configuration_V7_1.xsd" />
+ <option name="push" value="sine882hz3s.mp3->/data/local/tmp/sine882hz3s.mp3" />
+
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
diff --git a/audio/core/all-versions/vts/functional/data/sine882hz3s.mp3 b/audio/core/all-versions/vts/functional/data/sine882hz3s.mp3
new file mode 100644
index 0000000..0604f9b
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/data/sine882hz3s.mp3
Binary files differ
diff --git a/audio/policy/1.0/xml/api/current.txt b/audio/policy/1.0/xml/api/current.txt
index 0b77d45..84a2b71 100644
--- a/audio/policy/1.0/xml/api/current.txt
+++ b/audio/policy/1.0/xml/api/current.txt
@@ -217,6 +217,10 @@
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_GAME;
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_MEDIA;
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION;
+ enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
+ enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
+ enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
+ enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_EVENT;
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE;
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_UNKNOWN;
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_VIRTUAL_SOURCE;
diff --git a/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd b/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd
index 3ce12e7..b58a6c8 100644
--- a/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd
+++ b/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd
@@ -347,6 +347,11 @@
<xs:enumeration value="AUDIO_USAGE_ALARM"/>
<xs:enumeration value="AUDIO_USAGE_NOTIFICATION"/>
<xs:enumeration value="AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE"/>
+ <!-- Note: the following 3 values were deprecated in Android T (13) SDK -->
+ <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST"/>
+ <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT"/>
+ <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED"/>
+ <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_EVENT"/>
<xs:enumeration value="AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY"/>
<xs:enumeration value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"/>
<xs:enumeration value="AUDIO_USAGE_ASSISTANCE_SONIFICATION"/>
diff --git a/automotive/audiocontrol/aidl/Android.bp b/automotive/audiocontrol/aidl/Android.bp
index e5f7a4f..03dab08 100644
--- a/automotive/audiocontrol/aidl/Android.bp
+++ b/automotive/audiocontrol/aidl/Android.bp
@@ -15,7 +15,7 @@
srcs: ["android/hardware/automotive/audiocontrol/*.aidl"],
imports: [
"android.hardware.audio.common-V1",
- "android.media.audio.common.types-V1",
+ "android.media.audio.common.types-V2",
],
stability: "vintf",
backend: {
@@ -33,14 +33,14 @@
version: "1",
imports: [
"android.hardware.audio.common-V1",
- "android.media.audio.common.types-V1",
+ "android.media.audio.common.types-V2",
],
},
{
version: "2",
imports: [
"android.hardware.audio.common-V1",
- "android.media.audio.common.types-V1",
+ "android.media.audio.common.types-V2",
],
},
diff --git a/automotive/audiocontrol/aidl/vts/Android.bp b/automotive/audiocontrol/aidl/vts/Android.bp
index 3d4be48..edac160 100644
--- a/automotive/audiocontrol/aidl/vts/Android.bp
+++ b/automotive/audiocontrol/aidl/vts/Android.bp
@@ -24,6 +24,8 @@
cc_test {
name: "VtsAidlHalAudioControlTest",
defaults: [
+ "latest_android_media_audio_common_types_cpp_static",
+ "latest_android_hardware_audio_common_cpp_static",
"VtsHalTargetTestDefaults",
"use_libaidlvintf_gtest_helper_static",
],
@@ -38,8 +40,6 @@
],
static_libs: [
"android.hardware.automotive.audiocontrol-V2-cpp",
- "android.hardware.audio.common-V1-cpp",
- "android.media.audio.common.types-V1-cpp",
"libgmock",
],
test_suites: [
diff --git a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
index 6ce9a7d..18a3329 100644
--- a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
+++ b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp
@@ -96,14 +96,6 @@
} RawStreamConfig;
constexpr const size_t kStreamCfgSz = sizeof(RawStreamConfig) / sizeof(int32_t);
-const std::unordered_set<int32_t> gSupportedColorFormats ({
- HAL_PIXEL_FORMAT_RGBA_8888,
- HAL_PIXEL_FORMAT_BGRA_8888,
- HAL_PIXEL_FORMAT_YCRCB_420_SP, // NV21
- HAL_PIXEL_FORMAT_YV12, // YV12
- HAL_PIXEL_FORMAT_YCBCR_422_I // YUY2
-});
-
} // anonymous namespace
@@ -258,8 +250,7 @@
// Stream configurations are found in metadata
RawStreamConfig *ptr = reinterpret_cast<RawStreamConfig *>(streamCfgs.data.i32);
for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) {
- if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
- isSupportedColorFormat(ptr->format)) {
+ if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) {
targetCfg.width = ptr->width;
targetCfg.height = ptr->height;
targetCfg.format = static_cast<PixelFormat>(ptr->format);
@@ -272,10 +263,6 @@
return targetCfg;
}
- bool isSupportedColorFormat(int32_t format) {
- return gSupportedColorFormats.find(format) != gSupportedColorFormats.end();
- }
-
sp<IEvsEnumerator> pEnumerator; // Every test needs access to the service
std::vector<CameraDesc> cameraInfo; // Empty unless/until loadCameraList() is called
bool mIsHwModule; // boolean to tell current module under testing
@@ -2029,9 +2016,7 @@
// Stream configurations are found in metadata
RawStreamConfig *ptr = reinterpret_cast<RawStreamConfig *>(streamCfgs.data.i32);
for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) {
- if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
- isSupportedColorFormat(ptr->format)) {
-
+ if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) {
if (ptr->width * ptr->height > maxArea &&
ptr->framerate >= minReqFps) {
targetCfg.width = ptr->width;
@@ -2131,9 +2116,7 @@
// Stream configurations are found in metadata
RawStreamConfig *ptr = reinterpret_cast<RawStreamConfig *>(streamCfgs.data.i32);
for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) {
- if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
- isSupportedColorFormat(ptr->format)) {
-
+ if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) {
if (ptr->width * ptr->height > maxArea &&
ptr->framerate >= minReqFps) {
targetCfg.width = ptr->width;
diff --git a/automotive/evs/aidl/vts/VtsHalEvsTargetTest.cpp b/automotive/evs/aidl/vts/VtsHalEvsTargetTest.cpp
index 9c6c573..3ea1eaa 100644
--- a/automotive/evs/aidl/vts/VtsHalEvsTargetTest.cpp
+++ b/automotive/evs/aidl/vts/VtsHalEvsTargetTest.cpp
@@ -231,8 +231,7 @@
// Stream configurations are found in metadata
RawStreamConfig* ptr = reinterpret_cast<RawStreamConfig*>(streamCfgs.data.i32);
for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) {
- if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
- ptr->format == HAL_PIXEL_FORMAT_RGBA_8888) {
+ if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) {
targetCfg.width = ptr->width;
targetCfg.height = ptr->height;
targetCfg.format = static_cast<PixelFormat>(ptr->format);
@@ -1732,11 +1731,11 @@
// Stream configurations are found in metadata
RawStreamConfig* ptr = reinterpret_cast<RawStreamConfig*>(streamCfgs.data.i32);
for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) {
- if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
- ptr->format == HAL_PIXEL_FORMAT_RGBA_8888) {
+ if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) {
if (ptr->width * ptr->height > maxArea && ptr->framerate >= minReqFps) {
targetCfg.width = ptr->width;
targetCfg.height = ptr->height;
+ targetCfg.format = static_cast<PixelFormat>(ptr->format);
maxArea = ptr->width * ptr->height;
foundCfg = true;
@@ -1745,7 +1744,6 @@
++ptr;
}
}
- targetCfg.format = static_cast<PixelFormat>(HAL_PIXEL_FORMAT_RGBA_8888);
if (!foundCfg) {
// Current EVS camera does not provide stream configurations in the
@@ -1829,11 +1827,11 @@
// Stream configurations are found in metadata
RawStreamConfig* ptr = reinterpret_cast<RawStreamConfig*>(streamCfgs.data.i32);
for (unsigned offset = 0; offset < streamCfgs.count; offset += kStreamCfgSz) {
- if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT &&
- ptr->format == HAL_PIXEL_FORMAT_RGBA_8888) {
+ if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT) {
if (ptr->width * ptr->height > maxArea && ptr->framerate >= minReqFps) {
targetCfg.width = ptr->width;
targetCfg.height = ptr->height;
+ targetCfg.format = static_cast<PixelFormat>(ptr->format);
maxArea = ptr->width * ptr->height;
foundCfg = true;
@@ -1842,7 +1840,6 @@
++ptr;
}
}
- targetCfg.format = static_cast<PixelFormat>(HAL_PIXEL_FORMAT_RGBA_8888);
if (!foundCfg) {
LOG(INFO) << "Device " << cam.id
diff --git a/bluetooth/1.0/Android.bp b/bluetooth/1.0/Android.bp
index 20775dd..bd1ca69 100644
--- a/bluetooth/1.0/Android.bp
+++ b/bluetooth/1.0/Android.bp
@@ -23,6 +23,6 @@
gen_java: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
}
diff --git a/bluetooth/1.1/Android.bp b/bluetooth/1.1/Android.bp
index 4ac2009..f8a05f1 100644
--- a/bluetooth/1.1/Android.bp
+++ b/bluetooth/1.1/Android.bp
@@ -23,6 +23,6 @@
gen_java: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
}
diff --git a/bluetooth/a2dp/1.0/Android.bp b/bluetooth/a2dp/1.0/Android.bp
index 20776dc..6ffbefa 100644
--- a/bluetooth/a2dp/1.0/Android.bp
+++ b/bluetooth/a2dp/1.0/Android.bp
@@ -23,6 +23,6 @@
gen_java: false,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
}
diff --git a/bluetooth/audio/2.0/Android.bp b/bluetooth/audio/2.0/Android.bp
index e4d48c1..725ec11 100644
--- a/bluetooth/audio/2.0/Android.bp
+++ b/bluetooth/audio/2.0/Android.bp
@@ -26,6 +26,6 @@
gen_java: false,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
}
diff --git a/bluetooth/audio/2.1/Android.bp b/bluetooth/audio/2.1/Android.bp
index 1175fb3..4ca0bef 100644
--- a/bluetooth/audio/2.1/Android.bp
+++ b/bluetooth/audio/2.1/Android.bp
@@ -25,7 +25,7 @@
],
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
gen_java: false,
}
diff --git a/bluetooth/audio/aidl/Android.bp b/bluetooth/audio/aidl/Android.bp
index a687162..4aea83f 100644
--- a/bluetooth/audio/aidl/Android.bp
+++ b/bluetooth/audio/aidl/Android.bp
@@ -42,7 +42,7 @@
ndk: {
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
min_sdk_version: "31",
},
diff --git a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp
index ebd728d..e9b74b7 100644
--- a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp
+++ b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp
@@ -23,6 +23,7 @@
#include <android/binder_process.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
+#include <cutils/properties.h>
#include <fmq/AidlMessageQueue.h>
#include <cstdint>
@@ -248,7 +249,8 @@
case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH:
case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH:
case SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH: {
- ASSERT_FALSE(temp_provider_capabilities_.empty());
+ // empty capability means offload is unsupported since capabilities are
+ // not hardcoded
for (auto audio_capability : temp_provider_capabilities_) {
ASSERT_EQ(audio_capability.getTag(),
AudioCapabilities::leAudioCapabilities);
@@ -513,8 +515,9 @@
for (auto channel_mode : opus_capability->channelMode) {
OpusConfiguration opus_data{
.samplingFrequencyHz = samplingFrequencyHz,
+ .frameDurationUs = frameDurationUs,
.channelMode = channel_mode,
- .frameDurationUs = frameDurationUs};
+ };
opus_codec_specifics.push_back(
CodecConfiguration::CodecSpecific(opus_data));
}
@@ -623,8 +626,8 @@
for (auto channel_mode : a2dp_channel_modes) {
PcmConfiguration pcm_config{
.sampleRateHz = sample_rate,
- .bitsPerSample = bits_per_sample,
.channelMode = channel_mode,
+ .bitsPerSample = bits_per_sample,
};
bool is_codec_config_valid = IsPcmConfigSupported(pcm_config);
DataMQDesc mq_desc;
@@ -937,8 +940,8 @@
for (auto channel_mode : hearing_aid_channel_modes_) {
PcmConfiguration pcm_config{
.sampleRateHz = sample_rate,
- .bitsPerSample = bits_per_sample,
.channelMode = channel_mode,
+ .bitsPerSample = bits_per_sample,
};
bool is_codec_config_valid = IsPcmConfigSupported(pcm_config);
DataMQDesc mq_desc;
@@ -1008,8 +1011,8 @@
for (auto data_interval_us : le_audio_output_data_interval_us_) {
PcmConfiguration pcm_config{
.sampleRateHz = sample_rate,
- .bitsPerSample = bits_per_sample,
.channelMode = channel_mode,
+ .bitsPerSample = bits_per_sample,
.dataIntervalUs = data_interval_us,
};
bool is_codec_config_valid =
@@ -1081,8 +1084,8 @@
for (auto data_interval_us : le_audio_input_data_interval_us_) {
PcmConfiguration pcm_config{
.sampleRateHz = sample_rate,
- .bitsPerSample = bits_per_sample,
.channelMode = channel_mode,
+ .bitsPerSample = bits_per_sample,
.dataIntervalUs = data_interval_us,
};
bool is_codec_config_valid =
@@ -1144,7 +1147,7 @@
bool supported) {
std::vector<Lc3Configuration> le_audio_codec_configs;
if (!supported) {
- Lc3Configuration lc3_config{.samplingFrequencyHz = 0, .pcmBitDepth = 0};
+ Lc3Configuration lc3_config{.pcmBitDepth = 0, .samplingFrequencyHz = 0};
le_audio_codec_configs.push_back(lc3_config);
return le_audio_codec_configs;
}
@@ -1428,8 +1431,8 @@
for (auto data_interval_us : le_audio_output_data_interval_us_) {
PcmConfiguration pcm_config{
.sampleRateHz = sample_rate,
- .bitsPerSample = bits_per_sample,
.channelMode = channel_mode,
+ .bitsPerSample = bits_per_sample,
.dataIntervalUs = data_interval_us,
};
bool is_codec_config_valid =
@@ -1490,7 +1493,7 @@
std::vector<Lc3Configuration> GetBroadcastLc3SupportedList(bool supported) {
std::vector<Lc3Configuration> le_audio_codec_configs;
if (!supported) {
- Lc3Configuration lc3_config{.samplingFrequencyHz = 0, .pcmBitDepth = 0};
+ Lc3Configuration lc3_config{.pcmBitDepth = 0, .samplingFrequencyHz = 0};
le_audio_codec_configs.push_back(lc3_config);
return le_audio_codec_configs;
}
@@ -1650,8 +1653,8 @@
for (auto channel_mode : a2dp_channel_modes) {
PcmConfiguration pcm_config{
.sampleRateHz = sample_rate,
- .bitsPerSample = bits_per_sample,
.channelMode = channel_mode,
+ .bitsPerSample = bits_per_sample,
};
bool is_codec_config_valid = IsPcmConfigSupported(pcm_config);
DataMQDesc mq_desc;
diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp
index d08cb0a..674dd11 100644
--- a/bluetooth/audio/utils/Android.bp
+++ b/bluetooth/audio/utils/Android.bp
@@ -40,9 +40,13 @@
"aidl_session/BluetoothAudioCodecs.cpp",
"aidl_session/BluetoothAudioSession.cpp",
"aidl_session/HidlToAidlMiddleware.cpp",
+ "aidl_session/BluetoothLeAudioCodecsProvider.cpp",
],
export_include_dirs: ["aidl_session/"],
- header_libs: ["libhardware_headers"],
+ header_libs: [
+ "libhardware_headers",
+ "libxsdc-utils",
+ ],
shared_libs: [
"android.hardware.bluetooth.audio@2.0",
"android.hardware.bluetooth.audio@2.1",
@@ -53,5 +57,15 @@
"liblog",
"android.hardware.bluetooth.audio-V2-ndk",
"libhidlbase",
+ "libxml2",
],
+ generated_sources: ["le_audio_codec_capabilities"],
+ generated_headers: ["le_audio_codec_capabilities"],
+}
+
+xsd_config {
+ name: "le_audio_codec_capabilities",
+ srcs: ["le_audio_codec_capabilities/le_audio_codec_capabilities.xsd"],
+ package_name: "aidl.android.hardware.bluetooth.audio.setting",
+ api_dir: "le_audio_codec_capabilities/schema",
}
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp b/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp
index 036d6cd..855dd28 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioCodecs.cpp
@@ -32,6 +32,8 @@
#include <aidl/android/hardware/bluetooth/audio/SbcChannelMode.h>
#include <android-base/logging.h>
+#include "BluetoothLeAudioCodecsProvider.h"
+
namespace aidl {
namespace android {
namespace hardware {
@@ -96,67 +98,6 @@
std::vector<LeAudioCodecCapabilitiesSetting> kDefaultOffloadLeAudioCapabilities;
-static const UnicastCapability kInvalidUnicastCapability = {
- .codecType = CodecType::UNKNOWN};
-
-static const BroadcastCapability kInvalidBroadcastCapability = {
- .codecType = CodecType::UNKNOWN};
-
-// Default Supported Codecs
-// LC3 16_1: sample rate: 16 kHz, frame duration: 7.5 ms, octets per frame: 30
-static const Lc3Capabilities kLc3Capability_16_1 = {
- .samplingFrequencyHz = {16000},
- .frameDurationUs = {7500},
- .octetsPerFrame = {30}};
-
-// Default Supported Codecs
-// LC3 16_2: sample rate: 16 kHz, frame duration: 10 ms, octets per frame: 40
-static const Lc3Capabilities kLc3Capability_16_2 = {
- .samplingFrequencyHz = {16000},
- .frameDurationUs = {10000},
- .octetsPerFrame = {40}};
-
-// Default Supported Codecs
-// LC3 24_2: sample rate: 24 kHz, frame duration: 10 ms, octets per frame: 60
-static const Lc3Capabilities kLc3Capability_24_2 = {
- .samplingFrequencyHz = {24000},
- .frameDurationUs = {10000},
- .octetsPerFrame = {60}};
-
-// Default Supported Codecs
-// LC3 32_2: sample rate: 32 kHz, frame duration: 10 ms, octets per frame: 80
-static const Lc3Capabilities kLc3Capability_32_2 = {
- .samplingFrequencyHz = {32000},
- .frameDurationUs = {10000},
- .octetsPerFrame = {80}};
-
-// Default Supported Codecs
-// LC3 48_4: sample rate: 48 kHz, frame duration: 10 ms, octets per frame: 120
-static const Lc3Capabilities kLc3Capability_48_4 = {
- .samplingFrequencyHz = {48000},
- .frameDurationUs = {10000},
- .octetsPerFrame = {120}};
-
-static const std::vector<Lc3Capabilities> supportedLc3CapabilityList = {
- kLc3Capability_48_4, kLc3Capability_32_2, kLc3Capability_24_2,
- kLc3Capability_16_2, kLc3Capability_16_1};
-
-static AudioLocation stereoAudio = static_cast<AudioLocation>(
- static_cast<uint8_t>(AudioLocation::FRONT_LEFT) |
- static_cast<uint8_t>(AudioLocation::FRONT_RIGHT));
-static AudioLocation monoAudio = AudioLocation::UNKNOWN;
-
-// Stores the supported setting of audio location, connected device, and the
-// channel count for each device
-std::vector<std::tuple<AudioLocation, uint8_t, uint8_t>>
- supportedDeviceSetting = {
- // Stereo, two connected device, one for L one for R
- std::make_tuple(stereoAudio, 2, 1),
- // Stereo, one connected device for both L and R
- std::make_tuple(stereoAudio, 1, 2),
- // Mono
- std::make_tuple(monoAudio, 1, 1)};
-
template <class T>
bool BluetoothAudioCodecs::ContainedInVector(
const std::vector<T>& vector, const typename identity<T>::type& target) {
@@ -444,19 +385,6 @@
return false;
}
-UnicastCapability composeUnicastLc3Capability(
- AudioLocation audioLocation, uint8_t deviceCnt, uint8_t channelCount,
- const Lc3Capabilities& capability) {
- return {
- .codecType = CodecType::LC3,
- .supportedChannel = audioLocation,
- .deviceCount = deviceCnt,
- .channelCountPerDevice = channelCount,
- .leAudioCodecCapabilities =
- UnicastCapability::LeAudioCodecCapabilities(capability),
- };
-}
-
std::vector<LeAudioCodecCapabilitiesSetting>
BluetoothAudioCodecs::GetLeAudioOffloadCodecCapabilities(
const SessionType& session_type) {
@@ -470,41 +398,8 @@
}
if (kDefaultOffloadLeAudioCapabilities.empty()) {
- for (auto [audioLocation, deviceCnt, channelCount] :
- supportedDeviceSetting) {
- for (auto capability : supportedLc3CapabilityList) {
- UnicastCapability lc3Capability = composeUnicastLc3Capability(
- audioLocation, deviceCnt, channelCount, capability);
- UnicastCapability lc3MonoDecodeCapability =
- composeUnicastLc3Capability(monoAudio, 1, 1, capability);
-
- // Adds the capability for encode only
- kDefaultOffloadLeAudioCapabilities.push_back(
- {.unicastEncodeCapability = lc3Capability,
- .unicastDecodeCapability = kInvalidUnicastCapability,
- .broadcastCapability = kInvalidBroadcastCapability});
-
- // Adds the capability for decode only
- kDefaultOffloadLeAudioCapabilities.push_back(
- {.unicastEncodeCapability = kInvalidUnicastCapability,
- .unicastDecodeCapability = lc3Capability,
- .broadcastCapability = kInvalidBroadcastCapability});
-
- // Adds the capability for the case that encode and decode exist at the
- // same time(force one active device for decode)
- kDefaultOffloadLeAudioCapabilities.push_back(
- {.unicastEncodeCapability = lc3Capability,
- .unicastDecodeCapability = lc3MonoDecodeCapability,
- .broadcastCapability = kInvalidBroadcastCapability});
-
- // Adds the capability for the case that encode and decode exist at the
- // same time
- kDefaultOffloadLeAudioCapabilities.push_back(
- {.unicastEncodeCapability = lc3Capability,
- .unicastDecodeCapability = lc3Capability,
- .broadcastCapability = kInvalidBroadcastCapability});
- }
- }
+ kDefaultOffloadLeAudioCapabilities =
+ BluetoothLeAudioCodecsProvider::GetLeAudioCodecCapabilities();
}
return kDefaultOffloadLeAudioCapabilities;
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp
index 292d352..2b0caad 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp
@@ -60,12 +60,14 @@
LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
<< " MqDescriptor Invalid";
audio_config_ = nullptr;
+ leaudio_connection_map_ = nullptr;
} else {
stack_iface_ = stack_iface;
latency_modes_ = latency_modes;
LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
<< ", AudioConfiguration=" << audio_config.toString();
ReportSessionStatus();
+ is_streaming_ = false;
}
}
@@ -74,11 +76,13 @@
bool toggled = IsSessionReady();
LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
audio_config_ = nullptr;
+ leaudio_connection_map_ = nullptr;
stack_iface_ = nullptr;
UpdateDataPath(nullptr);
if (toggled) {
ReportSessionStatus();
}
+ is_streaming_ = false;
}
/***
@@ -106,18 +110,72 @@
return *audio_config_;
}
+const AudioConfiguration BluetoothAudioSession::GetLeAudioConnectionMap() {
+ std::lock_guard<std::recursive_mutex> guard(mutex_);
+ if (!IsSessionReady()) {
+ return AudioConfiguration(LeAudioConfiguration{});
+ }
+ return *leaudio_connection_map_;
+}
+
void BluetoothAudioSession::ReportAudioConfigChanged(
const AudioConfiguration& audio_config) {
if (session_type_ !=
SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH &&
session_type_ !=
- SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH &&
- session_type_ !=
- SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) {
+ SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) {
return;
}
+
std::lock_guard<std::recursive_mutex> guard(mutex_);
- audio_config_ = std::make_unique<AudioConfiguration>(audio_config);
+ if (audio_config.getTag() != AudioConfiguration::leAudioConfig) {
+ LOG(ERROR) << __func__ << " invalid audio config type for SessionType ="
+ << toString(session_type_);
+ return;
+ }
+
+ if (is_streaming_) {
+ if (audio_config_ == nullptr) {
+ LOG(ERROR) << __func__ << " for SessionType=" << toString(session_type_)
+ << " audio_config_ is nullptr during streaming. It shouldn't "
+ "be happened";
+ return;
+ }
+
+ auto new_leaudio_config =
+ audio_config.get<AudioConfiguration::leAudioConfig>();
+ auto current_leaudio_config =
+ (*audio_config_).get<AudioConfiguration::leAudioConfig>();
+ if (new_leaudio_config.codecType != current_leaudio_config.codecType) {
+ LOG(ERROR)
+ << __func__ << " for SessionType=" << toString(session_type_)
+ << " codec type changed during streaming. It shouldn't be happened ";
+ }
+ auto new_lc3_config = new_leaudio_config.leAudioCodecConfig
+ .get<LeAudioCodecConfiguration::lc3Config>();
+ auto current_lc3_config = current_leaudio_config.leAudioCodecConfig
+ .get<LeAudioCodecConfiguration::lc3Config>();
+ if ((new_lc3_config.pcmBitDepth != current_lc3_config.pcmBitDepth) ||
+ (new_lc3_config.samplingFrequencyHz !=
+ current_lc3_config.samplingFrequencyHz) ||
+ (new_lc3_config.frameDurationUs !=
+ current_lc3_config.frameDurationUs) ||
+ (new_lc3_config.octetsPerFrame != current_lc3_config.octetsPerFrame) ||
+ (new_lc3_config.blocksPerSdu != current_lc3_config.blocksPerSdu)) {
+ LOG(ERROR)
+ << __func__ << " for SessionType=" << toString(session_type_)
+ << " lc3 config changed during streaming. It shouldn't be happened";
+ return;
+ }
+
+ leaudio_connection_map_ =
+ std::make_unique<AudioConfiguration>(audio_config);
+ } else {
+ audio_config_ = std::make_unique<AudioConfiguration>(audio_config);
+ leaudio_connection_map_ =
+ std::make_unique<AudioConfiguration>(audio_config);
+ }
+
if (observers_.empty()) {
LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
<< " has NO port state observer";
@@ -129,7 +187,11 @@
LOG(INFO) << __func__ << " for SessionType=" << toString(session_type_)
<< ", bluetooth_audio=0x"
<< ::android::base::StringPrintf("%04x", cookie);
- if (cb->audio_configuration_changed_cb_ != nullptr) {
+ if (is_streaming_) {
+ if (cb->soft_audio_configuration_changed_cb_ != nullptr) {
+ cb->soft_audio_configuration_changed_cb_(cookie);
+ }
+ } else if (cb->audio_configuration_changed_cb_ != nullptr) {
cb->audio_configuration_changed_cb_(cookie);
}
}
@@ -419,6 +481,12 @@
<< " has NO port state observer";
return;
}
+ if (start_resp && status == BluetoothAudioStatus::SUCCESS) {
+ is_streaming_ = true;
+ } else if (!start_resp && (status == BluetoothAudioStatus::SUCCESS ||
+ status == BluetoothAudioStatus::RECONFIGURATION)) {
+ is_streaming_ = false;
+ }
for (auto& observer : observers_) {
uint16_t cookie = observer.first;
std::shared_ptr<PortStatusCallbacks> callback = observer.second;
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h
index 5bf17bd..faf4ffb 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h
@@ -102,6 +102,13 @@
***/
std::function<void(uint16_t cookie, bool allowed)>
low_latency_mode_allowed_cb_;
+ /***
+ * soft_audio_configuration_changed_cb_ - when the Bluetooth stack change
+ * the streamMap during the streaming, the BluetoothAudioProvider will invoke
+ * this callback to report to the bluetooth_audio module.
+ * @param: cookie - indicates which bluetooth_audio output should handle
+ ***/
+ std::function<void(uint16_t cookie)> soft_audio_configuration_changed_cb_;
};
class BluetoothAudioSession {
@@ -159,6 +166,12 @@
const AudioConfiguration GetAudioConfig();
/***
+ * The control function is for the bluetooth_audio module to get the current
+ * LE audio connection map
+ ***/
+ const AudioConfiguration GetLeAudioConnectionMap();
+
+ /***
* The report function is used to report that the Bluetooth stack has notified
* the audio configuration changed, and will invoke
* audio_configuration_changed_cb_ to notify registered bluetooth_audio
@@ -206,8 +219,11 @@
std::unique_ptr<DataMQ> data_mq_;
// audio data configuration for both software and offloading
std::unique_ptr<AudioConfiguration> audio_config_;
+ std::unique_ptr<AudioConfiguration> leaudio_connection_map_;
std::vector<LatencyMode> latency_modes_;
bool low_latency_allowed_ = true;
+ // saving those steaming state based on the session_type
+ bool is_streaming_ = false;
// saving those registered bluetooth_audio's callbacks
std::unordered_map<uint16_t, std::shared_ptr<struct PortStatusCallbacks>>
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h
index 0782c82..881c6c1 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h
@@ -95,6 +95,25 @@
}
/***
+ * The control API for the bluetooth_audio module to get current
+ * LE audio connection map
+ ***/
+ static const AudioConfiguration GetLeAudioConnectionMap(
+ const SessionType& session_type) {
+ std::shared_ptr<BluetoothAudioSession> session_ptr =
+ BluetoothAudioSessionInstance::GetSessionInstance(session_type);
+ if ((session_type ==
+ SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH ||
+ session_type ==
+ SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) &&
+ session_ptr != nullptr) {
+ return session_ptr->GetLeAudioConnectionMap();
+ }
+
+ return AudioConfiguration(LeAudioConfiguration{});
+ }
+
+ /***
* Those control APIs for the bluetooth_audio module to start / suspend /
stop
* stream, to check position, and to update metadata.
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp
new file mode 100644
index 0000000..bf49270
--- /dev/null
+++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BTAudioCodecsProviderAidl"
+
+#include "BluetoothLeAudioCodecsProvider.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+
+static const char* kLeAudioCodecCapabilitiesFile =
+ "/vendor/etc/le_audio_codec_capabilities.xml";
+
+static const AudioLocation kStereoAudio = static_cast<AudioLocation>(
+ static_cast<uint8_t>(AudioLocation::FRONT_LEFT) |
+ static_cast<uint8_t>(AudioLocation::FRONT_RIGHT));
+static const AudioLocation kMonoAudio = AudioLocation::UNKNOWN;
+
+static std::vector<LeAudioCodecCapabilitiesSetting> leAudioCodecCapabilities;
+
+std::vector<LeAudioCodecCapabilitiesSetting>
+BluetoothLeAudioCodecsProvider::GetLeAudioCodecCapabilities() {
+ if (!leAudioCodecCapabilities.empty()) {
+ return leAudioCodecCapabilities;
+ }
+
+ const auto le_audio_offload_setting =
+ setting::readLeAudioOffloadSetting(kLeAudioCodecCapabilitiesFile);
+ if (!le_audio_offload_setting.has_value()) {
+ LOG(ERROR) << __func__ << ": Failed to read "
+ << kLeAudioCodecCapabilitiesFile;
+ return {};
+ }
+
+ std::vector<setting::Scenario> supported_scenarios =
+ GetScenarios(le_audio_offload_setting);
+ if (supported_scenarios.empty()) {
+ LOG(ERROR) << __func__ << ": No scenarios in "
+ << kLeAudioCodecCapabilitiesFile;
+ return {};
+ }
+
+ UpdateConfigurationsToMap(le_audio_offload_setting);
+ if (configuration_map_.empty()) {
+ LOG(ERROR) << __func__ << ": No configurations in "
+ << kLeAudioCodecCapabilitiesFile;
+ return {};
+ }
+
+ UpdateCodecConfigurationsToMap(le_audio_offload_setting);
+ if (codec_configuration_map_.empty()) {
+ LOG(ERROR) << __func__ << ": No codec configurations in "
+ << kLeAudioCodecCapabilitiesFile;
+ return {};
+ }
+
+ UpdateStrategyConfigurationsToMap(le_audio_offload_setting);
+ if (strategy_configuration_map_.empty()) {
+ LOG(ERROR) << __func__ << ": No strategy configurations in "
+ << kLeAudioCodecCapabilitiesFile;
+ return {};
+ }
+
+ leAudioCodecCapabilities =
+ ComposeLeAudioCodecCapabilities(supported_scenarios);
+ return leAudioCodecCapabilities;
+}
+
+std::vector<setting::Scenario> BluetoothLeAudioCodecsProvider::GetScenarios(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting) {
+ std::vector<setting::Scenario> supported_scenarios;
+ if (le_audio_offload_setting->hasScenarioList()) {
+ for (const auto& scenario_list :
+ le_audio_offload_setting->getScenarioList()) {
+ if (!scenario_list.hasScenario()) {
+ continue;
+ }
+ for (const auto& scenario : scenario_list.getScenario()) {
+ if (scenario.hasEncode() && scenario.hasDecode()) {
+ supported_scenarios.push_back(scenario);
+ }
+ }
+ }
+ }
+ return supported_scenarios;
+}
+
+void BluetoothLeAudioCodecsProvider::UpdateConfigurationsToMap(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting) {
+ if (le_audio_offload_setting->hasConfigurationList()) {
+ for (const auto& configuration_list :
+ le_audio_offload_setting->getConfigurationList()) {
+ if (!configuration_list.hasConfiguration()) {
+ continue;
+ }
+ for (const auto& configuration : configuration_list.getConfiguration()) {
+ if (configuration.hasName() && configuration.hasCodecConfiguration() &&
+ configuration.hasStrategyConfiguration()) {
+ configuration_map_.insert(
+ make_pair(configuration.getName(), configuration));
+ }
+ }
+ }
+ }
+}
+
+void BluetoothLeAudioCodecsProvider::UpdateCodecConfigurationsToMap(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting) {
+ if (le_audio_offload_setting->hasCodecConfigurationList()) {
+ for (const auto& codec_configuration_list :
+ le_audio_offload_setting->getCodecConfigurationList()) {
+ if (!codec_configuration_list.hasCodecConfiguration()) {
+ continue;
+ }
+ for (const auto& codec_configuration :
+ codec_configuration_list.getCodecConfiguration()) {
+ if (IsValidCodecConfiguration(codec_configuration)) {
+ codec_configuration_map_.insert(
+ make_pair(codec_configuration.getName(), codec_configuration));
+ }
+ }
+ }
+ }
+}
+
+void BluetoothLeAudioCodecsProvider::UpdateStrategyConfigurationsToMap(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting) {
+ if (le_audio_offload_setting->hasStrategyConfigurationList()) {
+ for (const auto& strategy_configuration_list :
+ le_audio_offload_setting->getStrategyConfigurationList()) {
+ if (!strategy_configuration_list.hasStrategyConfiguration()) {
+ continue;
+ }
+ for (const auto& strategy_configuration :
+ strategy_configuration_list.getStrategyConfiguration()) {
+ if (IsValidStrategyConfiguration(strategy_configuration)) {
+ strategy_configuration_map_.insert(make_pair(
+ strategy_configuration.getName(), strategy_configuration));
+ }
+ }
+ }
+ }
+}
+
+std::vector<LeAudioCodecCapabilitiesSetting>
+BluetoothLeAudioCodecsProvider::ComposeLeAudioCodecCapabilities(
+ const std::vector<setting::Scenario>& supported_scenarios) {
+ std::vector<LeAudioCodecCapabilitiesSetting> le_audio_codec_capabilities;
+ for (const auto& scenario : supported_scenarios) {
+ UnicastCapability unicast_encode_capability =
+ GetUnicastCapability(scenario.getEncode());
+ UnicastCapability unicast_decode_capability =
+ GetUnicastCapability(scenario.getDecode());
+ // encode and decode cannot be unknown at the same time
+ if (unicast_encode_capability.codecType == CodecType::UNKNOWN &&
+ unicast_decode_capability.codecType == CodecType::UNKNOWN) {
+ continue;
+ }
+ BroadcastCapability broadcast_capability = {.codecType =
+ CodecType::UNKNOWN};
+ le_audio_codec_capabilities.push_back(
+ {.unicastEncodeCapability = unicast_encode_capability,
+ .unicastDecodeCapability = unicast_decode_capability,
+ .broadcastCapability = broadcast_capability});
+ }
+ return le_audio_codec_capabilities;
+}
+
+UnicastCapability BluetoothLeAudioCodecsProvider::GetUnicastCapability(
+ const std::string& coding_direction) {
+ if (coding_direction == "invalid") {
+ return {.codecType = CodecType::UNKNOWN};
+ }
+
+ auto configuration_iter = configuration_map_.find(coding_direction);
+ if (configuration_iter == configuration_map_.end()) {
+ return {.codecType = CodecType::UNKNOWN};
+ }
+
+ auto codec_configuration_iter = codec_configuration_map_.find(
+ configuration_iter->second.getCodecConfiguration());
+ if (codec_configuration_iter == codec_configuration_map_.end()) {
+ return {.codecType = CodecType::UNKNOWN};
+ }
+
+ auto strategy_configuration_iter = strategy_configuration_map_.find(
+ configuration_iter->second.getStrategyConfiguration());
+ if (strategy_configuration_iter == strategy_configuration_map_.end()) {
+ return {.codecType = CodecType::UNKNOWN};
+ }
+
+ CodecType codec_type =
+ GetCodecType(codec_configuration_iter->second.getCodec());
+ if (codec_type == CodecType::LC3) {
+ return ComposeUnicastCapability(
+ codec_type,
+ GetAudioLocation(
+ strategy_configuration_iter->second.getAudioLocation()),
+ strategy_configuration_iter->second.getConnectedDevice(),
+ strategy_configuration_iter->second.getChannelCount(),
+ ComposeLc3Capability(codec_configuration_iter->second));
+ }
+ return {.codecType = CodecType::UNKNOWN};
+}
+
+template <class T>
+UnicastCapability BluetoothLeAudioCodecsProvider::ComposeUnicastCapability(
+ const CodecType& codec_type, const AudioLocation& audio_location,
+ const uint8_t& device_cnt, const uint8_t& channel_count,
+ const T& capability) {
+ return {
+ .codecType = codec_type,
+ .supportedChannel = audio_location,
+ .deviceCount = device_cnt,
+ .channelCountPerDevice = channel_count,
+ .leAudioCodecCapabilities =
+ UnicastCapability::LeAudioCodecCapabilities(capability),
+ };
+}
+
+Lc3Capabilities BluetoothLeAudioCodecsProvider::ComposeLc3Capability(
+ const setting::CodecConfiguration& codec_configuration) {
+ return {.samplingFrequencyHz = {codec_configuration.getSamplingFrequency()},
+ .frameDurationUs = {codec_configuration.getFrameDurationUs()},
+ .octetsPerFrame = {codec_configuration.getOctetsPerCodecFrame()}};
+}
+
+AudioLocation BluetoothLeAudioCodecsProvider::GetAudioLocation(
+ const setting::AudioLocation& audio_location) {
+ switch (audio_location) {
+ case setting::AudioLocation::MONO:
+ return kMonoAudio;
+ case setting::AudioLocation::STEREO:
+ return kStereoAudio;
+ default:
+ return AudioLocation::UNKNOWN;
+ }
+}
+
+CodecType BluetoothLeAudioCodecsProvider::GetCodecType(
+ const setting::CodecType& codec_type) {
+ switch (codec_type) {
+ case setting::CodecType::LC3:
+ return CodecType::LC3;
+ default:
+ return CodecType::UNKNOWN;
+ }
+}
+
+bool BluetoothLeAudioCodecsProvider::IsValidCodecConfiguration(
+ const setting::CodecConfiguration& codec_configuration) {
+ return codec_configuration.hasName() && codec_configuration.hasCodec() &&
+ codec_configuration.hasSamplingFrequency() &&
+ codec_configuration.hasFrameDurationUs() &&
+ codec_configuration.hasOctetsPerCodecFrame();
+}
+
+bool BluetoothLeAudioCodecsProvider::IsValidStrategyConfiguration(
+ const setting::StrategyConfiguration& strategy_configuration) {
+ if (!strategy_configuration.hasName() ||
+ !strategy_configuration.hasAudioLocation() ||
+ !strategy_configuration.hasConnectedDevice() ||
+ !strategy_configuration.hasChannelCount()) {
+ return false;
+ }
+ if (strategy_configuration.getAudioLocation() ==
+ setting::AudioLocation::STEREO) {
+ if ((strategy_configuration.getConnectedDevice() == 2 &&
+ strategy_configuration.getChannelCount() == 1) ||
+ (strategy_configuration.getConnectedDevice() == 1 &&
+ strategy_configuration.getChannelCount() == 2)) {
+ // Stereo
+ // 1. two connected device, one for L one for R
+ // 2. one connected device for both L and R
+ return true;
+ }
+ } else if (strategy_configuration.getAudioLocation() ==
+ setting::AudioLocation::MONO) {
+ if (strategy_configuration.getConnectedDevice() == 1 &&
+ strategy_configuration.getChannelCount() == 1) {
+ // Mono
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.h b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.h
new file mode 100644
index 0000000..402235f
--- /dev/null
+++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioCodecsProvider.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/bluetooth/audio/LeAudioCodecCapabilitiesSetting.h>
+#include <android-base/logging.h>
+
+#include <unordered_map>
+
+#include "aidl_android_hardware_bluetooth_audio_setting.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace bluetooth {
+namespace audio {
+
+class BluetoothLeAudioCodecsProvider {
+ public:
+ static std::vector<LeAudioCodecCapabilitiesSetting>
+ GetLeAudioCodecCapabilities();
+
+ private:
+ static inline std::unordered_map<std::string, setting::Configuration>
+ configuration_map_;
+ static inline std::unordered_map<std::string, setting::CodecConfiguration>
+ codec_configuration_map_;
+ static inline std::unordered_map<std::string, setting::StrategyConfiguration>
+ strategy_configuration_map_;
+
+ static std::vector<setting::Scenario> GetScenarios(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting);
+ static void UpdateConfigurationsToMap(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting);
+ static void UpdateCodecConfigurationsToMap(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting);
+ static void UpdateStrategyConfigurationsToMap(
+ const std::optional<setting::LeAudioOffloadSetting>&
+ le_audio_offload_setting);
+
+ static std::vector<LeAudioCodecCapabilitiesSetting>
+ ComposeLeAudioCodecCapabilities(
+ const std::vector<setting::Scenario>& supported_scenarios);
+
+ static UnicastCapability GetUnicastCapability(
+ const std::string& coding_direction);
+ template <class T>
+ static inline UnicastCapability ComposeUnicastCapability(
+ const CodecType& codec_type, const AudioLocation& audio_location,
+ const uint8_t& device_cnt, const uint8_t& channel_count,
+ const T& capability);
+
+ static inline Lc3Capabilities ComposeLc3Capability(
+ const setting::CodecConfiguration& codec_configuration);
+
+ static inline AudioLocation GetAudioLocation(
+ const setting::AudioLocation& audio_location);
+ static inline CodecType GetCodecType(const setting::CodecType& codec_type);
+
+ static inline bool IsValidCodecConfiguration(
+ const setting::CodecConfiguration& codec_configuration);
+ static inline bool IsValidStrategyConfiguration(
+ const setting::StrategyConfiguration& strategy_configuration);
+};
+
+} // namespace audio
+} // namespace bluetooth
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/bluetooth/audio/utils/le_audio_codec_capabilities/le_audio_codec_capabilities.xml b/bluetooth/audio/utils/le_audio_codec_capabilities/le_audio_codec_capabilities.xml
new file mode 100644
index 0000000..c7904b3
--- /dev/null
+++ b/bluetooth/audio/utils/le_audio_codec_capabilities/le_audio_codec_capabilities.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!---
+ This is an example to configure LE Audio hardware offload supported capability settings
+ In the follow list, there would be only one list in this file. Add element into each list as needed.
+
+ codecConfigurationList:
+ Supported codec capability along with its parameter setting
+
+ strategyConfigurationList:
+ ASE Configuration strategies
+
+ configurationList:
+ For each configuration , there are two attributes
+ - codecConfiguration
+ - strategyConfiguration
+
+ scenarioList:
+ There would be only one `scenarios` group
+ For each scenario, the are two attributes
+ - encode
+ - decode
+ If a scenario is unidirectional, mark another direction as `invalid`
+ The configuration should be chosen from `configurationList`
+-->
+<leAudioOffloadSetting>
+ <scenarioList>
+ <!-- encode only -->
+ <scenario encode="OneChanMono_16_1" decode="invalid"/>
+ <scenario encode="TwoChanStereo_16_1" decode="invalid"/>
+ <scenario encode="OneChanStereo_16_1" decode="invalid"/>
+ <scenario encode="OneChanMono_16_2" decode="invalid"/>
+ <scenario encode="TwoChanStereo_16_2" decode="invalid"/>
+ <scenario encode="OneChanStereo_16_2" decode="invalid"/>
+ <!-- encode and decode -->
+ <scenario encode="OneChanStereo_16_1" decode="OneChanStereo_16_1"/>
+ <scenario encode="OneChanStereo_16_1" decode="OneChanMono_16_1"/>
+ <scenario encode="TwoChanStereo_16_1" decode="OneChanMono_16_1"/>
+ <scenario encode="OneChanMono_16_1" decode="OneChanMono_16_1"/>
+ <scenario encode="OneChanStereo_16_2" decode="OneChanStereo_16_2"/>
+ <scenario encode="OneChanStereo_16_2" decode="OneChanMono_16_2"/>
+ <scenario encode="TwoChanStereo_16_2" decode="OneChanMono_16_2"/>
+ <scenario encode="OneChanMono_16_2" decode="OneChanMono_16_2"/>
+ </scenarioList>
+ <configurationList>
+ <configuration name="OneChanMono_16_1" codecConfiguration="LC3_16k_1" strategyConfiguration="MONO_ONE_CIS_PER_DEVICE"/>
+ <configuration name="TwoChanStereo_16_1" codecConfiguration="LC3_16k_1" strategyConfiguration="STEREO_TWO_CISES_PER_DEVICE"/>
+ <configuration name="OneChanStereo_16_1" codecConfiguration="LC3_16k_1" strategyConfiguration="STEREO_ONE_CIS_PER_DEVICE"/>
+ <configuration name="OneChanMono_16_2" codecConfiguration="LC3_16k_2" strategyConfiguration="MONO_ONE_CIS_PER_DEVICE"/>
+ <configuration name="TwoChanStereo_16_2" codecConfiguration="LC3_16k_2" strategyConfiguration="STEREO_TWO_CISES_PER_DEVICE"/>
+ <configuration name="OneChanStereo_16_2" codecConfiguration="LC3_16k_2" strategyConfiguration="STEREO_ONE_CIS_PER_DEVICE"/>
+ </configurationList>
+ <codecConfigurationList>
+ <codecConfiguration name="LC3_16k_1" codec="LC3" samplingFrequency="16000" frameDurationUs="7500" octetsPerCodecFrame="30"/>
+ <codecConfiguration name="LC3_16k_2" codec="LC3" samplingFrequency="16000" frameDurationUs="10000" octetsPerCodecFrame="40"/>
+ </codecConfigurationList>
+ <strategyConfigurationList>
+ <strategyConfiguration name="STEREO_ONE_CIS_PER_DEVICE" audioLocation="STEREO" connectedDevice="2" channelCount="1"/>
+ <strategyConfiguration name="STEREO_TWO_CISES_PER_DEVICE" audioLocation="STEREO" connectedDevice="1" channelCount="2"/>
+ <strategyConfiguration name="MONO_ONE_CIS_PER_DEVICE" audioLocation="MONO" connectedDevice="1" channelCount="1"/>
+ </strategyConfigurationList>
+</leAudioOffloadSetting>
diff --git a/bluetooth/audio/utils/le_audio_codec_capabilities/le_audio_codec_capabilities.xsd b/bluetooth/audio/utils/le_audio_codec_capabilities/le_audio_codec_capabilities.xsd
new file mode 100644
index 0000000..213e597
--- /dev/null
+++ b/bluetooth/audio/utils/le_audio_codec_capabilities/le_audio_codec_capabilities.xsd
@@ -0,0 +1,74 @@
+<!-- LE Audio Offload Codec Capability Schema -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:element name="leAudioOffloadSetting">
+ <xs:complexType>
+ <xs:element ref="scenarioList" minOccurs="1" maxOccurs="1"/>
+ <xs:element ref="configurationList" minOccurs="1" maxOccurs="1"/>
+ <xs:element ref="codecConfigurationList" minOccurs="1" maxOccurs="1"/>
+ <xs:element ref="strategyConfigurationList" minOccurs="1" maxOccurs="1"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="scenarioList">
+ <xs:complexType>
+ <xs:element ref="scenario" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="configurationList">
+ <xs:complexType>
+ <xs:element ref="configuration" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="codecConfigurationList">
+ <xs:complexType>
+ <xs:element ref="codecConfiguration" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="strategyConfigurationList">
+ <xs:complexType>
+ <xs:element ref="strategyConfiguration" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="scenario">
+ <xs:complexType>
+ <xs:attribute name="encode" type="xs:string"/>
+ <xs:attribute name="decode" type="xs:string"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="configuration">
+ <xs:complexType>
+ <xs:attribute name="name" type="xs:string"/>
+ <xs:attribute name="codecConfiguration" type="xs:string"/>
+ <xs:attribute name="strategyConfiguration" type="xs:string"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="codecConfiguration">
+ <xs:complexType>
+ <xs:attribute name="name" type="xs:string"/>
+ <xs:attribute name="codec" type="codecType"/>
+ <xs:attribute name="pcmBitDepth" type="xs:unsignedByte"/>
+ <xs:attribute name="samplingFrequency" type="xs:int"/>
+ <xs:attribute name="frameDurationUs" type="xs:int"/>
+ <xs:attribute name="octetsPerCodecFrame" type="xs:int"/>
+ <xs:attribute name="codecFrameBlocksPerSdu" type="xs:unsignedByte"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="strategyConfiguration">
+ <xs:complexType>
+ <xs:attribute name="name" type="xs:string"/>
+ <xs:attribute name="audioLocation" type="audioLocation"/>
+ <xs:attribute name="connectedDevice" type="xs:unsignedByte"/>
+ <xs:attribute name="channelCount" type="xs:unsignedByte"/>
+ </xs:complexType>
+ </xs:element>
+ <xs:simpleType name="audioLocation">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="MONO"/>
+ <xs:enumeration value="STEREO"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType name="codecType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="LC3"/>
+ </xs:restriction>
+ </xs:simpleType>
+</xs:schema>
diff --git a/bluetooth/audio/utils/le_audio_codec_capabilities/schema/current.txt b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/current.txt
new file mode 100644
index 0000000..06aa21a
--- /dev/null
+++ b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/current.txt
@@ -0,0 +1,111 @@
+// Signature format: 2.0
+package aidl.android.hardware.bluetooth.audio.setting {
+
+ public enum AudioLocation {
+ method public String getRawName();
+ enum_constant public static final aidl.android.hardware.bluetooth.audio.setting.AudioLocation MONO;
+ enum_constant public static final aidl.android.hardware.bluetooth.audio.setting.AudioLocation STEREO;
+ }
+
+ public class CodecConfiguration {
+ ctor public CodecConfiguration();
+ method public aidl.android.hardware.bluetooth.audio.setting.CodecType getCodec();
+ method public short getCodecFrameBlocksPerSdu();
+ method public int getFrameDurationUs();
+ method public String getName();
+ method public int getOctetsPerCodecFrame();
+ method public short getPcmBitDepth();
+ method public int getSamplingFrequency();
+ method public void setCodec(aidl.android.hardware.bluetooth.audio.setting.CodecType);
+ method public void setCodecFrameBlocksPerSdu(short);
+ method public void setFrameDurationUs(int);
+ method public void setName(String);
+ method public void setOctetsPerCodecFrame(int);
+ method public void setPcmBitDepth(short);
+ method public void setSamplingFrequency(int);
+ }
+
+ public class CodecConfigurationList {
+ ctor public CodecConfigurationList();
+ method public java.util.List<aidl.android.hardware.bluetooth.audio.setting.CodecConfiguration> getCodecConfiguration();
+ }
+
+ public enum CodecType {
+ method public String getRawName();
+ enum_constant public static final aidl.android.hardware.bluetooth.audio.setting.CodecType LC3;
+ }
+
+ public class Configuration {
+ ctor public Configuration();
+ method public String getCodecConfiguration();
+ method public String getName();
+ method public String getStrategyConfiguration();
+ method public void setCodecConfiguration(String);
+ method public void setName(String);
+ method public void setStrategyConfiguration(String);
+ }
+
+ public class ConfigurationList {
+ ctor public ConfigurationList();
+ method public java.util.List<aidl.android.hardware.bluetooth.audio.setting.Configuration> getConfiguration();
+ }
+
+ public class LeAudioOffloadSetting {
+ ctor public LeAudioOffloadSetting();
+ method public aidl.android.hardware.bluetooth.audio.setting.CodecConfigurationList getCodecConfigurationList();
+ method public aidl.android.hardware.bluetooth.audio.setting.ConfigurationList getConfigurationList();
+ method public aidl.android.hardware.bluetooth.audio.setting.ScenarioList getScenarioList();
+ method public aidl.android.hardware.bluetooth.audio.setting.StrategyConfigurationList getStrategyConfigurationList();
+ method public void setCodecConfigurationList(aidl.android.hardware.bluetooth.audio.setting.CodecConfigurationList);
+ method public void setConfigurationList(aidl.android.hardware.bluetooth.audio.setting.ConfigurationList);
+ method public void setScenarioList(aidl.android.hardware.bluetooth.audio.setting.ScenarioList);
+ method public void setStrategyConfigurationList(aidl.android.hardware.bluetooth.audio.setting.StrategyConfigurationList);
+ }
+
+ public class Scenario {
+ ctor public Scenario();
+ method public String getDecode();
+ method public String getEncode();
+ method public void setDecode(String);
+ method public void setEncode(String);
+ }
+
+ public class ScenarioList {
+ ctor public ScenarioList();
+ method public java.util.List<aidl.android.hardware.bluetooth.audio.setting.Scenario> getScenario();
+ }
+
+ public class StrategyConfiguration {
+ ctor public StrategyConfiguration();
+ method public aidl.android.hardware.bluetooth.audio.setting.AudioLocation getAudioLocation();
+ method public short getChannelCount();
+ method public short getConnectedDevice();
+ method public String getName();
+ method public void setAudioLocation(aidl.android.hardware.bluetooth.audio.setting.AudioLocation);
+ method public void setChannelCount(short);
+ method public void setConnectedDevice(short);
+ method public void setName(String);
+ }
+
+ public class StrategyConfigurationList {
+ ctor public StrategyConfigurationList();
+ method public java.util.List<aidl.android.hardware.bluetooth.audio.setting.StrategyConfiguration> getStrategyConfiguration();
+ }
+
+ public class XmlParser {
+ ctor public XmlParser();
+ method public static aidl.android.hardware.bluetooth.audio.setting.CodecConfiguration readCodecConfiguration(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.CodecConfigurationList readCodecConfigurationList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.Configuration readConfiguration(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.ConfigurationList readConfigurationList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.LeAudioOffloadSetting readLeAudioOffloadSetting(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.Scenario readScenario(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.ScenarioList readScenarioList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.StrategyConfiguration readStrategyConfiguration(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static aidl.android.hardware.bluetooth.audio.setting.StrategyConfigurationList readStrategyConfigurationList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ }
+
+}
+
diff --git a/bluetooth/audio/utils/le_audio_codec_capabilities/schema/last_current.txt b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/last_current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/last_current.txt
diff --git a/bluetooth/audio/utils/le_audio_codec_capabilities/schema/last_removed.txt b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/last_removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/last_removed.txt
diff --git a/bluetooth/audio/utils/le_audio_codec_capabilities/schema/removed.txt b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/bluetooth/audio/utils/le_audio_codec_capabilities/schema/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/boot/1.1/default/boot_control/Android.bp b/boot/1.1/default/boot_control/Android.bp
index ad71700..6aa30c2 100644
--- a/boot/1.1/default/boot_control/Android.bp
+++ b/boot/1.1/default/boot_control/Android.bp
@@ -25,8 +25,6 @@
cc_defaults {
name: "libboot_control_defaults",
- vendor: true,
- recovery_available: true,
relative_install_path: "hw",
cflags: [
@@ -42,7 +40,7 @@
"liblog",
],
static_libs: [
- "libbootloader_message_vendor",
+ "libbootloader_message",
"libfstab",
],
}
@@ -51,6 +49,8 @@
name: "libboot_control",
defaults: ["libboot_control_defaults"],
export_include_dirs: ["include"],
+ recovery_available: true,
+ vendor_available: true,
srcs: ["libboot_control.cpp"],
}
@@ -58,6 +58,8 @@
cc_library_shared {
name: "bootctrl.default",
defaults: ["libboot_control_defaults"],
+ recovery_available: true,
+ vendor_available: true,
srcs: ["legacy_boot_control.cpp"],
diff --git a/boot/aidl/default/Android.bp b/boot/aidl/default/Android.bp
index 6aefae8..dcb40db 100644
--- a/boot/aidl/default/Android.bp
+++ b/boot/aidl/default/Android.bp
@@ -23,13 +23,11 @@
default_applicable_licenses: ["hardware_interfaces_license"],
}
-cc_binary {
- name: "android.hardware.boot-service.default",
- defaults: ["libboot_control_defaults"],
+cc_defaults {
+ name: "android.hardware.boot-service_common",
relative_install_path: "hw",
- init_rc: ["boot-default.rc"],
- vintf_fragments: ["boot-default.xml"],
- vendor: true,
+ defaults: ["libboot_control_defaults"],
+ vintf_fragments: ["android.hardware.boot-service.default.xml"],
shared_libs: [
"libbase",
"libbinder_ndk",
@@ -41,3 +39,17 @@
],
srcs: ["main.cpp", "BootControl.cpp"],
}
+
+cc_binary {
+ name: "android.hardware.boot-service.default",
+ defaults: ["android.hardware.boot-service_common"],
+ init_rc: ["android.hardware.boot-service.default.rc"],
+ vendor: true,
+}
+
+cc_binary {
+ name: "android.hardware.boot-service.default_recovery",
+ defaults: ["android.hardware.boot-service_common"],
+ init_rc: ["android.hardware.boot-service.default_recovery.rc"],
+ recovery: true,
+}
diff --git a/boot/aidl/default/boot-default.rc b/boot/aidl/default/android.hardware.boot-service.default.rc
similarity index 100%
rename from boot/aidl/default/boot-default.rc
rename to boot/aidl/default/android.hardware.boot-service.default.rc
diff --git a/boot/aidl/default/boot-default.xml b/boot/aidl/default/android.hardware.boot-service.default.xml
similarity index 100%
rename from boot/aidl/default/boot-default.xml
rename to boot/aidl/default/android.hardware.boot-service.default.xml
diff --git a/boot/aidl/default/android.hardware.boot-service.default_recovery.rc b/boot/aidl/default/android.hardware.boot-service.default_recovery.rc
new file mode 100644
index 0000000..cb08a39
--- /dev/null
+++ b/boot/aidl/default/android.hardware.boot-service.default_recovery.rc
@@ -0,0 +1,7 @@
+service vendor.boot-default /system/bin/hw/android.hardware.boot-service.default_recovery
+ class early_hal
+ user root
+ group root
+ seclabel u:r:hal_bootctl_default:s0
+ interface aidl android.hardware.boot.IBootControl/default
+
diff --git a/camera/common/1.0/default/HandleImporter.cpp b/camera/common/1.0/default/HandleImporter.cpp
index fbe8686..d2fdf02 100644
--- a/camera/common/1.0/default/HandleImporter.cpp
+++ b/camera/common/1.0/default/HandleImporter.cpp
@@ -18,6 +18,7 @@
#include "HandleImporter.h"
#include <gralloctypes/Gralloc4.h>
+#include "aidl/android/hardware/graphics/common/Smpte2086.h"
#include <log/log.h>
namespace android {
@@ -30,6 +31,7 @@
using aidl::android::hardware::graphics::common::PlaneLayout;
using aidl::android::hardware::graphics::common::PlaneLayoutComponent;
using aidl::android::hardware::graphics::common::PlaneLayoutComponentType;
+using aidl::android::hardware::graphics::common::Smpte2086;
using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
using MapperErrorV2 = android::hardware::graphics::mapper::V2_0::Error;
using MapperErrorV3 = android::hardware::graphics::mapper::V3_0::Error;
@@ -127,16 +129,35 @@
bool isMetadataPesent(const sp<IMapperV4> mapper, const buffer_handle_t& buf,
MetadataType metadataType) {
auto buffer = const_cast<native_handle_t*>(buf);
- mapper->get(buffer, metadataType, [] (const auto& tmpError,
+ bool ret = false;
+ hidl_vec<uint8_t> vec;
+ mapper->get(buffer, metadataType, [&] (const auto& tmpError,
const auto& tmpMetadata) {
if (tmpError == MapperErrorV4::NONE) {
- return tmpMetadata.size() > 0;
+ vec = tmpMetadata;
} else {
ALOGE("%s: failed to get metadata %d!", __FUNCTION__, tmpError);
- return false;
}});
- return false;
+ if (vec.size() > 0) {
+ if (metadataType == gralloc4::MetadataType_Smpte2086){
+ std::optional<Smpte2086> realSmpte2086;
+ gralloc4::decodeSmpte2086(vec, &realSmpte2086);
+ ret = realSmpte2086.has_value();
+ } else if (metadataType == gralloc4::MetadataType_Smpte2094_10) {
+ std::optional<std::vector<uint8_t>> realSmpte2094_10;
+ gralloc4::decodeSmpte2094_10(vec, &realSmpte2094_10);
+ ret = realSmpte2094_10.has_value();
+ } else if (metadataType == gralloc4::MetadataType_Smpte2094_40) {
+ std::optional<std::vector<uint8_t>> realSmpte2094_40;
+ gralloc4::decodeSmpte2094_40(vec, &realSmpte2094_40);
+ ret = realSmpte2094_40.has_value();
+ } else {
+ ALOGE("%s: Unknown metadata type!", __FUNCTION__);
+ }
+ }
+
+ return ret;
}
std::vector<PlaneLayout> getPlaneLayouts(const sp<IMapperV4> mapper, buffer_handle_t& buf) {
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index 6866776..b0ae20e 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -7883,6 +7883,7 @@
}
}
+ camera_metadata_t* staticMetadata;
camera_metadata_ro_entry physicalMultiResStreamConfigs;
camera_metadata_ro_entry physicalStreamConfigs;
camera_metadata_ro_entry physicalMaxResolutionStreamConfigs;
@@ -7901,8 +7902,9 @@
ret = subDevice->getCameraCharacteristics([&](auto status, const auto& chars) {
ASSERT_EQ(Status::OK, status);
- const camera_metadata_t* staticMetadata =
- reinterpret_cast<const camera_metadata_t*>(chars.data());
+ staticMetadata = clone_camera_metadata(
+ reinterpret_cast<const camera_metadata_t*>(chars.data()));
+ ASSERT_NE(nullptr, staticMetadata);
rc = getSystemCameraKind(staticMetadata, &physSystemCameraKind);
ASSERT_EQ(rc, Status::OK);
// Make sure that the system camera kind of a non-hidden
@@ -7936,7 +7938,9 @@
verifyCameraCharacteristics(status, chars);
verifyMonochromeCharacteristics(chars, deviceVersion);
- auto staticMetadata = (const camera_metadata_t*)chars.data();
+ staticMetadata = clone_camera_metadata(
+ reinterpret_cast<const camera_metadata_t*>(chars.data()));
+ ASSERT_NE(nullptr, staticMetadata);
retcode = find_camera_metadata_ro_entry(
staticMetadata, ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry);
bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2);
@@ -8064,6 +8068,7 @@
}
}
}
+ free_camera_metadata(staticMetadata);
}
// If a multi-resolution stream is supported, there must be at least one
diff --git a/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp b/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp
index fe03732..70ab7a0 100644
--- a/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp
+++ b/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp
@@ -1739,6 +1739,10 @@
std::list<PixelFormat> pixelFormats = {PixelFormat::YCBCR_420_888, PixelFormat::RAW16};
for (PixelFormat format : pixelFormats) {
+ previewStream.usage =
+ static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
+ GRALLOC1_CONSUMER_USAGE_CPU_READ);
+ previewStream.dataSpace = Dataspace::UNKNOWN;
configureStreams(name, mProvider, format, &mSession, &previewStream, &halStreams,
&supportsPartialResults, &partialResultCount, &useHalBufManager, &cb,
0, /*maxResolution*/ true);
@@ -1843,7 +1847,6 @@
TEST_P(CameraAidlTest, process10BitDynamicRangeRequest) {
std::vector<std::string> cameraDeviceNames = getCameraDeviceNames(mProvider);
int64_t bufferId = 1;
- int32_t frameNumber = 1;
CameraMetadata settings;
for (const auto& name : cameraDeviceNames) {
@@ -1866,7 +1869,7 @@
CameraMetadata req;
android::hardware::camera::common::V1_0::helper::CameraMetadata defaultSettings;
ndk::ScopedAStatus ret =
- mSession->constructDefaultRequestSettings(RequestTemplate::STILL_CAPTURE, &req);
+ mSession->constructDefaultRequestSettings(RequestTemplate::PREVIEW, &req);
ASSERT_TRUE(ret.isOk());
const camera_metadata_t* metadata =
@@ -1896,6 +1899,10 @@
Stream previewStream;
std::shared_ptr<DeviceCb> cb;
for (const auto& profile : profileList) {
+ previewStream.usage =
+ static_cast<aidl::android::hardware::graphics::common::BufferUsage>(
+ GRALLOC1_CONSUMER_USAGE_HWCOMPOSER);
+ previewStream.dataSpace = getDataspace(PixelFormat::IMPLEMENTATION_DEFINED);
configureStreams(name, mProvider, PixelFormat::IMPLEMENTATION_DEFINED, &mSession,
&previewStream, &halStreams, &supportsPartialResults,
&partialResultCount, &useHalBufManager, &cb, 0,
@@ -1916,63 +1923,75 @@
// Don't use the queue onwards.
}
- std::vector<buffer_handle_t> graphicBuffers;
- graphicBuffers.reserve(halStreams.size());
+ mInflightMap.clear();
+ // Stream as long as needed to fill the Hal inflight queue
+ std::vector<CaptureRequest> requests(halStreams[0].maxBuffers);
- std::shared_ptr<InFlightRequest> inflightReq = std::make_shared<InFlightRequest>(
- static_cast<ssize_t>(halStreams.size()), false, supportsPartialResults,
- partialResultCount, std::unordered_set<std::string>(), resultQueue);
+ for (int32_t frameNumber = 0; frameNumber < requests.size(); frameNumber++) {
+ std::shared_ptr<InFlightRequest> inflightReq = std::make_shared<InFlightRequest>(
+ static_cast<ssize_t>(halStreams.size()), false, supportsPartialResults,
+ partialResultCount, std::unordered_set<std::string>(), resultQueue);
- std::vector<CaptureRequest> requests(1);
- CaptureRequest& request = requests[0];
- std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
- outputBuffers.resize(halStreams.size());
+ CaptureRequest& request = requests[frameNumber];
+ std::vector<StreamBuffer>& outputBuffers = request.outputBuffers;
+ outputBuffers.resize(halStreams.size());
- size_t k = 0;
- for (const auto& halStream : halStreams) {
- buffer_handle_t buffer_handle;
- if (useHalBufManager) {
- outputBuffers[k] = {halStream.id, 0,
- NativeHandle(), BufferStatus::OK,
- NativeHandle(), NativeHandle()};
- } else {
- allocateGraphicBuffer(previewStream.width, previewStream.height,
- android_convertGralloc1To0Usage(
- static_cast<uint64_t>(halStream.producerUsage),
- static_cast<uint64_t>(halStream.consumerUsage)),
- halStream.overrideFormat, &buffer_handle);
+ size_t k = 0;
+ inflightReq->mOutstandingBufferIds.resize(halStreams.size());
+ std::vector<buffer_handle_t> graphicBuffers;
+ graphicBuffers.reserve(halStreams.size());
- graphicBuffers.push_back(buffer_handle);
- outputBuffers[k] = {
- halStream.id, bufferId, android::makeToAidl(buffer_handle),
- BufferStatus::OK, NativeHandle(), NativeHandle()};
- bufferId++;
+ for (const auto& halStream : halStreams) {
+ buffer_handle_t buffer_handle;
+ if (useHalBufManager) {
+ outputBuffers[k] = {halStream.id, 0,
+ NativeHandle(), BufferStatus::OK,
+ NativeHandle(), NativeHandle()};
+ } else {
+ auto usage = android_convertGralloc1To0Usage(
+ static_cast<uint64_t>(halStream.producerUsage),
+ static_cast<uint64_t>(halStream.consumerUsage));
+ allocateGraphicBuffer(previewStream.width, previewStream.height, usage,
+ halStream.overrideFormat, &buffer_handle);
+
+ inflightReq->mOutstandingBufferIds[halStream.id][bufferId] = buffer_handle;
+ graphicBuffers.push_back(buffer_handle);
+ outputBuffers[k] = {halStream.id, bufferId,
+ android::makeToAidl(buffer_handle), BufferStatus::OK, NativeHandle(),
+ NativeHandle()};
+ bufferId++;
+ }
+ k++;
}
- k++;
- }
- request.inputBuffer = {
- -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
- request.frameNumber = frameNumber;
- request.fmqSettingsSize = 0;
- request.settings = settings;
- request.inputWidth = 0;
- request.inputHeight = 0;
+ request.inputBuffer = {
+ -1, 0, NativeHandle(), BufferStatus::ERROR, NativeHandle(), NativeHandle()};
+ request.frameNumber = frameNumber;
+ request.fmqSettingsSize = 0;
+ request.settings = settings;
+ request.inputWidth = 0;
+ request.inputHeight = 0;
- {
- std::unique_lock<std::mutex> l(mLock);
- mInflightMap.clear();
- mInflightMap[frameNumber] = inflightReq;
+ {
+ std::unique_lock<std::mutex> l(mLock);
+ mInflightMap[frameNumber] = inflightReq;
+ }
+
}
int32_t numRequestProcessed = 0;
std::vector<BufferCache> cachesToRemove;
ndk::ScopedAStatus returnStatus =
- mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
+ mSession->processCaptureRequest(requests, cachesToRemove, &numRequestProcessed);
ASSERT_TRUE(returnStatus.isOk());
- ASSERT_EQ(numRequestProcessed, 1u);
+ ASSERT_EQ(numRequestProcessed, requests.size());
- {
+ returnStatus = mSession->repeatingRequestEnd(requests.size() - 1,
+ std::vector<int32_t> {halStreams[0].id});
+ ASSERT_TRUE(returnStatus.isOk());
+
+ for (int32_t frameNumber = 0; frameNumber < requests.size(); frameNumber++) {
+ const auto& inflightReq = mInflightMap[frameNumber];
std::unique_lock<std::mutex> l(mLock);
while (!inflightReq->errorCodeValid &&
((0 < inflightReq->numBuffersLeft) || (!inflightReq->haveResultMetadata))) {
@@ -1985,6 +2004,7 @@
ASSERT_NE(inflightReq->resultOutputBuffers.size(), 0u);
verify10BitMetadata(mHandleImporter, *inflightReq, profile);
}
+
if (useHalBufManager) {
std::vector<int32_t> streamIds(halStreams.size());
for (size_t i = 0; i < streamIds.size(); i++) {
diff --git a/camera/provider/aidl/vts/camera_aidl_test.cpp b/camera/provider/aidl/vts/camera_aidl_test.cpp
index c11fc0c..2c2f1b2 100644
--- a/camera/provider/aidl/vts/camera_aidl_test.cpp
+++ b/camera/provider/aidl/vts/camera_aidl_test.cpp
@@ -1181,6 +1181,7 @@
camera_metadata_ro_entry physicalMultiResStreamConfigs;
camera_metadata_ro_entry physicalStreamConfigs;
camera_metadata_ro_entry physicalMaxResolutionStreamConfigs;
+ CameraMetadata physChars;
bool isUltraHighRes = false;
std::unordered_set<int32_t> subCameraPrivacyTestPatterns;
if (isPublicId) {
@@ -1189,12 +1190,11 @@
ASSERT_TRUE(ret.isOk());
ASSERT_NE(subDevice, nullptr);
- CameraMetadata subDeviceChars;
- ret = subDevice->getCameraCharacteristics(&subDeviceChars);
+ ret = subDevice->getCameraCharacteristics(&physChars);
ASSERT_TRUE(ret.isOk());
const camera_metadata_t* staticMetadata =
- reinterpret_cast<const camera_metadata_t*>(subDeviceChars.metadata.data());
+ reinterpret_cast<const camera_metadata_t*>(physChars.metadata.data());
retStatus = getSystemCameraKind(staticMetadata, &physSystemCameraKind);
ASSERT_EQ(retStatus, Status::OK);
@@ -1215,7 +1215,6 @@
getPrivacyTestPatternModes(staticMetadata, &subCameraPrivacyTestPatterns);
} else {
// Check camera characteristics for hidden camera id
- CameraMetadata physChars;
ndk::ScopedAStatus ret =
device->getPhysicalCameraCharacteristics(physicalId, &physChars);
ASSERT_TRUE(ret.isOk());
@@ -2639,8 +2638,20 @@
outputStreams.clear();
Size maxSize;
- auto rc = getMaxOutputSizeForFormat(staticMeta, format, &maxSize, maxResolution);
- ASSERT_EQ(Status::OK, rc);
+ if (maxResolution) {
+ auto rc = getMaxOutputSizeForFormat(staticMeta, format, &maxSize, maxResolution);
+ ASSERT_EQ(Status::OK, rc);
+ } else {
+ AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight,
+ static_cast<int32_t>(format)};
+ auto rc = getAvailableOutputStreams(staticMeta, outputStreams, &previewThreshold);
+
+ ASSERT_EQ(Status::OK, rc);
+ ASSERT_FALSE(outputStreams.empty());
+ maxSize.width = outputStreams[0].width;
+ maxSize.height = outputStreams[0].height;
+ }
+
std::vector<Stream> streams(1);
streams[0] = {0,
@@ -2648,9 +2659,8 @@
maxSize.width,
maxSize.height,
format,
- static_cast<::aidl::android::hardware::graphics::common::BufferUsage>(
- GRALLOC1_CONSUMER_USAGE_CPU_READ),
- Dataspace::UNKNOWN,
+ previewStream->usage,
+ previewStream->dataSpace,
StreamRotation::ROTATION_0,
"",
0,
@@ -2736,7 +2746,8 @@
HandleImporter& importer, const InFlightRequest& request,
aidl::android::hardware::camera::metadata::RequestAvailableDynamicRangeProfilesMap
profile) {
- for (const auto& b : request.resultOutputBuffers) {
+ for (auto b : request.resultOutputBuffers) {
+ importer.importBuffer(b.buffer.buffer);
bool smpte2086Present = importer.isSmpte2086Present(b.buffer.buffer);
bool smpte2094_10Present = importer.isSmpte2094_10Present(b.buffer.buffer);
bool smpte2094_40Present = importer.isSmpte2094_40Present(b.buffer.buffer);
@@ -2753,7 +2764,6 @@
ASSERT_FALSE(smpte2094_40Present);
break;
case ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS:
- ASSERT_FALSE(smpte2086Present);
ASSERT_FALSE(smpte2094_10Present);
ASSERT_TRUE(smpte2094_40Present);
break;
@@ -2774,6 +2784,7 @@
profile);
ADD_FAILURE();
}
+ importer.freeBuffer(b.buffer.buffer);
}
}
diff --git a/camera/provider/aidl/vts/camera_aidl_test.h b/camera/provider/aidl/vts/camera_aidl_test.h
index ac4b2c9..d828cee 100644
--- a/camera/provider/aidl/vts/camera_aidl_test.h
+++ b/camera/provider/aidl/vts/camera_aidl_test.h
@@ -399,6 +399,10 @@
// Result metadata
::android::hardware::camera::common::V1_0::helper::CameraMetadata collectedResult;
+ // Inflight buffers
+ using OutstandingBuffers = std::unordered_map<uint64_t, buffer_handle_t>;
+ std::vector<OutstandingBuffers> mOutstandingBufferIds;
+
// A copy-able StreamBuffer using buffer_handle_t instead of AIDLs NativeHandle
struct NativeStreamBuffer {
int32_t streamId;
diff --git a/camera/provider/aidl/vts/device_cb.cpp b/camera/provider/aidl/vts/device_cb.cpp
index e5f2f1e..4698b4a 100644
--- a/camera/provider/aidl/vts/device_cb.cpp
+++ b/camera/provider/aidl/vts/device_cb.cpp
@@ -155,7 +155,7 @@
BufferStatus::OK, NativeHandle(), NativeHandle(),
};
- mOutstandingBufferIds[idx][mNextBufferId++] = ::android::dupToAidl(handle);
+ mOutstandingBufferIds[idx][mNextBufferId++] = handle;
}
atLeastOneStreamOk = true;
bufRets[i].streamId = stream.id;
@@ -427,9 +427,13 @@
}
CameraAidlTest::InFlightRequest::StreamBufferAndTimestamp streamBufferAndTimestamp;
+ auto outstandingBuffers = mUseHalBufManager ? mOutstandingBufferIds :
+ request->mOutstandingBufferIds;
+ auto outputBuffer = outstandingBuffers.empty() ? ::android::makeFromAidl(buffer.buffer) :
+ outstandingBuffers[buffer.streamId][buffer.bufferId];
streamBufferAndTimestamp.buffer = {buffer.streamId,
buffer.bufferId,
- ::android::makeFromAidl(buffer.buffer),
+ outputBuffer,
buffer.status,
::android::makeFromAidl(buffer.acquireFence),
::android::makeFromAidl(buffer.releaseFence)};
diff --git a/camera/provider/aidl/vts/device_cb.h b/camera/provider/aidl/vts/device_cb.h
index 82ca10d..3ae7d10 100644
--- a/camera/provider/aidl/vts/device_cb.h
+++ b/camera/provider/aidl/vts/device_cb.h
@@ -73,7 +73,7 @@
std::vector<Stream> mStreams;
std::vector<HalStream> mHalStreams;
int64_t mNextBufferId = 1;
- using OutstandingBuffers = std::unordered_map<uint64_t, NativeHandle>;
+ using OutstandingBuffers = std::unordered_map<uint64_t, buffer_handle_t>;
// size == mStreams.size(). Tracking each streams outstanding buffers
std::vector<OutstandingBuffers> mOutstandingBufferIds;
std::condition_variable mFlushedCondition;
diff --git a/common/aidl/Android.bp b/common/aidl/Android.bp
index 549d970..43f8c4d 100644
--- a/common/aidl/Android.bp
+++ b/common/aidl/Android.bp
@@ -30,7 +30,7 @@
ndk: {
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
"com.android.media.swcodec",
"com.android.neuralnetworks",
],
diff --git a/common/fmq/aidl/Android.bp b/common/fmq/aidl/Android.bp
index 6fd4200..a85597c 100644
--- a/common/fmq/aidl/Android.bp
+++ b/common/fmq/aidl/Android.bp
@@ -32,7 +32,7 @@
ndk: {
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
min_sdk_version: "29",
},
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 91a14bd..5f5f95d 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -16,7 +16,7 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="false">
+ <hal format="hidl" optional="true">
<name>android.hardware.audio.effect</name>
<version>6.0</version>
<version>7.0</version>
@@ -26,6 +26,26 @@
</interface>
</hal>
<hal format="aidl" optional="true">
+ <name>android.hardware.audio.core</name>
+ <version>1</version>
+ <interface>
+ <name>IModule</name>
+ <instance>default</instance>
+ </interface>
+ <interface>
+ <name>IConfig</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.audio.effect</name>
+ <version>1</version>
+ <interface>
+ <name>IFactory</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+ <hal format="aidl" optional="true">
<name>android.hardware.authsecret</name>
<version>1</version>
<interface>
@@ -221,7 +241,7 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="false">
+ <hal format="hidl" optional="true">
<name>android.hardware.gatekeeper</name>
<version>1.0</version>
<interface>
@@ -229,11 +249,11 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
- <name>android.hardware.gnss</name>
- <version>2.0-1</version>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.gatekeeper</name>
+ <version>1</version>
<interface>
- <name>IGnss</name>
+ <name>IGatekeeper</name>
<instance>default</instance>
</interface>
</hal>
@@ -448,14 +468,6 @@
<regex-instance>.*</regex-instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
- <name>android.hardware.nfc</name>
- <version>1.2</version>
- <interface>
- <name>INfc</name>
- <instance>default</instance>
- </interface>
- </hal>
<hal format="aidl" optional="true">
<name>android.hardware.nfc</name>
<interface>
@@ -692,14 +704,6 @@
<instance>default</instance>
</interface>
</hal>
- <hal format="hidl" optional="true">
- <name>android.hardware.usb</name>
- <version>1.0-3</version>
- <interface>
- <name>IUsb</name>
- <instance>default</instance>
- </interface>
- </hal>
<hal format="aidl" optional="true">
<name>android.hardware.usb</name>
<interface>
diff --git a/gatekeeper/OWNERS b/gatekeeper/OWNERS
new file mode 100644
index 0000000..d95b856
--- /dev/null
+++ b/gatekeeper/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+guangzhu@google.com
diff --git a/gatekeeper/aidl/Android.bp b/gatekeeper/aidl/Android.bp
new file mode 100644
index 0000000..6b1bc7e
--- /dev/null
+++ b/gatekeeper/aidl/Android.bp
@@ -0,0 +1,29 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+aidl_interface {
+ name: "android.hardware.gatekeeper",
+ vendor_available: true,
+ imports: [
+ "android.hardware.security.keymint-V2",
+ ],
+ srcs: ["android/hardware/gatekeeper/*.aidl"],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ apps_enabled: false,
+ },
+ cpp: {
+ enabled: false,
+ },
+ },
+}
diff --git a/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
new file mode 100644
index 0000000..ae64ffc
--- /dev/null
+++ b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.hardware.gatekeeper;
+@VintfStability
+parcelable GatekeeperEnrollResponse {
+ int statusCode;
+ int timeoutMs;
+ long secureUserId;
+ byte[] data;
+}
diff --git a/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
new file mode 100644
index 0000000..f55da30
--- /dev/null
+++ b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.hardware.gatekeeper;
+@VintfStability
+parcelable GatekeeperVerifyResponse {
+ int statusCode;
+ int timeoutMs;
+ android.hardware.security.keymint.HardwareAuthToken hardwareAuthToken;
+}
diff --git a/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/IGatekeeper.aidl b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/IGatekeeper.aidl
new file mode 100644
index 0000000..1a6f1ff
--- /dev/null
+++ b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/IGatekeeper.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.hardware.gatekeeper;
+@SensitiveData @VintfStability
+interface IGatekeeper {
+ void deleteAllUsers();
+ void deleteUser(in int uid);
+ android.hardware.gatekeeper.GatekeeperEnrollResponse enroll(in int uid, in byte[] currentPasswordHandle, in byte[] currentPassword, in byte[] desiredPassword);
+ android.hardware.gatekeeper.GatekeeperVerifyResponse verify(in int uid, in long challenge, in byte[] enrolledPasswordHandle, in byte[] providedPassword);
+ const int STATUS_REENROLL = 1;
+ const int STATUS_OK = 0;
+ const int ERROR_GENERAL_FAILURE = -1;
+ const int ERROR_RETRY_TIMEOUT = -2;
+ const int ERROR_NOT_IMPLEMENTED = -3;
+}
diff --git a/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
new file mode 100644
index 0000000..04bacf0
--- /dev/null
+++ b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.hardware.gatekeeper;
+
+/**
+ * Gatekeeper response to enroll requests has this structure as mandatory part
+ */
+@VintfStability
+parcelable GatekeeperEnrollResponse {
+ /**
+ * Request completion status
+ */
+ int statusCode;
+ /**
+ * Retry timeout in ms, if code == ERROR_RETRY_TIMEOUT
+ * otherwise unused (0)
+ */
+ int timeoutMs;
+ /**
+ * secure user id.
+ */
+ long secureUserId;
+ /**
+ * optional crypto blob. Opaque to Android system.
+ */
+ byte[] data;
+}
diff --git a/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
new file mode 100644
index 0000000..bcf2d76
--- /dev/null
+++ b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.hardware.gatekeeper;
+
+import android.hardware.security.keymint.HardwareAuthToken;
+
+/**
+ * Gatekeeper response to verify requests has this structure as mandatory part
+ */
+@VintfStability
+parcelable GatekeeperVerifyResponse {
+ /**
+ * Request completion status
+ */
+ int statusCode;
+ /**
+ * Retry timeout in ms, if code == ERROR_RETRY_TIMEOUT
+ * otherwise unused (0)
+ */
+ int timeoutMs;
+ /**
+ * On successful verification of the password,
+ * IGatekeeper implementations must return hardware auth token
+ * in the response.
+ */
+ HardwareAuthToken hardwareAuthToken;
+}
diff --git a/gatekeeper/aidl/android/hardware/gatekeeper/IGatekeeper.aidl b/gatekeeper/aidl/android/hardware/gatekeeper/IGatekeeper.aidl
new file mode 100644
index 0000000..927293e
--- /dev/null
+++ b/gatekeeper/aidl/android/hardware/gatekeeper/IGatekeeper.aidl
@@ -0,0 +1,146 @@
+/*
+ * 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.hardware.gatekeeper;
+
+import android.hardware.gatekeeper.GatekeeperEnrollResponse;
+import android.hardware.gatekeeper.GatekeeperVerifyResponse;
+
+@VintfStability
+@SensitiveData
+interface IGatekeeper {
+ /**
+ * enroll and verify binder calls may return a ServiceSpecificException
+ * with the following error codes.
+ */
+ /* Success, but upper layers should re-enroll the verified password due to a version change. */
+ const int STATUS_REENROLL = 1;
+ /* operation is successful */
+ const int STATUS_OK = 0;
+ /* operation is successful. */
+ const int ERROR_GENERAL_FAILURE = -1;
+ /* operation should be retried after timeout. */
+ const int ERROR_RETRY_TIMEOUT = -2;
+ /* operation is not implemented. */
+ const int ERROR_NOT_IMPLEMENTED = -3;
+
+ /**
+ * Deletes all the enrolled_password_handles for all uid's. Once called,
+ * no users must be enrolled on the device.
+ * This is an optional method.
+ *
+ * Service status return:
+ *
+ * OK if all the users are deleted successfully.
+ * ERROR_GENERAL_FAILURE on failure.
+ * ERROR_NOT_IMPLEMENTED if not implemented.
+ */
+ void deleteAllUsers();
+
+ /**
+ * Deletes the enrolledPasswordHandle associated with the uid. Once deleted
+ * the user cannot be verified anymore.
+ * This is an optional method.
+ *
+ * Service status return:
+ *
+ * OK if user is deleted successfully.
+ * ERROR_GENERAL_FAILURE on failure.
+ * ERROR_NOT_IMPLEMENTED if not implemented.
+ *
+ * @param uid The Android user identifier
+ */
+ void deleteUser(in int uid);
+
+ /**
+ * Enrolls desiredPassword, which may be derived from a user selected pin
+ * or password, with the private key used only for enrolling authentication
+ * factor data.
+ *
+ * If there was already a password enrolled, current password handle must be
+ * passed in currentPasswordHandle, and current password must be passed in
+ * currentPassword. Valid currentPassword must verify() against
+ * currentPasswordHandle.
+ *
+ * Service status return:
+ *
+ * OK if password is enrolled successfully.
+ * ERROR_GENERAL_FAILURE on failure.
+ * ERROR_NOT_IMPLEMENTED if not implemented.
+ *
+ * @param uid The Android user identifier
+ *
+ * @param currentPasswordHandle The currently enrolled password handle the user
+ * wants to replace. May be empty only if there's no currently enrolled
+ * password. Otherwise must be non-empty.
+ *
+ * @param currentPassword The user's current password in plain text.
+ * it MUST verify against current_password_handle if the latter is not-empty
+ *
+ * @param desiredPassword The new password the user wishes to enroll in
+ * plaintext.
+ *
+ * @return
+ * On success, data buffer must contain the new password handle referencing
+ * the password provided in desiredPassword.
+ * This buffer can be used on subsequent calls to enroll or
+ * verify. response.statusCode must contain either ERROR_RETRY_TIMEOUT or
+ * STATUS_OK. On error, this buffer must be empty. This method may return
+ * ERROR_GENERAL_FAILURE on failure.
+ * If ERROR_RETRY_TIMEOUT is returned, response.timeout must be non-zero.
+ */
+ GatekeeperEnrollResponse enroll(in int uid, in byte[] currentPasswordHandle,
+ in byte[] currentPassword, in byte[] desiredPassword);
+
+ /**
+ * Verifies that providedPassword matches enrolledPasswordHandle.
+ *
+ * Implementations of this module may retain the result of this call
+ * to attest to the recency of authentication.
+ *
+ * On success, returns verification token in response.data, which shall be
+ * usable to attest password verification to other trusted services.
+ *
+ * Service status return:
+ *
+ * OK if password is enrolled successfully.
+ * ERROR_GENERAL_FAILURE on failure.
+ * ERROR_NOT_IMPLEMENTED if not implemented.
+ *
+ * @param uid The Android user identifier
+ *
+ * @param challenge An optional challenge to authenticate against, or 0.
+ * Used when a separate authenticator requests password verification,
+ * or for transactional password authentication.
+ *
+ * @param enrolledPasswordHandle The currently enrolled password handle that
+ * user wishes to verify against. Must be non-empty.
+ *
+ * @param providedPassword The plaintext password to be verified against the
+ * enrolledPasswordHandle
+ *
+ * @return
+ * On success, a HardwareAuthToken resulting from this verification is returned.
+ * response.statusCode must contain either ERROR_RETRY_TIMEOUT or
+ * or STATUS_REENROLL or STATUS_OK.
+ * On error, data buffer must be empty.
+ * This method may return ERROR_GENERAL_FAILURE on failure.
+ * If password re-enrollment is necessary, it must return STATUS_REENROLL.
+ * If ERROR_RETRY_TIMEOUT is returned, response.timeout must be non-zero.
+ */
+ GatekeeperVerifyResponse verify(in int uid, in long challenge, in byte[] enrolledPasswordHandle,
+ in byte[] providedPassword);
+}
diff --git a/gatekeeper/aidl/vts/functional/Android.bp b/gatekeeper/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..2fa80de
--- /dev/null
+++ b/gatekeeper/aidl/vts/functional/Android.bp
@@ -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.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+ name: "VtsHalGatekeeperTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ ],
+ srcs: ["VtsHalGatekeeperTargetTest.cpp"],
+ shared_libs: [
+ "libbinder_ndk",
+ "libbase",
+ ],
+ static_libs: ["android.hardware.gatekeeper-V1-ndk"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/gatekeeper/aidl/vts/functional/VtsHalGatekeeperTargetTest.cpp b/gatekeeper/aidl/vts/functional/VtsHalGatekeeperTargetTest.cpp
new file mode 100644
index 0000000..c89243b
--- /dev/null
+++ b/gatekeeper/aidl/vts/functional/VtsHalGatekeeperTargetTest.cpp
@@ -0,0 +1,413 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "gatekeeper_aidl_hal_test"
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cmath>
+#include <string>
+#include <vector>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.h>
+#include <aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.h>
+#include <aidl/android/hardware/gatekeeper/IGatekeeper.h>
+#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
+#include <android-base/endian.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <hardware/hw_auth_token.h>
+
+#include <log/log.h>
+
+using aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
+using aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
+using aidl::android::hardware::gatekeeper::IGatekeeper;
+using aidl::android::hardware::security::keymint::HardwareAuthToken;
+using Status = ::ndk::ScopedAStatus;
+
+struct GatekeeperRequest {
+ uint32_t uid;
+ uint64_t challenge;
+ std::vector<uint8_t> curPwdHandle;
+ std::vector<uint8_t> curPwd;
+ std::vector<uint8_t> newPwd;
+ GatekeeperRequest() : uid(0), challenge(0) {}
+};
+
+// ASSERT_* macros generate return "void" internally
+// we have to use EXPECT_* if we return anything but "void"
+static void verifyAuthToken(GatekeeperVerifyResponse& rsp) {
+ uint32_t auth_type = static_cast<uint32_t>(rsp.hardwareAuthToken.authenticatorType);
+ uint64_t auth_tstamp = static_cast<uint64_t>(rsp.hardwareAuthToken.timestamp.milliSeconds);
+
+ EXPECT_EQ(HW_AUTH_PASSWORD, auth_type);
+ EXPECT_NE(UINT64_C(~0), auth_tstamp);
+ ALOGI("Authenticator ID: %016" PRIX64, rsp.hardwareAuthToken.authenticatorId);
+ EXPECT_NE(UINT32_C(0), rsp.hardwareAuthToken.userId);
+}
+
+// The main test class for Gatekeeper AIDL HAL.
+class GatekeeperAidlTest : public ::testing::TestWithParam<std::string> {
+ protected:
+ void setUid(uint32_t uid) { uid_ = uid; }
+
+ Status doEnroll(GatekeeperRequest& req, GatekeeperEnrollResponse& rsp) {
+ Status ret;
+ while (true) {
+ ret = gatekeeper_->enroll(uid_, req.curPwdHandle, req.curPwd, req.newPwd, &rsp);
+ if (ret.isOk()) break;
+ if (getReturnStatusCode(ret) != IGatekeeper::ERROR_RETRY_TIMEOUT) break;
+ ALOGI("%s: got retry code; retrying in 1 sec", __func__);
+ sleep(1);
+ }
+ return ret;
+ }
+
+ Status doVerify(GatekeeperRequest& req, GatekeeperVerifyResponse& rsp) {
+ Status ret;
+ while (true) {
+ ret = gatekeeper_->verify(uid_, req.challenge, req.curPwdHandle, req.newPwd, &rsp);
+ if (ret.isOk()) break;
+ if (getReturnStatusCode(ret) != IGatekeeper::ERROR_RETRY_TIMEOUT) break;
+ ALOGI("%s: got retry code; retrying in 1 sec", __func__);
+ sleep(1);
+ }
+ return ret;
+ }
+
+ Status doDeleteUser() { return gatekeeper_->deleteUser(uid_); }
+
+ Status doDeleteAllUsers() { return gatekeeper_->deleteAllUsers(); }
+
+ void generatePassword(std::vector<uint8_t>& password, uint8_t seed) {
+ password.resize(16);
+ memset(password.data(), seed, password.size());
+ }
+
+ void checkEnroll(GatekeeperEnrollResponse& rsp, Status& ret, bool expectSuccess) {
+ if (expectSuccess) {
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_EQ(IGatekeeper::STATUS_OK, rsp.statusCode);
+ EXPECT_NE(nullptr, rsp.data.data());
+ EXPECT_GT(rsp.data.size(), UINT32_C(0));
+ EXPECT_NE(UINT32_C(0), rsp.secureUserId);
+ } else {
+ EXPECT_EQ(IGatekeeper::ERROR_GENERAL_FAILURE, getReturnStatusCode(ret));
+ EXPECT_EQ(UINT32_C(0), rsp.data.size());
+ }
+ }
+
+ void checkVerify(GatekeeperVerifyResponse& rsp, Status& ret, uint64_t challenge,
+ bool expectSuccess) {
+ if (expectSuccess) {
+ EXPECT_TRUE(ret.isOk());
+ EXPECT_GE(rsp.statusCode, IGatekeeper::STATUS_OK);
+ EXPECT_LE(rsp.statusCode, IGatekeeper::STATUS_REENROLL);
+
+ verifyAuthToken(rsp);
+ EXPECT_EQ(challenge, rsp.hardwareAuthToken.challenge);
+ } else {
+ EXPECT_EQ(IGatekeeper::ERROR_GENERAL_FAILURE, getReturnStatusCode(ret));
+ }
+ }
+
+ void enrollNewPassword(std::vector<uint8_t>& password, GatekeeperEnrollResponse& rsp,
+ bool expectSuccess) {
+ GatekeeperRequest req;
+ req.newPwd = password;
+ Status ret = doEnroll(req, rsp);
+ checkEnroll(rsp, ret, expectSuccess);
+ }
+
+ void verifyPassword(std::vector<uint8_t>& password, std::vector<uint8_t>& passwordHandle,
+ uint64_t challenge, GatekeeperVerifyResponse& verifyRsp,
+ bool expectSuccess) {
+ GatekeeperRequest verifyReq;
+
+ // build verify request for the same password (we want it to succeed)
+ verifyReq.newPwd = password;
+ // use enrolled password handle we've got
+ verifyReq.curPwdHandle = passwordHandle;
+ verifyReq.challenge = challenge;
+ Status ret = doVerify(verifyReq, verifyRsp);
+ checkVerify(verifyRsp, ret, challenge, expectSuccess);
+ }
+
+ int32_t getReturnStatusCode(const Status& result) {
+ if (!result.isOk()) {
+ if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+ return result.getServiceSpecificError();
+ }
+ return IGatekeeper::ERROR_GENERAL_FAILURE;
+ }
+ return IGatekeeper::STATUS_OK;
+ }
+
+ protected:
+ std::shared_ptr<IGatekeeper> gatekeeper_;
+ uint32_t uid_;
+
+ public:
+ GatekeeperAidlTest() : uid_(0) {}
+ virtual void SetUp() override {
+ gatekeeper_ = IGatekeeper::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+ ASSERT_NE(nullptr, gatekeeper_.get());
+ doDeleteAllUsers();
+ }
+
+ virtual void TearDown() override { doDeleteAllUsers(); }
+};
+
+/**
+ * Ensure we can enroll new password
+ */
+TEST_P(GatekeeperAidlTest, EnrollSuccess) {
+ std::vector<uint8_t> password;
+ GatekeeperEnrollResponse rsp;
+ ALOGI("Testing Enroll (expected success)");
+ generatePassword(password, 0);
+ enrollNewPassword(password, rsp, true);
+ ALOGI("Testing Enroll done");
+}
+
+/**
+ * Ensure we can not enroll empty password
+ */
+TEST_P(GatekeeperAidlTest, EnrollNoPassword) {
+ std::vector<uint8_t> password;
+ GatekeeperEnrollResponse rsp;
+ ALOGI("Testing Enroll (expected failure)");
+ enrollNewPassword(password, rsp, false);
+ ALOGI("Testing Enroll done");
+}
+
+/**
+ * Ensure we can successfully verify previously enrolled password
+ */
+TEST_P(GatekeeperAidlTest, VerifySuccess) {
+ GatekeeperEnrollResponse enrollRsp;
+ GatekeeperVerifyResponse verifyRsp;
+ std::vector<uint8_t> password;
+
+ ALOGI("Testing Enroll+Verify (expected success)");
+ generatePassword(password, 0);
+ enrollNewPassword(password, enrollRsp, true);
+ verifyPassword(password, enrollRsp.data, 1, verifyRsp, true);
+
+ ALOGI("Testing unenrolled password doesn't verify");
+ verifyRsp = {0, 0, {}};
+ generatePassword(password, 1);
+ verifyPassword(password, enrollRsp.data, 1, verifyRsp, false);
+ ALOGI("Testing Enroll+Verify done");
+}
+
+/**
+ * Ensure we can securely update password (keep the same
+ * secure user_id) if we prove we know old password
+ */
+TEST_P(GatekeeperAidlTest, TrustedReenroll) {
+ GatekeeperEnrollResponse enrollRsp;
+ GatekeeperRequest reenrollReq;
+ GatekeeperEnrollResponse reenrollRsp;
+ GatekeeperVerifyResponse verifyRsp;
+ GatekeeperVerifyResponse reenrollVerifyRsp;
+ std::vector<uint8_t> password;
+ std::vector<uint8_t> newPassword;
+
+ generatePassword(password, 0);
+
+ ALOGI("Testing Trusted Reenroll (expected success)");
+ enrollNewPassword(password, enrollRsp, true);
+ verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+ ALOGI("Primary Enroll+Verify done");
+
+ generatePassword(newPassword, 1);
+ reenrollReq.newPwd = newPassword;
+ reenrollReq.curPwd = password;
+ reenrollReq.curPwdHandle = enrollRsp.data;
+
+ Status ret = doEnroll(reenrollReq, reenrollRsp);
+ checkEnroll(reenrollRsp, ret, true);
+ verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
+ ALOGI("Trusted ReEnroll+Verify done");
+
+ verifyAuthToken(verifyRsp);
+ verifyAuthToken(reenrollVerifyRsp);
+ EXPECT_EQ(verifyRsp.hardwareAuthToken.userId, reenrollVerifyRsp.hardwareAuthToken.userId);
+ ALOGI("Testing Trusted Reenroll done");
+}
+
+/**
+ * Ensure we can update password (and get new
+ * secure user_id) if we don't know old password
+ */
+TEST_P(GatekeeperAidlTest, UntrustedReenroll) {
+ GatekeeperEnrollResponse enrollRsp;
+ GatekeeperEnrollResponse reenrollRsp;
+ GatekeeperVerifyResponse verifyRsp;
+ GatekeeperVerifyResponse reenrollVerifyRsp;
+ std::vector<uint8_t> password;
+ std::vector<uint8_t> newPassword;
+
+ ALOGI("Testing Untrusted Reenroll (expected success)");
+ generatePassword(password, 0);
+ enrollNewPassword(password, enrollRsp, true);
+ verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+ ALOGI("Primary Enroll+Verify done");
+
+ generatePassword(newPassword, 1);
+ enrollNewPassword(newPassword, reenrollRsp, true);
+ verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
+ ALOGI("Untrusted ReEnroll+Verify done");
+
+ verifyAuthToken(verifyRsp);
+ verifyAuthToken(reenrollVerifyRsp);
+ EXPECT_NE(verifyRsp.hardwareAuthToken.userId, reenrollVerifyRsp.hardwareAuthToken.userId);
+ ALOGI("Testing Untrusted Reenroll done");
+}
+
+/**
+ * Ensure we don't get successful verify with invalid data
+ */
+TEST_P(GatekeeperAidlTest, VerifyNoData) {
+ std::vector<uint8_t> password;
+ std::vector<uint8_t> passwordHandle;
+ GatekeeperVerifyResponse verifyRsp;
+
+ ALOGI("Testing Verify (expected failure)");
+ verifyPassword(password, passwordHandle, 0, verifyRsp, false);
+ ALOGI("Testing Verify done");
+}
+
+/**
+ * Ensure we can not verify password after we enrolled it and then deleted user
+ */
+TEST_P(GatekeeperAidlTest, DeleteUserTest) {
+ std::vector<uint8_t> password;
+ GatekeeperEnrollResponse enrollRsp;
+ GatekeeperVerifyResponse verifyRsp;
+ ALOGI("Testing deleteUser (expected success)");
+ setUid(10001);
+ generatePassword(password, 0);
+ enrollNewPassword(password, enrollRsp, true);
+ verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+ ALOGI("Enroll+Verify done");
+ auto result = doDeleteUser();
+ EXPECT_TRUE(result.isOk() ||
+ (getReturnStatusCode(result) == IGatekeeper::ERROR_NOT_IMPLEMENTED));
+ ALOGI("DeleteUser done");
+ if (result.isOk()) {
+ verifyRsp = {0, 0, {}};
+ verifyPassword(password, enrollRsp.data, 0, verifyRsp, false);
+ ALOGI("Verify after Delete done (must fail)");
+ }
+ ALOGI("Testing deleteUser done: rsp=%" PRIi32, getReturnStatusCode(result));
+}
+
+/**
+ * Ensure we can not delete a user that does not exist
+ */
+TEST_P(GatekeeperAidlTest, DeleteInvalidUserTest) {
+ std::vector<uint8_t> password;
+ GatekeeperEnrollResponse enrollRsp;
+ GatekeeperVerifyResponse verifyRsp;
+ ALOGI("Testing deleteUser (expected failure)");
+ setUid(10002);
+ generatePassword(password, 0);
+ enrollNewPassword(password, enrollRsp, true);
+ verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+ ALOGI("Enroll+Verify done");
+
+ // Delete the user
+ Status result1 = doDeleteUser();
+ EXPECT_TRUE(result1.isOk() ||
+ (getReturnStatusCode(result1) == IGatekeeper::ERROR_NOT_IMPLEMENTED));
+
+ // Delete the user again
+ Status result2 = doDeleteUser();
+ int32_t retCode2 = getReturnStatusCode(result2);
+ EXPECT_TRUE((retCode2 == IGatekeeper::ERROR_NOT_IMPLEMENTED) ||
+ (retCode2 == IGatekeeper::ERROR_GENERAL_FAILURE));
+ ALOGI("DeleteUser done");
+ ALOGI("Testing deleteUser done: rsp=%" PRIi32, retCode2);
+}
+
+/**
+ * Ensure we can not verify passwords after we enrolled them and then deleted
+ * all users
+ */
+TEST_P(GatekeeperAidlTest, DeleteAllUsersTest) {
+ struct UserData {
+ uint32_t userId;
+ std::vector<uint8_t> password;
+ GatekeeperEnrollResponse enrollRsp;
+ GatekeeperVerifyResponse verifyRsp;
+ UserData(int id) { userId = id; }
+ } users[3]{10001, 10002, 10003};
+ ALOGI("Testing deleteAllUsers (expected success)");
+
+ // enroll multiple users
+ for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
+ setUid(users[i].userId);
+ generatePassword(users[i].password, (i % 255) + 1);
+ enrollNewPassword(users[i].password, users[i].enrollRsp, true);
+ }
+ ALOGI("Multiple users enrolled");
+
+ // verify multiple users
+ for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
+ setUid(users[i].userId);
+ verifyPassword(users[i].password, users[i].enrollRsp.data, 0, users[i].verifyRsp, true);
+ }
+ ALOGI("Multiple users verified");
+
+ Status result = doDeleteAllUsers();
+ EXPECT_TRUE(result.isOk() ||
+ (getReturnStatusCode(result) == IGatekeeper::ERROR_NOT_IMPLEMENTED));
+ ALOGI("All users deleted");
+
+ if (result.isOk()) {
+ // verify multiple users after they are deleted; all must fail
+ for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
+ setUid(users[i].userId);
+ users[i].verifyRsp = {0, 0, {}};
+ verifyPassword(users[i].password, users[i].enrollRsp.data, 0, users[i].verifyRsp,
+ false);
+ }
+ ALOGI("Multiple users verified after delete (all must fail)");
+ }
+
+ ALOGI("Testing deleteAllUsers done: rsp=%" PRIi32, getReturnStatusCode(result));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GatekeeperAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+ PerInstance, GatekeeperAidlTest,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IGatekeeper::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/graphics/bufferqueue/1.0/Android.bp b/graphics/bufferqueue/1.0/Android.bp
index 11559b2..82c71f1 100644
--- a/graphics/bufferqueue/1.0/Android.bp
+++ b/graphics/bufferqueue/1.0/Android.bp
@@ -27,7 +27,7 @@
gen_java: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
"com.android.media",
"com.android.media.swcodec",
],
diff --git a/graphics/bufferqueue/2.0/Android.bp b/graphics/bufferqueue/2.0/Android.bp
index 552daff..3067e24 100644
--- a/graphics/bufferqueue/2.0/Android.bp
+++ b/graphics/bufferqueue/2.0/Android.bp
@@ -29,7 +29,7 @@
gen_java: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
"com.android.media",
"com.android.media.swcodec",
],
diff --git a/graphics/common/1.0/Android.bp b/graphics/common/1.0/Android.bp
index 19c51cd..3288583 100644
--- a/graphics/common/1.0/Android.bp
+++ b/graphics/common/1.0/Android.bp
@@ -23,7 +23,7 @@
gen_java_constants: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
"com.android.media.swcodec",
"test_com.android.media.swcodec",
],
diff --git a/graphics/common/1.1/Android.bp b/graphics/common/1.1/Android.bp
index 0f1b5bf..5d07eae 100644
--- a/graphics/common/1.1/Android.bp
+++ b/graphics/common/1.1/Android.bp
@@ -26,7 +26,7 @@
gen_java_constants: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
"com.android.media.swcodec",
"test_com.android.media.swcodec",
],
diff --git a/graphics/common/1.2/Android.bp b/graphics/common/1.2/Android.bp
index ce3350d..4aa4af5 100644
--- a/graphics/common/1.2/Android.bp
+++ b/graphics/common/1.2/Android.bp
@@ -27,7 +27,7 @@
gen_java_constants: true,
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
"com.android.media.swcodec",
"test_com.android.media.swcodec",
],
diff --git a/graphics/composer/aidl/vts/VtsComposerClient.cpp b/graphics/composer/aidl/vts/VtsComposerClient.cpp
index 2b60703..5bc7296 100644
--- a/graphics/composer/aidl/vts/VtsComposerClient.cpp
+++ b/graphics/composer/aidl/vts/VtsComposerClient.cpp
@@ -488,10 +488,13 @@
}
bool VtsComposerClient::destroyAllLayers() {
- for (const auto& it : mDisplayResources) {
- const auto& [display, resource] = it;
+ std::unordered_map<int64_t, DisplayResource> physicalDisplays;
+ while (!mDisplayResources.empty()) {
+ const auto& it = mDisplayResources.begin();
+ const auto& [display, resource] = *it;
- for (auto layer : resource.layers) {
+ while (!resource.layers.empty()) {
+ auto layer = *resource.layers.begin();
const auto status = destroyLayer(display, layer);
if (!status.isOk()) {
ALOGE("Unable to destroy all the layers, failed at layer %" PRId64 " with error %s",
@@ -507,8 +510,12 @@
status.getDescription().c_str());
return false;
}
+ } else {
+ auto extractIter = mDisplayResources.extract(it);
+ physicalDisplays.insert(std::move(extractIter));
}
}
+ mDisplayResources.swap(physicalDisplays);
mDisplayResources.clear();
return true;
}
diff --git a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h
index db7e67d..5f0a176 100644
--- a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h
+++ b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h
@@ -259,19 +259,22 @@
for (int i = 0; i < 3; i++) {
const auto& plane = flex.planes[i];
- // must have 8-bit depth
- if (plane.bits_per_component != 8 || plane.bits_used != 8) {
+ // Must be a positive multiple of 8.
+ if (plane.bits_per_component <= 0 || (plane.bits_per_component % 8) != 0) {
return false;
}
-
+ // Must be between 1 and bits_per_component, inclusive.
+ if (plane.bits_used < 1 || plane.bits_used > plane.bits_per_component) {
+ return false;
+ }
if (plane.component == FLEX_COMPONENT_Y) {
// Y must not be interleaved
- if (plane.h_increment != 1) {
+ if (plane.h_increment != 1 && plane.h_increment != 2) {
return false;
}
} else {
// Cb and Cr can be interleaved
- if (plane.h_increment != 1 && plane.h_increment != 2) {
+ if (plane.h_increment != 1 && plane.h_increment != 2 && plane.h_increment != 4) {
return false;
}
}
diff --git a/health/aidl/default/Android.bp b/health/aidl/default/Android.bp
index 0d426da..4eb3cb1 100644
--- a/health/aidl/default/Android.bp
+++ b/health/aidl/default/Android.bp
@@ -192,33 +192,14 @@
name: "android.hardware.health-service.aidl_fuzzer",
defaults: [
"libhealth_aidl_impl_user",
+ "service_fuzzer_defaults",
],
static_libs: [
"android.hardware.health-V1-ndk",
"libbase",
- "libbinder_random_parcel",
- "libcutils",
"liblog",
- "libutils",
"fuzz_libhealth_aidl_impl",
],
- target: {
- android: {
- shared_libs: [
- "libbinder_ndk",
- "libbinder",
- ],
- },
- host: {
- static_libs: [
- "libbinder_ndk",
- "libbinder",
- ],
- },
- darwin: {
- enabled: false,
- },
- },
srcs: ["fuzzer.cpp"],
fuzz_config: {
cc: [
diff --git a/identity/aidl/Android.bp b/identity/aidl/Android.bp
index f6855e8..57451ed 100644
--- a/identity/aidl/Android.bp
+++ b/identity/aidl/Android.bp
@@ -59,3 +59,27 @@
],
}
+
+// cc_defaults that includes the latest Identity AIDL library.
+// Modules that depend on Identity directly can include this cc_defaults to
+// avoid managing dependency versions explicitly.
+cc_defaults {
+ name: "identity_use_latest_hal_aidl_ndk_static",
+ static_libs: [
+ "android.hardware.identity-V4-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "identity_use_latest_hal_aidl_ndk_shared",
+ shared_libs: [
+ "android.hardware.identity-V4-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "identity_use_latest_hal_aidl_cpp_static",
+ static_libs: [
+ "android.hardware.identity-V4-cpp",
+ ],
+}
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
index 31ab400..a57875a 100644
--- a/identity/aidl/default/Android.bp
+++ b/identity/aidl/default/Android.bp
@@ -10,6 +10,10 @@
cc_library_static {
name: "android.hardware.identity-libeic-hal-common",
vendor_available: true,
+ defaults: [
+ "identity_use_latest_hal_aidl_ndk_static",
+ "keymint_use_latest_hal_aidl_ndk_static",
+ ],
srcs: [
"common/IdentityCredential.cpp",
"common/IdentityCredentialStore.cpp",
@@ -40,9 +44,7 @@
"libsoft_attestation_cert",
"libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
- "android.hardware.identity-V4-ndk",
"android.hardware.keymaster-V3-ndk",
- "android.hardware.security.keymint-V2-ndk",
],
}
@@ -83,6 +85,7 @@
vintf_fragments: ["identity-default.xml"],
vendor: true,
defaults: [
+ "identity_use_latest_hal_aidl_ndk_static",
"keymint_use_latest_hal_aidl_ndk_static",
],
cflags: [
@@ -106,7 +109,6 @@
"libsoft_attestation_cert",
"libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
- "android.hardware.identity-V4-ndk",
"android.hardware.keymaster-V3-ndk",
"android.hardware.identity-libeic-hal-common",
"android.hardware.identity-libeic-library",
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index 51ab110..dd29819 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -11,6 +11,7 @@
name: "VtsHalIdentityTargetTest",
defaults: [
"VtsHalTargetTestDefaults",
+ "identity_use_latest_hal_aidl_cpp_static",
"keymint_use_latest_hal_aidl_cpp_static",
"keymint_use_latest_hal_aidl_ndk_static",
"use_libaidlvintf_gtest_helper_static",
@@ -46,7 +47,6 @@
"libpuresoftkeymasterdevice",
"android.hardware.keymaster@4.0",
"android.hardware.identity-support-lib",
- "android.hardware.identity-V4-cpp",
"android.hardware.keymaster-V3-cpp",
"android.hardware.keymaster-V3-ndk",
"libkeymaster4support",
diff --git a/light/aidl/default/Lights.h b/light/aidl/default/Lights.h
index d6d5bf1..cba147f 100644
--- a/light/aidl/default/Lights.h
+++ b/light/aidl/default/Lights.h
@@ -26,7 +26,7 @@
// Default implementation that reports a few placeholder lights.
class Lights : public BnLights {
ndk::ScopedAStatus setLightState(int id, const HwLightState& state) override;
- ndk::ScopedAStatus getLights(std::vector<HwLight>* types) override;
+ ndk::ScopedAStatus getLights(std::vector<HwLight>* lights) override;
};
} // namespace light
diff --git a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
index 5ffbd43..34bc962 100644
--- a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
@@ -232,26 +232,24 @@
// currently 1Mb, which is shared by all transactions in progress
// for the process."
//
-// Will our representation fit under this limit? There are two complications:
+// Will our representation fit under this limit? There are three complications:
// - Our representation size is just approximate (see sizeForBinder()).
-// - This object may not be the only occupant of the Binder transaction buffer.
+// - This object may not be the only occupant of the Binder transaction buffer
+// (although our VTS test suite should not be putting multiple objects in the
+// buffer at once).
+// - IBinder.MAX_IPC_SIZE recommends limiting a transaction to 64 * 1024 bytes.
// So we'll be very conservative: We want the representation size to be no
-// larger than half the transaction buffer size.
+// larger than half the recommended limit.
//
// If our representation grows large enough that it still fits within
// the transaction buffer but combined with other transactions may
// exceed the buffer size, then we may see intermittent HAL transport
// errors.
static bool exceedsBinderSizeLimit(size_t representationSize) {
- // Instead of using this fixed buffer size, we might instead be able to use
- // ProcessState::self()->getMmapSize(). However, this has a potential
- // problem: The binder/mmap size of the current process does not necessarily
- // indicate the binder/mmap size of the service (i.e., the other process).
- // The only way it would be a good indication is if both the current process
- // and the service use the default size.
- static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+ // There is no C++ API to retrieve the value of the Java variable IBinder.MAX_IPC_SIZE.
+ static const size_t kHalfMaxIPCSize = 64 * 1024 / 2;
- return representationSize > kHalfBufferSize;
+ return representationSize > kHalfMaxIPCSize;
}
///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
diff --git a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
index 1f4e4ed..dbabbaf 100644
--- a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
@@ -252,26 +252,24 @@
// currently 1Mb, which is shared by all transactions in progress
// for the process."
//
-// Will our representation fit under this limit? There are two complications:
+// Will our representation fit under this limit? There are three complications:
// - Our representation size is just approximate (see sizeForBinder()).
-// - This object may not be the only occupant of the Binder transaction buffer.
+// - This object may not be the only occupant of the Binder transaction buffer
+// (although our VTS test suite should not be putting multiple objects in the
+// buffer at once).
+// - IBinder.MAX_IPC_SIZE recommends limiting a transaction to 64 * 1024 bytes.
// So we'll be very conservative: We want the representation size to be no
-// larger than half the transaction buffer size.
+// larger than half the recommended limit.
//
// If our representation grows large enough that it still fits within
// the transaction buffer but combined with other transactions may
// exceed the buffer size, then we may see intermittent HAL transport
// errors.
static bool exceedsBinderSizeLimit(size_t representationSize) {
- // Instead of using this fixed buffer size, we might instead be able to use
- // ProcessState::self()->getMmapSize(). However, this has a potential
- // problem: The binder/mmap size of the current process does not necessarily
- // indicate the binder/mmap size of the service (i.e., the other process).
- // The only way it would be a good indication is if both the current process
- // and the service use the default size.
- static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+ // There is no C++ API to retrieve the value of the Java variable IBinder.MAX_IPC_SIZE.
+ static const size_t kHalfMaxIPCSize = 64 * 1024 / 2;
- return representationSize > kHalfBufferSize;
+ return representationSize > kHalfMaxIPCSize;
}
///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
diff --git a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
index 3375602..d7cd6f5 100644
--- a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
@@ -291,26 +291,24 @@
// currently 1Mb, which is shared by all transactions in progress
// for the process."
//
-// Will our representation fit under this limit? There are two complications:
+// Will our representation fit under this limit? There are three complications:
// - Our representation size is just approximate (see sizeForBinder()).
-// - This object may not be the only occupant of the Binder transaction buffer.
+// - This object may not be the only occupant of the Binder transaction buffer
+// (although our VTS test suite should not be putting multiple objects in the
+// buffer at once).
+// - IBinder.MAX_IPC_SIZE recommends limiting a transaction to 64 * 1024 bytes.
// So we'll be very conservative: We want the representation size to be no
-// larger than half the transaction buffer size.
+// larger than half the recommended limit.
//
// If our representation grows large enough that it still fits within
// the transaction buffer but combined with other transactions may
// exceed the buffer size, then we may see intermittent HAL transport
// errors.
static bool exceedsBinderSizeLimit(size_t representationSize) {
- // Instead of using this fixed buffer size, we might instead be able to use
- // ProcessState::self()->getMmapSize(). However, this has a potential
- // problem: The binder/mmap size of the current process does not necessarily
- // indicate the binder/mmap size of the service (i.e., the other process).
- // The only way it would be a good indication is if both the current process
- // and the service use the default size.
- static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+ // There is no C++ API to retrieve the value of the Java variable IBinder.MAX_IPC_SIZE.
+ static const size_t kHalfMaxIPCSize = 64 * 1024 / 2;
- return representationSize > kHalfBufferSize;
+ return representationSize > kHalfMaxIPCSize;
}
///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
diff --git a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
index 849ef7b..d8c7cd1 100644
--- a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
@@ -308,26 +308,24 @@
// currently 1Mb, which is shared by all transactions in progress
// for the process."
//
-// Will our representation fit under this limit? There are two complications:
+// Will our representation fit under this limit? There are three complications:
// - Our representation size is just approximate (see sizeForBinder()).
-// - This object may not be the only occupant of the Binder transaction buffer.
+// - This object may not be the only occupant of the Binder transaction buffer
+// (although our VTS test suite should not be putting multiple objects in the
+// buffer at once).
+// - IBinder.MAX_IPC_SIZE recommends limiting a transaction to 64 * 1024 bytes.
// So we'll be very conservative: We want the representation size to be no
-// larger than half the transaction buffer size.
+// larger than half the recommended limit.
//
// If our representation grows large enough that it still fits within
// the transaction buffer but combined with other transactions may
// exceed the buffer size, then we may see intermittent HAL transport
// errors.
static bool exceedsBinderSizeLimit(size_t representationSize) {
- // Instead of using this fixed buffer size, we might instead be able to use
- // ProcessState::self()->getMmapSize(). However, this has a potential
- // problem: The binder/mmap size of the current process does not necessarily
- // indicate the binder/mmap size of the service (i.e., the other process).
- // The only way it would be a good indication is if both the current process
- // and the service use the default size.
- static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+ // There is no C++ API to retrieve the value of the Java variable IBinder.MAX_IPC_SIZE.
+ static const size_t kHalfMaxIPCSize = 64 * 1024 / 2;
- return representationSize > kHalfBufferSize;
+ return representationSize > kHalfMaxIPCSize;
}
///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
diff --git a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
index 060434e..d7baf19 100644
--- a/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/aidl/vts/functional/ValidateModel.cpp
@@ -344,26 +344,24 @@
// currently 1Mb, which is shared by all transactions in progress
// for the process."
//
-// Will our representation fit under this limit? There are two complications:
+// Will our representation fit under this limit? There are three complications:
// - Our representation size is just approximate (see sizeForBinder()).
-// - This object may not be the only occupant of the Binder transaction buffer.
+// - This object may not be the only occupant of the Binder transaction buffer
+// (although our VTS test suite should not be putting multiple objects in the
+// buffer at once).
+// - IBinder.MAX_IPC_SIZE recommends limiting a transaction to 64 * 1024 bytes.
// So we'll be very conservative: We want the representation size to be no
-// larger than half the transaction buffer size.
+// larger than half the recommended limit.
//
// If our representation grows large enough that it still fits within
// the transaction buffer but combined with other transactions may
// exceed the buffer size, then we may see intermittent HAL transport
// errors.
static bool exceedsBinderSizeLimit(size_t representationSize) {
- // Instead of using this fixed buffer size, we might instead be able to use
- // ProcessState::self()->getMmapSize(). However, this has a potential
- // problem: The binder/mmap size of the current process does not necessarily
- // indicate the binder/mmap size of the service (i.e., the other process).
- // The only way it would be a good indication is if both the current process
- // and the service use the default size.
- static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+ // There is no C++ API to retrieve the value of the Java variable IBinder.MAX_IPC_SIZE.
+ static const size_t kHalfMaxIPCSize = 64 * 1024 / 2;
- return representationSize > kHalfBufferSize;
+ return representationSize > kHalfMaxIPCSize;
}
///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
diff --git a/power/OWNERS b/power/OWNERS
new file mode 100644
index 0000000..7229b22
--- /dev/null
+++ b/power/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 826709
+
+# ADPF virtual team
+lpy@google.com
+wvw@google.com
diff --git a/radio/1.0/Android.bp b/radio/1.0/Android.bp
index 8d0d782..e49a50d 100644
--- a/radio/1.0/Android.bp
+++ b/radio/1.0/Android.bp
@@ -25,7 +25,7 @@
],
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
gen_java: true,
}
diff --git a/radio/aidl/OWNERS b/radio/aidl/OWNERS
new file mode 100644
index 0000000..7b01aad
--- /dev/null
+++ b/radio/aidl/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 20868
+include ../1.0/vts/OWNERS
+
diff --git a/radio/aidl/vts/OWNERS b/radio/aidl/vts/OWNERS
deleted file mode 100644
index e75c6c8..0000000
--- a/radio/aidl/vts/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 20868
-include ../../1.0/vts/OWNERS
-
diff --git a/security/keymint/RKP_CHANGELOG.md b/security/keymint/RKP_CHANGELOG.md
index 67d68d4..dfcc938 100644
--- a/security/keymint/RKP_CHANGELOG.md
+++ b/security/keymint/RKP_CHANGELOG.md
@@ -6,13 +6,28 @@
## Releases
* **Android S (12):** IRemotelyProvisionedComponent v1
* **Android T (13):** IRemotelyProvisionedComponent v2
+* **Android U (14):** IRemotelyProvisionedComponent v3
## IRemotelyProvisionedComponent 1 -> 2
* DeviceInfo
- * Most entries are no longer optional.
- * `att_id_state` is now `fused`. `fused` is used to indicate if SecureBoot is enabled.
- * `version` is now `2`.
- * `board` has been removed.
- * `device` has been added.
+ * Most entries are no longer optional.
+ * `att_id_state` is now `fused`. `fused` is used to indicate if SecureBoot is enabled.
+ * `version` is now `2`.
+ * `board` has been removed.
+ * `device` has been added.
* RpcHardwareInfo
- * `uniqueId` String added as a field in order to differentiate IRPC instances on device.
\ No newline at end of file
+ * `uniqueId` String added as a field in order to differentiate IRPC instances on device.
+
+## IRemotelyProvisionedComponent 2 -> 3
+* ProtectedData has been removed.
+* DeviceInfo
+ * `cert_type` has been added, with values corresponding to `widevine` or `keymint`
+ * `version` has moved to a top-level field within the CSR generated by the HAL
+* IRemotelyProvisionedComponent
+ * The need for an EEK has been removed. There is no longer an encrypted portion of the CSR.
+ * Test mode has been removed.
+ * The schema for the CSR itself has been significantly simplified, please see
+ IRemotelyProvisionedComponent.aidl for more details.
+ * Notably, the chain of signing, MACing, and encryption operations has been replaced with a single
+ COSE_Sign1 object.
+
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
index f566462..626ece8 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -38,9 +38,11 @@
android.hardware.security.keymint.RpcHardwareInfo getHardwareInfo();
byte[] generateEcdsaP256KeyPair(in boolean testMode, out android.hardware.security.keymint.MacedPublicKey macedPublicKey);
byte[] generateCertificateRequest(in boolean testMode, in android.hardware.security.keymint.MacedPublicKey[] keysToSign, in byte[] endpointEncryptionCertChain, in byte[] challenge, out android.hardware.security.keymint.DeviceInfo deviceInfo, out android.hardware.security.keymint.ProtectedData protectedData);
+ byte[] generateCertificateRequestV2(in android.hardware.security.keymint.MacedPublicKey[] keysToSign, in byte[] challenge);
const int STATUS_FAILED = 1;
const int STATUS_INVALID_MAC = 2;
const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
const int STATUS_INVALID_EEK = 5;
+ const int STATUS_REMOVED = 6;
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl b/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl
index abb2a7b..6954d65 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl
@@ -37,19 +37,19 @@
* "product" : tstr,
* "model" : tstr,
* "device" : tstr,
- * "vb_state" : "green" / "yellow" / "orange", // Taken from the AVB values
- * "bootloader_state" : "locked" / "unlocked", // Taken from the AVB values
- * "vbmeta_digest": bstr, // Taken from the AVB values
- * ? "os_version" : tstr, // Same as
- * // android.os.Build.VERSION.release
- * // Not optional for TEE.
- * "system_patch_level" : uint, // YYYYMMDD
- * "boot_patch_level" : uint, // YYYYMMDD
- * "vendor_patch_level" : uint, // YYYYMMDD
- * "version" : 2, // The CDDL schema version.
+ * "vb_state" : "green" / "yellow" / "orange", ; Taken from the AVB values
+ * "bootloader_state" : "locked" / "unlocked", ; Taken from the AVB values
+ * "vbmeta_digest": bstr, ; Taken from the AVB values
+ * ? "os_version" : tstr, ; Same as
+ * ; android.os.Build.VERSION.release
+ * ; Not optional for TEE.
+ * "system_patch_level" : uint, ; YYYYMMDD
+ * "boot_patch_level" : uint, ; YYYYMMDD
+ * "vendor_patch_level" : uint, ; YYYYMMDD
* "security_level" : "tee" / "strongbox",
- * "fused": 1 / 0, // 1 if secure boot is enforced for the processor that the IRPC
- * // implementation is contained in. 0 otherwise.
+ * "fused": 1 / 0, ; 1 if secure boot is enforced for the processor that the IRPC
+ * ; implementation is contained in. 0 otherwise.
+ * "cert_type": "widevine" / "keymint"
* }
*/
byte[] deviceInfo;
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
index a29fb08..c2acbed 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -31,26 +31,31 @@
* This interface does not provide any way to use the generated and certified key pairs. It's
* intended to be implemented by a HAL service that does other things with keys (e.g. Keymint).
*
- * The root of trust for secure provisioning is something called the "Boot Certificate Chain", or
- * BCC. The BCC is a chain of public key certificates, represented as COSE_Sign1 objects containing
- * COSE_Key representations of the public keys. The "root" of the BCC is
- * a device-unique public key, denoted DK_pub. All public keys in the BCC are device-unique. The
- * public key from each certificate in the chain is used to sign the next certificate in the
- * chain. The final, "leaf" certificate contains a public key, denoted KM_pub, whose corresponding
- * private key, denoted KM_priv, is available for use by the IRemotelyProvisionedComponent.
+ * The root of trust for secure provisioning is something called the Device Identifier Composition
+ * Engine (DICE) Chain. The DICE Chain is a chain of certificates, represented as COSE_Sign1 objects
+ * containing CBOR Web Tokens (CWT) which have descriptions about the stage of firmware being
+ * signed, including a COSE_Key representation of that stage's public key.
*
- * BCC Design
- * ==========
+ * DICE Chain Design
+ * =================
*
- * The BCC is designed to mirror the boot stages of a device, and to prove the content and integrity
- * of each firmware image. In a proper BCC, each boot stage hashes its own private key with the code
- * and any relevant configuration parameters of the next stage to produce a key pair for the next
- * stage. Each stage also uses its own private key to sign the public key of the next stage,
- * including in the certificate the hash of the next firmware stage, then loads the next stage,
- * passing the private key and certificate to it in a manner that does not leak the private key to
- * later boot stages. The BCC root key pair is generated by immutable code (e.g. ROM), from a
- * device-unique secret. After the device-unique secret is used, it must be made unavailable to any
- * later boot stage.
+ * For a more exhaustive and thorough look at DICE and the implementation used within this protocol,
+ * please see: https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md
+ *
+ * The DICE Chain is designed to mirror the boot stages of a device, and to prove the content and
+ * integrity of each firmware image. In a proper DICE Chain, each boot stage hashes its own private
+ * key material with the code and any relevant configuration parameters of the next stage to produce
+ * a Compound Device Identifier, or CDI, which is used as the secret key material for the next
+ * stage. From the CDI, a key pair - CDI_*_Pub and CDI_*_Priv - is derived and certified for the
+ * next stage by the current stages CDI_*_Priv. The next stage is then loaded and given its CDI and
+ * the DICE certificate chain generated so far in a manner that does not leak the previous stage's
+ * CDI_*_Priv or CDI to later boot stages. The final, "leaf" CDI certificate contains a public key,
+ * denoted CDI_Leaf_Pub, whose corresponding private key, denoted CDI_Leaf_Priv, is available for
+ * use by the IRemotelyProvisionedComponent.
+ *
+ * The root keypair is generated by immutable code (e.g. ROM), from a Unique Device Secret (UDS).
+ * The keypair that is generated from it can be referred to as the UDS_Pub/UDS_Priv keys. After the
+ * device-unique secret is used, it must be made unavailable to any later boot stage.
*
* In this way, booting the device incrementally builds a certificate chain that (a) identifies and
* validates the integrity of every stage and (b) contains a set of public keys that correspond to
@@ -58,64 +63,64 @@
* (given the necessary input), but no stage can compute the secret of any preceding stage. Updating
* the firmware or configuration of any stage changes the key pair of that stage, and of all
* subsequent stages, and no attacker who compromised the previous version of the updated firmware
- * can know or predict the post-update key pairs. It is recommended and expected that the BCC is
- * constructed using the Open Profile for DICE.
+ * can know or predict the post-update key pairs. It is recommended and expected that the DICE Chain
+ * is constructed using the Open Profile for DICE.
*
- * When the provisioning server receives a message signed by KM_priv and containing a BCC that
- * chains from DK_pub to KM_pub, it can be certain that (barring vulnerabilities in some boot
- * stage), the CertificateRequest came from the device associated with DK_pub, running the specific
- * software identified by the certificates in the BCC. If the server has some mechanism for knowing
- * which the DK_pub values of "valid" devices, it can determine whether signing certificates is
- * appropriate.
+ * When the provisioning server receives a message signed by CDI_Leaf_Priv and containing a DICE
+ * chain that chains from UDS_Pub to CDI_Leaf_Pub, it can be certain that (barring vulnerabilities
+ * in some boot stage), the CertificateRequest came from the device associated with UDS_Pub, running
+ * the specific software identified by the certificates in the chain. If the server has some
+ * mechanism for knowing the hash values of compromised stages, it can determine whether signing
+ * certificates is appropriate.
*
- * Degenerate BCCs
- * ===============
+ * Degenerate DICE Chains
+ * ======================
*
- * While a proper BCC, as described above, reflects the complete boot sequence from boot ROM to the
- * secure area image of the IRemotelyProvisionedComponent, it's also possible to use a "degenerate"
- * BCC which consists only of a single, self-signed certificate containing the public key of a
- * hardware-bound key pair. This is an appopriate solution for devices which haven't implemented
- * everything necessary to produce a proper BCC, but can derive a unique key pair in the secure
- * area. In this degenerate case, DK_pub is the same as KM_pub.
+ * While a proper DICE Chain, as described above, reflects the complete boot sequence from boot ROM
+ * to the secure area image of the IRemotelyProvisionedComponent, it's also possible to use a
+ * "degenerate" DICE Chain which consists only of a single, self-signed certificate containing the
+ * public key of a hardware-bound key pair. This is an appopriate solution for devices which haven't
+ * implemented everything necessary to produce a proper DICE Chain, but can derive a unique key pair
+ * in the secure area. In this degenerate case, UDS_Pub is the same as CDI_Leaf_Pub.
*
- * BCC Privacy
- * ===========
+ * DICE Chain Privacy
+ * ==================
*
- * Because the BCC constitutes an unspoofable, device-unique identifier, special care is taken to
- * prevent its availability to entities who may wish to track devices. Two precautions are taken:
+ * Because the DICE Chain constitutes an unspoofable, device-unique identifier, special care is
+ * taken to prevent its availability to entities who may wish to track devices. Three precautions
+ * are taken:
*
- * 1. The BCC is never exported from the IRemotelyProvisionedComponent except in encrypted
- * form. The portion of the CertificateRequest that contains the BCC is encrypted using an
- * Endpoint Encryption Key (EEK). The EEK is provided in the form of a certificate chain whose
- * root must be pre-provisioned into the secure area (hardcoding the roots into the secure area
- * firmware image is a recommended approach). Multiple roots may be provisioned. If the provided
- * EEK does not chain back to this already-known root, the IRemotelyProvisionedComponent must
- * reject it.
+ * 1) The DICE chain is only handled by the native Remote Key Provisioning Daemon (RKPD) service on
+ * the HLOS and is not exposed to apps running on device.
*
- * 2. Precaution 1 above ensures that only an entity with a valid EEK private key can decrypt the
- * BCC. To make it feasible to build a provisioning server which cannot use the BCC to track
- * devices, the CertificateRequest is structured so that the server can be partitioned into two
- * components. The "decrypter" decrypts the BCC, verifies DK_pub and the device's right to
- * receive provisioned certificates, but does not see the public keys to be signed or the
- * resulting certificates. The "certifier" gets informed of the results of the decrypter's
- * validation and sees the public keys to be signed and resulting certificates, but does not see
- * the BCC.
+ * 2) The CDI_Leaf_Priv key cannot be used to sign arbitrary data.
*
- * Test Mode
- * =========
+ * 3) Backend infrastructure does not correlate UDS_Pub with the certificates signed and sent back
+ * to the device.
*
- * The IRemotelyProvisionedComponent supports a test mode, allowing the generation of test key pairs
- * and test CertificateRequests. Test keys/requests are annotated as such, and the BCC used for test
- * CertificateRequests must contain freshly-generated keys, not the real BCC key pairs.
+ * Versioning
+ * ==========
+ * Versions 1 and 2 of the schema, as previously defined in DeviceInfo.aidl, diverge in
+ * functionality from Version 3. Version 3 removes the need to have testMode in function calls and
+ * deprecates the Endpoint Encryption Key (EEK) as well. Vendors implementing Version 1
+ * (Android S/12) or Version 2 (Android T/13) do not need to implement generateCertificateRequestV2.
+ * Vendors implementing Version 3 (Android U/14) need to implement generateCertificateRequestV2.
+ *
+ * For better coverage of changes from version to version, please see RKP_CHANGELOG.md in the root
+ * of the keymint interface directory.
+ *
* @hide
*/
@VintfStability
interface IRemotelyProvisionedComponent {
const int STATUS_FAILED = 1;
const int STATUS_INVALID_MAC = 2;
+ // --------- START: Versions 1 and 2 Only ----------
const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
const int STATUS_INVALID_EEK = 5;
+ // --------- END: Versions 1 and 2 Only ------------
+ const int STATUS_REMOVED = 6;
/**
* @return info which contains information about the underlying IRemotelyProvisionedComponent
@@ -124,11 +129,14 @@
RpcHardwareInfo getHardwareInfo();
/**
- * generateKeyPair generates a new ECDSA P-256 key pair that can be certified. Note that this
- * method only generates ECDSA P-256 key pairs, but the interface can be extended to add methods
- * for generating keys for other algorithms, if necessary.
+ * generateKeyPair generates a new ECDSA P-256 key pair that can be attested by the remote
+ * server.
*
- * @param in boolean testMode indicates whether the generated key is for testing only. Test keys
+ * @param in boolean testMode this field is now deprecated. It is ignored by the implementation
+ * in v3, but retained to simplify backwards compatibility support. V1 and V2
+ * implementations must still respect the testMode flag.
+ *
+ * testMode indicates whether the generated key is for testing only. Test keys
* are marked (see the definition of PublicKey in the MacedPublicKey structure) to
* prevent them from being confused with production keys.
*
@@ -142,6 +150,10 @@
byte[] generateEcdsaP256KeyPair(in boolean testMode, out MacedPublicKey macedPublicKey);
/**
+ * This method has been removed in version 3 of the HAL. The header is kept around for
+ * backwards compatibility purposes. Calling this method should return STATUS_REMOVED on v3.
+ *
+ * For v1 and v2 implementations:
* generateCertificateRequest creates a certificate request to be sent to the provisioning
* server.
*
@@ -167,9 +179,9 @@
*
* EekChain = [ + SignedSignatureKey, SignedEek ]
*
- * SignedSignatureKey = [ // COSE_Sign1
+ * SignedSignatureKey = [ ; COSE_Sign1
* protected: bstr .cbor {
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
+ * 1 : AlgorithmEdDSA / AlgorithmES256, ; Algorithm
* },
* unprotected: {},
* payload: bstr .cbor SignatureKeyEd25519 /
@@ -178,63 +190,58 @@
* bstr ECDSA(.cbor SignatureKeySignatureInput)
* ]
*
- * SignatureKeyEd25519 = { // COSE_Key
- * 1 : 1, // Key type : Octet Key Pair
- * 3 : AlgorithmEdDSA, // Algorithm
- * -1 : 6, // Curve : Ed25519
- * -2 : bstr // Ed25519 public key
+ * SignatureKeyEd25519 = { ; COSE_Key
+ * 1 : 1, ; Key type : Octet Key Pair
+ * 3 : AlgorithmEdDSA, ; Algorithm
+ * -1 : 6, ; Curve : Ed25519
+ * -2 : bstr ; Ed25519 public key
* }
*
* SignatureKeyP256 = {
- * 1 : 2, // Key type : EC2
- * 3 : AlgorithmES256, // Algorithm
- * -1 : 1, // Curve: P256
- * -2 : bstr, // X coordinate
- * -3 : bstr // Y coordinate
+ * 1 : 2, ; Key type : EC2
+ * 3 : AlgorithmES256, ; Algorithm
+ * -1 : 1, ; Curve: P256
+ * -2 : bstr, ; X coordinate
+ * -3 : bstr ; Y coordinate
* }
*
* SignatureKeySignatureInput = [
* context: "Signature1",
- * body_protected: bstr .cbor {
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
- * },
+ * body_protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
* external_aad: bstr .size 0,
* payload: bstr .cbor SignatureKeyEd25519 /
* bstr .cbor SignatureKeyP256
* ]
*
- * SignedEek = [ // COSE_Sign1
- * protected: bstr .cbor {
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
- * },
+ * ; COSE_Sign1
+ * SignedEek = [
+ * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
* unprotected: {},
* payload: bstr .cbor EekX25519 / .cbor EekP256,
* signature: bstr PureEd25519(.cbor EekSignatureInput) /
* bstr ECDSA(.cbor EekSignatureInput)
* ]
*
- * EekX25519 = { // COSE_Key
- * 1 : 1, // Key type : Octet Key Pair
- * 2 : bstr // KID : EEK ID
- * 3 : -25, // Algorithm : ECDH-ES + HKDF-256
- * -1 : 4, // Curve : X25519
- * -2 : bstr // Ed25519 public key
+ * EekX25519 = { ; COSE_Key
+ * 1 : 1, ; Key type : Octet Key Pair
+ * 2 : bstr ; KID : EEK ID
+ * 3 : -25, ; Algorithm : ECDH-ES + HKDF-256
+ * -1 : 4, ; Curve : X25519
+ * -2 : bstr ; Ed25519 public key
* }
*
- * EekP256 = { // COSE_Key
- * 1 : 2, // Key type : EC2
- * 2 : bstr // KID : EEK ID
- * 3 : -25, // Algorithm : ECDH-ES + HKDF-256
- * -1 : 1, // Curve : P256
- * -2 : bstr // Sender X coordinate
- * -3 : bstr // Sender Y coordinate
+ * EekP256 = { ; COSE_Key
+ * 1 : 2, ; Key type : EC2
+ * 2 : bstr ; KID : EEK ID
+ * 3 : -25, ; Algorithm : ECDH-ES + HKDF-256
+ * -1 : 1, ; Curve : P256
+ * -2 : bstr ; Sender X coordinate
+ * -3 : bstr ; Sender Y coordinate
* }
*
* EekSignatureInput = [
* context: "Signature1",
- * body_protected: bstr .cbor {
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
- * },
+ * body_protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
* external_aad: bstr .size 0,
* payload: bstr .cbor EekX25519 / .cbor EekP256
* ]
@@ -270,25 +277,188 @@
* Where EK_mac is an ephemeral MAC key, found in ProtectedData (see below). The MACed
* data is the "tag" field of a COSE_Mac0 structure like:
*
- * MacedKeys = [ // COSE_Mac0
+ * MacedKeys = [ ; COSE_Mac0
* protected : bstr .cbor {
- * 1 : 5, // Algorithm : HMAC-256
+ * 1 : 5, ; Algorithm : HMAC-256
* },
* unprotected : {},
- * // Payload is PublicKeys from keysToSign argument, in provided order.
+ * ; Payload is PublicKeys from keysToSign argument, in provided order.
* payload: bstr .cbor [ * PublicKey ],
* tag: bstr
* ]
*
* KeysToMacStructure = [
* context : "MAC0",
- * protected : bstr .cbor { 1 : 5 }, // Algorithm : HMAC-256
+ * protected : bstr .cbor { 1 : 5 }, ; Algorithm : HMAC-256
* external_aad : bstr .size 0,
- * // Payload is PublicKeys from keysToSign argument, in provided order.
+ * ; Payload is PublicKeys from keysToSign argument, in provided order.
* payload : bstr .cbor [ * PublicKey ]
* ]
*/
byte[] generateCertificateRequest(in boolean testMode, in MacedPublicKey[] keysToSign,
in byte[] endpointEncryptionCertChain, in byte[] challenge, out DeviceInfo deviceInfo,
out ProtectedData protectedData);
+
+ /**
+ * generateCertificateRequestV2 creates a certificate signing request to be sent to the
+ * provisioning server.
+ *
+ * @param in MacedPublicKey[] keysToSign contains the set of keys to certify. The
+ * IRemotelyProvisionedComponent must validate the MACs on each key. If any entry in the
+ * array lacks a valid MAC, the method must return STATUS_INVALID_MAC.
+ *
+ * @param in challenge contains a byte string from the provisioning server which will be
+ * included in the signed data of the CSR structure. Different provisioned backends may
+ * use different semantic data for this field, but the supported sizes must be between 32
+ * and 64 bytes, inclusive.
+ *
+ * @return the following CBOR Certificate Signing Request (Csr) serialized into a byte array:
+ *
+ * Csr = [
+ * version: 3, ; The CDDL Schema version.
+ * UdsCerts,
+ * DiceCertChain,
+ * SignedData
+ * ]
+ *
+ * ; COSE_Sign1 (untagged)
+ * SignedData = [
+ * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
+ * unprotected: {},
+ * payload: bstr .cbor SignedDataPayload,
+ * signature: bstr ; PureEd25519(CDI_Leaf_Priv, bstr .cbor SignedDataSigStruct) /
+ * ; ECDSA(CDI_Leaf_Priv, bstr .cbor SignedDataSigStruct)
+ * ]
+ *
+ * ; Sig_structure for SignedData
+ * SignedDataSigStruct = [
+ * context: "Signature1",
+ * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
+ * external_aad: bstr .size 0,
+ * payload: bstr .cbor SignedDataPayload
+ * ]
+ *
+ * SignedDataPayload = [ ; CBOR Array defining the payload for SignedData
+ * DeviceInfo, ; Defined in DeviceInfo.aidl
+ * challenge: bstr .size (32..64), ; Provided by the method parameters
+ * KeysToSign, ; Provided by the method parameters
+ * ]
+ *
+ * KeysToSign = [ * PublicKey ] ; Please see MacedPublicKey.aidl for the PublicKey definition.
+ *
+ * ; UdsCerts allows the platform to provide additional certifications for the UDS_Pub. For
+ * ; example, this could be provided by the hardware vendor, who certifies all of their chips.
+ * ; The SignerName is a free-form string describing who generated the signature. The root
+ * ; certificate will need to be communicated to the verifier out of band, along with the
+ * ; SignerName that is expected for the given root certificate.
+ * UdsCerts = {
+ * * SignerName => UdsCertChain
+ * }
+ *
+ * ; SignerName is a string identifier that indicates both the signing authority as
+ * ; well as the format of the UdsCertChain
+ * SignerName = tstr
+ *
+ * UdsCertChain = [
+ * 2* X509Certificate ; Root -> ... -> Leaf. "Root" is the vendor self-signed
+ * ; cert, "Leaf" contains UDS_Public. There may also be
+ * ; intermediate certificates between Root and Leaf.
+ * ]
+ *
+ * ; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or edDSA)
+ * X509Certificate = bstr
+ *
+ * ; The DICE Chain contains measurements about the device firmware.
+ * ; The first entry in the DICE Chain is the UDS_Pub, encoded as a COSE_key. All entries
+ * ; after the first describe a link in the boot chain (e.g. bootloaders: BL1, BL2, ... BLN)
+ * ; Note that there is no DiceChainEntry for UDS_pub, only a "bare" COSE_key.
+ * DiceCertChain = [
+ * PubKeyEd25519 / PubKeyECDSA256, ; UDS_Pub
+ * + DiceChainEntry, ; First CDI_Certificate -> Last CDI_Certificate
+ * ; Last certificate corresponds to KeyMint's DICE key.
+ * ]
+ *
+ * ; This is the signed payload for each entry in the DCC. Note that the "Configuration
+ * ; Input Values" described by the Open Profile are not used here. Instead, the Dcc
+ * ; defines its own configuration values for the Configuration Descriptor field. See
+ * ; the Open Profile for DICE for more details on the fields. SHA256 and SHA512 are acceptable
+ * ; hash algorithms. The digest bstr values in the payload are the digest values without any
+ * ; padding. Note that for SHA256, this implies the digest bstr is 32 bytes. This is an
+ * ; intentional, minor deviation from Open Profile for DICE, which specifies all digests are
+ * ; 64 bytes.
+ * DiceChainEntryPayload = { ; CWT [RFC8392]
+ * 1 : tstr, ; Issuer
+ * 2 : tstr, ; Subject
+ * -4670552 : bstr .cbor PubKeyEd25519 /
+ * bstr .cbor PubKeyECDSA256, ; Subject Public Key
+ * -4670553 : bstr ; Key Usage
+ *
+ * ; NOTE: All of the following fields may be omitted for a "Degenerate DICE Chain", as
+ * ; described above.
+ * -4670545 : bstr, ; Code Hash
+ * ? -4670546 : bstr, ; Code Descriptor
+ * ? -4670547 : bstr, ; Configuration Hash
+ * -4670548 : bstr .cbor { ; Configuration Descriptor
+ * ? -70002 : tstr, ; Component name
+ * ? -70003 : int, ; Firmware version
+ * ? -70004 : null, ; Resettable
+ * },
+ * -4670549 : bstr, ; Authority Hash
+ * ? -4670550 : bstr, ; Authority Descriptor
+ * -4670551 : bstr, ; Mode
+ * }
+ *
+ * ; Each entry in the Dcc is a DiceChainEntryPayload signed by the key from the previous entry
+ * ; in the Dcc array.
+ * DiceChainEntry = [ ; COSE_Sign1 (untagged)
+ * protected : bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
+ * unprotected: {},
+ * payload: bstr .cbor DiceChainEntryPayload,
+ * signature: bstr ; PureEd25519(SigningKey, bstr .cbor DiceChainEntryInput) /
+ * ; ECDSA(SigningKey, bstr .cbor DiceChainEntryInput)
+ * ; See RFC 8032 for details of how to encode the signature value
+ * ; for Ed25519.
+ * ]
+ *
+ * DiceChainEntryInput = [
+ * context: "Signature1",
+ * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
+ * external_aad: bstr .size 0,
+ * payload: bstr .cbor DiceChainEntryPayload
+ * ]
+ *
+ * ; The following section defines some types that are reused throughout the above
+ * ; data structures.
+ * PubKeyX25519 = { ; COSE_Key
+ * 1 : 1, ; Key type : Octet Key Pair
+ * -1 : 4, ; Curve : X25519
+ * -2 : bstr ; Sender X25519 public key
+ * }
+ *
+ * PubKeyEd25519 = { ; COSE_Key
+ * 1 : 1, ; Key type : octet key pair
+ * 3 : AlgorithmEdDSA, ; Algorithm : EdDSA
+ * -1 : 6, ; Curve : Ed25519
+ * -2 : bstr ; X coordinate, little-endian
+ * }
+ *
+ * PubKeyEcdhP256 = { ; COSE_Key
+ * 1 : 2, ; Key type : EC2
+ * -1 : 1, ; Curve : P256
+ * -2 : bstr ; Sender X coordinate
+ * -3 : bstr ; Sender Y coordinate
+ * }
+ *
+ * PubKeyECDSA256 = { ; COSE_Key
+ * 1 : 2, ; Key type : EC2
+ * 3 : AlgorithmES256, ; Algorithm : ECDSA w/ SHA-256
+ * -1 : 1, ; Curve: P256
+ * -2 : bstr, ; X coordinate
+ * -3 : bstr ; Y coordinate
+ * }
+ *
+ * AlgorithmES256 = -7
+ * AlgorithmEdDSA = -8
+ */
+ byte[] generateCertificateRequestV2(in MacedPublicKey[] keysToSign, in byte[] challenge);
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
index ae75579..4c2be89 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
@@ -158,12 +158,23 @@
* Failed (3),
* }
*
+ * -- Note that the AuthorizationList SEQUENCE is also used in IKeyMintDevice::importWrappedKey
+ * -- as a way of describing the authorizations associated with a key that is being securely
+ * -- imported. As such, it includes the ability to describe tags that are only relevant for
+ * -- symmetric keys, and which will never appear in the attestation extension of an X.509
+ * -- certificate that holds the public key part of an asymmetric keypair. Importing a wrapped
+ * -- key also allows the use of Tag::USER_SECURE_ID, which is never included in an attestation
+ * -- extension because it has no meaning off-device.
+ *
* AuthorizationList ::= SEQUENCE {
* purpose [1] EXPLICIT SET OF INTEGER OPTIONAL,
* algorithm [2] EXPLICIT INTEGER OPTIONAL,
* keySize [3] EXPLICIT INTEGER OPTIONAL,
+ * blockMode [4] EXPLICIT SET OF INTEGER OPTIONAL, -- symmetric only
* digest [5] EXPLICIT SET OF INTEGER OPTIONAL,
* padding [6] EXPLICIT SET OF INTEGER OPTIONAL,
+ * callerNonce [7] EXPLICIT NULL OPTIONAL, -- symmetric only
+ * minMacLength [8] EXPLICIT INTEGER OPTIONAL, -- symmetric only
* ecCurve [10] EXPLICIT INTEGER OPTIONAL,
* rsaPublicExponent [200] EXPLICIT INTEGER OPTIONAL,
* mgfDigest [203] EXPLICIT SET OF INTEGER OPTIONAL,
@@ -173,6 +184,7 @@
* originationExpireDateTime [401] EXPLICIT INTEGER OPTIONAL,
* usageExpireDateTime [402] EXPLICIT INTEGER OPTIONAL,
* usageCountLimit [405] EXPLICIT INTEGER OPTIONAL,
+ * userSecureId [502] EXPLICIT INTEGER OPTIONAL, -- only used on import
* noAuthRequired [503] EXPLICIT NULL OPTIONAL,
* userAuthType [504] EXPLICIT INTEGER OPTIONAL,
* authTimeout [505] EXPLICIT INTEGER OPTIONAL,
diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
index ad97443..275e322 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -28,21 +28,22 @@
* only to the secure environment, as proof that the public key was generated by that
* environment. In CDDL, assuming the contained key is a P-256 public key:
*
- * MacedPublicKey = [ // COSE_Mac0
- * protected: bstr .cbor { 1 : 5}, // Algorithm : HMAC-256
+ * MacedPublicKey = [ ; COSE_Mac0
+ * protected: bstr .cbor { 1 : 5}, ; Algorithm : HMAC-256
* unprotected: { },
* payload : bstr .cbor PublicKey,
* tag : bstr HMAC-256(K_mac, MAC_structure)
* ]
*
- * PublicKey = { // COSE_Key
- * 1 : 2, // Key type : EC2
- * 3 : -7, // Algorithm : ES256
- * -1 : 1, // Curve : P256
- * -2 : bstr, // X coordinate, little-endian
- * -3 : bstr, // Y coordinate, little-endian
- * ? -70000 : nil // Presence indicates this is a test key. If set, K_mac is
- * // all zeros.
+ * ; NOTE: -70000 is deprecated for v3 HAL implementations.
+ * PublicKey = { ; COSE_Key
+ * 1 : 2, ; Key type : EC2
+ * 3 : -7, ; Algorithm : ES256
+ * -1 : 1, ; Curve : P256
+ * -2 : bstr, ; X coordinate, little-endian
+ * -3 : bstr, ; Y coordinate, little-endian
+ * -70000 : nil ; Presence indicates this is a test key. If set, K_mac is
+ * ; all zeros.
* },
*
* MAC_structure = [
@@ -51,9 +52,6 @@
* external_aad : bstr .size 0,
* payload : bstr .cbor PublicKey
* ]
- *
- * if a non-P256 public key were contained, the contents of the PublicKey map would change a
- * little; see RFC 8152 for details.
*/
byte[] macedKey;
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
index 8b3875b..d59508b 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
@@ -17,9 +17,13 @@
package android.hardware.security.keymint;
/**
+ * NOTE: ProtectedData has been removed as of version 3, but is kept around for backwards
+ * compatibility reasons. For versions 1 and 2:
+ *
* ProtectedData contains the encrypted BCC and the ephemeral MAC key used to
* authenticate the keysToSign (see keysToSignMac output argument of
* IRemotelyProvisionedComponent.generateCertificateRequest).
+ *
* @hide
*/
@VintfStability
@@ -33,201 +37,201 @@
* - None of the CBOR in ProtectedData uses CBOR tags. If an implementation includes
* tags, parsers may reject the data.
*
- * ProtectedData = [ // COSE_Encrypt
+ * ProtectedData = [ ; COSE_Encrypt
* protected: bstr .cbor {
- * 1 : 3 // Algorithm : AES-GCM 256
+ * 1 : 3 ; Algorithm : AES-GCM 256
* },
* unprotected: {
- * 5 : bstr .size 12 // IV
+ * 5 : bstr .size 12 ; IV
* },
- * ciphertext: bstr, // AES-GCM-256(K, .cbor ProtectedDataPayload)
- * // Where the encryption key 'K' is derived as follows:
- * // ikm = ECDH(EEK_pub, Ephemeral_priv)
- * // salt = null
- * // info = .cbor Context (see below)
- * // K = HKDF-SHA-256(ikm, salt, info)
+ * ciphertext: bstr, ; AES-GCM-256(K, .cbor ProtectedDataPayload)
+ * ; Where the encryption key 'K' is derived as follows:
+ * ; ikm = ECDH(EEK_pub, Ephemeral_priv)
+ * ; salt = null
+ * ; info = .cbor Context (see below)
+ * ; K = HKDF-SHA-256(ikm, salt, info)
* recipients : [
- * [ // COSE_Recipient
+ * [ ; COSE_Recipient
* protected : bstr .cbor {
- * 1 : -25 // Algorithm : ECDH-ES + HKDF-256
+ * 1 : -25 ; Algorithm : ECDH-ES + HKDF-256
* },
* unprotected : {
- * -1 : PubKeyX25519 / PubKeyEcdhP256 // Ephemeral_pub
- * 4 : bstr, // KID : EEK ID
+ * -1 : PubKeyX25519 / PubKeyEcdhP256 ; Ephemeral_pub
+ * 4 : bstr, ; KID : EEK ID
* },
* ciphertext : nil
* ]
* ]
* ]
*
- * // The COSE_KDF_Context that is used to derive the ProtectedData encryption key with
- * // HKDF. See details on use in ProtectedData comments above.
+ * ; The COSE_KDF_Context that is used to derive the ProtectedData encryption key with
+ * ; HKDF. See details on use in ProtectedData comments above.
* Context = [
- * AlgorithmID : 3 // AES-GCM 256
+ * AlgorithmID : 3 ; AES-GCM 256
* PartyUInfo : [
* identity : bstr "client"
* nonce : bstr .size 0,
- * other : bstr // Ephemeral_pub
+ * other : bstr ; Ephemeral_pub
* ],
* PartyVInfo : [
* identity : bstr "server",
* nonce : bstr .size 0,
- * other : bstr // EEK pubkey
+ * other : bstr ; EEK pubkey
* ],
* SuppPubInfo : [
- * 256, // Output key length
+ * 256, ; Output key length
* protected : bstr .size 0
* ]
* ]
*
- * // The data that is encrypted and included in ProtectedData ciphertext (see above).
+ * ; The data that is encrypted and included in ProtectedData ciphertext (see above).
* ProtectedDataPayload [
* SignedMac,
* Bcc,
* ? AdditionalDKSignatures,
* ]
*
- * // AdditionalDKSignatures allows the platform to provide additional certifications
- * // for the DK_pub. For example, this could be provided by the hardware vendor, who
- * // certifies all of their devices. The SignerName is a free-form string describing
- * // who generated the signature.
+ * ; AdditionalDKSignatures allows the platform to provide additional certifications
+ * ; for the DK_pub. For example, this could be provided by the hardware vendor, who
+ * ; certifies all of their devices. The SignerName is a free-form string describing
+ * ; who generated the signature.
* AdditionalDKSignatures = {
* + SignerName => DKCertChain
* }
*
- * // SignerName is a string identifier that indicates both the signing authority as
- * // well as the format of the DKCertChain
+ * ; SignerName is a string identifier that indicates both the signing authority as
+ * ; well as the format of the DKCertChain
* SignerName = tstr
*
* DKCertChain = [
- * 2* X509Certificate // Root -> ... -> Leaf. "Root" is the vendor self-signed
- * // cert, "Leaf" contains DK_pub. There may also be
- * // intermediate certificates between Root and Leaf.
+ * 2* X509Certificate ; Root -> ... -> Leaf. "Root" is the vendor self-signed
+ * ; cert, "Leaf" contains DK_pub. There may also be
+ * ; intermediate certificates between Root and Leaf.
* ]
*
- * // A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or edDSA)
+ * ; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or edDSA)
* X509Certificate = bstr
*
- * // The SignedMac, which authenticates the MAC key that is used to authenticate the
- * // keysToSign.
- * SignedMac = [ // COSE_Sign1
- * bstr .cbor { // Protected params
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
+ * ; The SignedMac, which authenticates the MAC key that is used to authenticate the
+ * ; keysToSign.
+ * SignedMac = [ ; COSE_Sign1
+ * bstr .cbor { ; Protected params
+ * 1 : AlgorithmEdDSA / AlgorithmES256, ; Algorithm
* },
- * {}, // Unprotected params
- * bstr .size 32, // Payload: MAC key
- * bstr // PureEd25519(KM_priv, bstr .cbor SignedMac_structure) /
- * // ECDSA(KM_priv, bstr .cbor SignedMac_structure)
+ * {}, ; Unprotected params
+ * bstr .size 32, ; Payload: MAC key
+ * bstr ; PureEd25519(KM_priv, bstr .cbor SignedMac_structure) /
+ * ; ECDSA(KM_priv, bstr .cbor SignedMac_structure)
* ]
*
- * SignedMac_structure = [ // COSE Sig_structure
+ * SignedMac_structure = [ ; COSE Sig_structure
* "Signature1",
- * bstr .cbor { // Protected params
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
+ * bstr .cbor { ; Protected params
+ * 1 : AlgorithmEdDSA / AlgorithmES256, ; Algorithm
* },
* bstr .cbor SignedMacAad,
- * bstr .size 32 // MAC key
+ * bstr .size 32 ; MAC key
* ]
*
* SignedMacAad = [
- * challenge : bstr .size (32..64), // Size between 32 - 64
- * // bytes inclusive
+ * challenge : bstr .size (32..64), ; Size between 32 - 64
+ * ; bytes inclusive
* VerifiedDeviceInfo,
- * tag: bstr // This is the tag from COSE_Mac0 of
- * // KeysToCertify, to tie the key set to
- * // the signature.
+ * tag: bstr ; This is the tag from COSE_Mac0 of
+ * ; KeysToCertify, to tie the key set to
+ * ; the signature.
* ]
*
- * VerifiedDeviceInfo = DeviceInfo // See DeviceInfo.aidl
+ * VerifiedDeviceInfo = DeviceInfo ; See DeviceInfo.aidl
*
- * // The BCC is the boot certificate chain, containing measurements about the device
- * // boot chain. The BCC generally follows the Open Profile for DICE specification at
- * // https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md.
- * //
- * // The first entry in the Bcc is the DK_pub, encoded as a COSE_key. All entries after
- * // the first describe a link in the boot chain (e.g. bootloaders: BL1, BL2, ... BLN).
- * // Note that there is no BccEntry for DK_pub, only a "bare" COSE_key.
+ * ; The BCC is the boot certificate chain, containing measurements about the device
+ * ; boot chain. The BCC generally follows the Open Profile for DICE specification at
+ * ; https:;pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md.
+ * ;
+ * ; The first entry in the Bcc is the DK_pub, encoded as a COSE_key. All entries after
+ * ; the first describe a link in the boot chain (e.g. bootloaders: BL1, BL2, ... BLN).
+ * ; Note that there is no BccEntry for DK_pub, only a "bare" COSE_key.
* Bcc = [
- * PubKeyEd25519 / PubKeyECDSA256, // DK_pub
- * + BccEntry, // Root -> leaf (KM_pub)
+ * PubKeyEd25519 / PubKeyECDSA256, ; DK_pub
+ * + BccEntry, ; Root -> leaf (KM_pub)
* ]
*
- * // This is the signed payload for each entry in the Bcc. Note that the "Configuration
- * // Input Values" described by the Open Profile are not used here. Instead, the Bcc
- * // defines its own configuration values for the Configuration Descriptor field. See
- * // the Open Profile for DICE for more details on the fields. All hashes are SHA256.
- * BccPayload = { // CWT [RFC8392]
- * 1 : tstr, // Issuer
- * 2 : tstr, // Subject
+ * ; This is the signed payload for each entry in the Bcc. Note that the "Configuration
+ * ; Input Values" described by the Open Profile are not used here. Instead, the Bcc
+ * ; defines its own configuration values for the Configuration Descriptor field. See
+ * ; the Open Profile for DICE for more details on the fields. All hashes are SHA256.
+ * BccPayload = { ; CWT [RFC8392]
+ * 1 : tstr, ; Issuer
+ * 2 : tstr, ; Subject
* -4670552 : bstr .cbor PubKeyEd25519 /
- * bstr .cbor PubKeyECDSA256, // Subject Public Key
- * -4670553 : bstr // Key Usage
+ * bstr .cbor PubKeyECDSA256, ; Subject Public Key
+ * -4670553 : bstr ; Key Usage
*
- * // NOTE: All of the following fields may be omitted for a "Degenerate BCC", as
- * // described by IRemotelyProvisionedComponent.aidl.
- * -4670545 : bstr, // Code Hash
- * ? -4670546 : bstr, // Code Descriptor
- * ? -4670547 : bstr, // Configuration Hash
- * -4670548 : bstr .cbor { // Configuration Descriptor
- * ? -70002 : tstr, // Component name
- * ? -70003 : int, // Firmware version
- * ? -70004 : null, // Resettable
+ * ; NOTE: All of the following fields may be omitted for a "Degenerate BCC", as
+ * ; described by IRemotelyProvisionedComponent.aidl.
+ * -4670545 : bstr, ; Code Hash
+ * ? -4670546 : bstr, ; Code Descriptor
+ * ? -4670547 : bstr, ; Configuration Hash
+ * -4670548 : bstr .cbor { ; Configuration Descriptor
+ * ? -70002 : tstr, ; Component name
+ * ? -70003 : int, ; Firmware version
+ * ? -70004 : null, ; Resettable
* },
- * -4670549 : bstr, // Authority Hash
- * ? -4670550 : bstr, // Authority Descriptor
- * -4670551 : bstr, // Mode
+ * -4670549 : bstr, ; Authority Hash
+ * ? -4670550 : bstr, ; Authority Descriptor
+ * -4670551 : bstr, ; Mode
* }
*
- * // Each entry in the Bcc is a BccPayload signed by the key from the previous entry
- * // in the Bcc array.
- * BccEntry = [ // COSE_Sign1 (untagged)
+ * ; Each entry in the Bcc is a BccPayload signed by the key from the previous entry
+ * ; in the Bcc array.
+ * BccEntry = [ ; COSE_Sign1 (untagged)
* protected : bstr .cbor {
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
+ * 1 : AlgorithmEdDSA / AlgorithmES256, ; Algorithm
* },
* unprotected: {},
* payload: bstr .cbor BccPayload,
- * signature: bstr // PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
- * // ECDSA(SigningKey, bstr .cbor BccEntryInput)
- * // See RFC 8032 for details of how to encode the signature value for Ed25519.
+ * signature: bstr ; PureEd25519(SigningKey, bstr .cbor BccEntryInput) /
+ * ; ECDSA(SigningKey, bstr .cbor BccEntryInput)
+ * ; See RFC 8032 for details of how to encode the signature value for Ed25519.
* ]
*
* BccEntryInput = [
* context: "Signature1",
* protected: bstr .cbor {
- * 1 : AlgorithmEdDSA / AlgorithmES256, // Algorithm
+ * 1 : AlgorithmEdDSA / AlgorithmES256, ; Algorithm
* },
* external_aad: bstr .size 0,
* payload: bstr .cbor BccPayload
* ]
*
- * // The following section defines some types that are reused throughout the above
- * // data structures.
- * PubKeyX25519 = { // COSE_Key
- * 1 : 1, // Key type : Octet Key Pair
- * -1 : 4, // Curve : X25519
- * -2 : bstr // Sender X25519 public key
+ * ; The following section defines some types that are reused throughout the above
+ * ; data structures.
+ * PubKeyX25519 = { ; COSE_Key
+ * 1 : 1, ; Key type : Octet Key Pair
+ * -1 : 4, ; Curve : X25519
+ * -2 : bstr ; Sender X25519 public key
* }
*
- * PubKeyEd25519 = { // COSE_Key
- * 1 : 1, // Key type : octet key pair
- * 3 : AlgorithmEdDSA, // Algorithm : EdDSA
- * -1 : 6, // Curve : Ed25519
- * -2 : bstr // X coordinate, little-endian
+ * PubKeyEd25519 = { ; COSE_Key
+ * 1 : 1, ; Key type : octet key pair
+ * 3 : AlgorithmEdDSA, ; Algorithm : EdDSA
+ * -1 : 6, ; Curve : Ed25519
+ * -2 : bstr ; X coordinate, little-endian
* }
*
- * PubKeyEcdhP256 = { // COSE_Key
- * 1 : 2, // Key type : EC2
- * -1 : 1, // Curve : P256
- * -2 : bstr // Sender X coordinate
- * -3 : bstr // Sender Y coordinate
+ * PubKeyEcdhP256 = { ; COSE_Key
+ * 1 : 2, ; Key type : EC2
+ * -1 : 1, ; Curve : P256
+ * -2 : bstr ; Sender X coordinate
+ * -3 : bstr ; Sender Y coordinate
* }
*
- * PubKeyECDSA256 = { // COSE_Key
- * 1 : 2, // Key type : EC2
- * 3 : AlgorithmES256, // Algorithm : ECDSA w/ SHA-256
- * -1 : 1, // Curve: P256
- * -2 : bstr, // X coordinate
- * -3 : bstr // Y coordinate
+ * PubKeyECDSA256 = { ; COSE_Key
+ * 1 : 2, ; Key type : EC2
+ * 3 : AlgorithmES256, ; Algorithm : ECDSA w/ SHA-256
+ * -1 : 1, ; Curve: P256
+ * -2 : bstr, ; X coordinate
+ * -3 : bstr ; Y coordinate
* }
*
* AlgorithmES256 = -7
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 871a1ac..47361d5 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -274,25 +274,10 @@
USAGE_EXPIRE_DATETIME = TagType.DATE | 402,
/**
- * Tag::MIN_SECONDS_BETWEEN_OPS specifies the minimum amount of time that elapses between
- * allowed operations using a key. This can be used to rate-limit uses of keys in contexts
- * where unlimited use may enable brute force attacks.
+ * OBSOLETE: Do not use.
*
- * The value is a 32-bit integer representing seconds between allowed operations.
- *
- * When a key with this tag is used in an operation, the IKeyMintDevice must start a timer
- * during the finish() or abort() call. Any call to begin() that is received before the timer
- * indicates that the interval specified by Tag::MIN_SECONDS_BETWEEN_OPS has elapsed must fail
- * with ErrorCode::KEY_RATE_LIMIT_EXCEEDED. This implies that the IKeyMintDevice must keep a
- * table of use counters for keys with this tag. Because memory is often limited, this table
- * may have a fixed maximum size and KeyMint may fail operations that attempt to use keys with
- * this tag when the table is full. The table must accommodate at least 8 in-use keys and
- * aggressively reuse table slots when key minimum-usage intervals expire. If an operation
- * fails because the table is full, KeyMint returns ErrorCode::TOO_MANY_OPERATIONS.
- *
- * Must be hardware-enforced.
- *
- * TODO(b/191738660): Remove in KeyMint V2. Currently only used for FDE.
+ * This tag value is included for historical reason, as it was present in Keymaster.
+ * KeyMint implementations do not need to support this tag.
*/
MIN_SECONDS_BETWEEN_OPS = TagType.UINT | 403,
@@ -898,8 +883,12 @@
STORAGE_KEY = TagType.BOOL | 722,
/**
- * OBSOLETE: Do not use. See IKeyMintOperation.updateAad instead.
- * TODO(b/191738660): Remove in KeyMint v2.
+ * OBSOLETE: Do not use.
+ *
+ * This tag value is included for historical reasons -- in Keymaster it was used to hold
+ * associated data for AEAD encryption, as an additional parameter to
+ * IKeymasterDevice::finish(). In KeyMint the IKeyMintOperation::updateAad() method is used for
+ * this.
*/
ASSOCIATED_DATA = TagType.BYTES | 1000,
@@ -938,10 +927,12 @@
RESET_SINCE_ID_ROTATION = TagType.BOOL | 1004,
/**
- * OBSOLETE: Do not use. See the authToken parameter for IKeyMintDevice::begin and for
- * IKeyMintOperation methods instead.
+ * OBSOLETE: Do not use.
*
- * TODO(b/191738660): Delete when keystore1 is deleted.
+ * This tag value is included for historical reasons -- in Keymaster it was used to hold
+ * a confirmation token as an additional parameter to
+ * IKeymasterDevice::finish(). In KeyMint the IKeyMintOperation::finish() method includes
+ * a confirmationToken argument for this.
*/
CONFIRMATION_TOKEN = TagType.BYTES | 1005,
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
index 7184613..e1f65fe 100644
--- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <memory>
+#include <string>
#define LOG_TAG "VtsRemotelyProvisionableComponentTests"
#include <AndroidRemotelyProvisionedComponentDevice.h>
@@ -30,6 +32,7 @@
#include <openssl/ec_key.h>
#include <openssl/x509.h>
#include <remote_prov/remote_prov_utils.h>
+#include <optional>
#include <set>
#include <vector>
@@ -57,22 +60,6 @@
using namespace remote_prov;
using namespace keymaster;
-std::set<std::string> getAllowedVbStates() {
- return {"green", "yellow", "orange"};
-}
-
-std::set<std::string> getAllowedBootloaderStates() {
- return {"locked", "unlocked"};
-}
-
-std::set<std::string> getAllowedSecurityLevels() {
- return {"tee", "strongbox"};
-}
-
-std::set<std::string> getAllowedAttIdStates() {
- return {"locked", "open"};
-}
-
bytevec string_to_bytevec(const char* s) {
const uint8_t* p = reinterpret_cast<const uint8_t*>(s);
return bytevec(p, p + strlen(s));
@@ -382,157 +369,6 @@
}
}
- ErrMsgOr<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& senderPubkey) {
- if (rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_25519 ||
- rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_NONE) {
- return x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey,
- senderPubkey->first, false /* senderIsA */);
- } else {
- return ECDH_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey,
- senderPubkey->first, false /* senderIsA */);
- }
- }
-
- void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
- const bytevec& keysToSignMac, const ProtectedData& protectedData,
- std::vector<BccEntryData>* bccOutput = nullptr) {
- auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
- ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
- ASSERT_TRUE(parsedProtectedData->asArray());
- ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
-
- auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
- ASSERT_TRUE(senderPubkey) << senderPubkey.message();
- EXPECT_EQ(senderPubkey->second, eekId_);
-
- auto sessionKey = getSessionKey(senderPubkey);
- ASSERT_TRUE(sessionKey) << sessionKey.message();
-
- auto protectedDataPayload =
- decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
- ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
-
- auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
- ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
- ASSERT_TRUE(parsedPayload->asArray());
- // Strongbox may contain additional certificate chain.
- EXPECT_LE(parsedPayload->asArray()->size(), 3U);
-
- auto& signedMac = parsedPayload->asArray()->get(0);
- auto& bcc = parsedPayload->asArray()->get(1);
- ASSERT_TRUE(signedMac && signedMac->asArray());
- ASSERT_TRUE(bcc && bcc->asArray());
-
- // BCC is [ pubkey, + BccEntry]
- auto bccContents = validateBcc(bcc->asArray());
- ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
- ASSERT_GT(bccContents->size(), 0U);
-
- auto [deviceInfoMap, __2, deviceInfoErrMsg] = cppbor::parse(deviceInfo.deviceInfo);
- ASSERT_TRUE(deviceInfoMap) << "Failed to parse deviceInfo: " << deviceInfoErrMsg;
- ASSERT_TRUE(deviceInfoMap->asMap());
- checkDeviceInfo(deviceInfoMap->asMap(), deviceInfo.deviceInfo);
- auto& signingKey = bccContents->back().pubKey;
- deviceInfoMap->asMap()->canonicalize();
- auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey,
- cppbor::Array() // SignedMacAad
- .add(challenge_)
- .add(std::move(deviceInfoMap))
- .add(keysToSignMac)
- .encode());
- ASSERT_TRUE(macKey) << macKey.message();
-
- auto coseMac0 = cppbor::Array()
- .add(cppbor::Map() // protected
- .add(ALGORITHM, HMAC_256)
- .canonicalize()
- .encode())
- .add(cppbor::Map()) // unprotected
- .add(keysToSign.encode()) // payload (keysToSign)
- .add(keysToSignMac); // tag
-
- auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
- ASSERT_TRUE(macPayload) << macPayload.message();
-
- if (bccOutput) {
- *bccOutput = std::move(*bccContents);
- }
- }
-
- void checkType(const cppbor::Map* devInfo, uint8_t majorType, std::string entryName) {
- const auto& val = devInfo->get(entryName);
- ASSERT_TRUE(val) << entryName << " does not exist";
- ASSERT_EQ(val->type(), majorType) << entryName << " has the wrong type.";
- switch (majorType) {
- case cppbor::TSTR:
- EXPECT_GT(val->asTstr()->value().size(), 0);
- break;
- case cppbor::BSTR:
- EXPECT_GT(val->asBstr()->value().size(), 0);
- break;
- default:
- break;
- }
- }
-
- void checkDeviceInfo(const cppbor::Map* deviceInfo, bytevec deviceInfoBytes) {
- EXPECT_EQ(deviceInfo->clone()->asMap()->canonicalize().encode(), deviceInfoBytes)
- << "DeviceInfo ordering is non-canonical.";
- const auto& version = deviceInfo->get("version");
- ASSERT_TRUE(version);
- ASSERT_TRUE(version->asUint());
- RpcHardwareInfo info;
- provisionable_->getHardwareInfo(&info);
- ASSERT_EQ(version->asUint()->value(), info.versionNumber);
- std::set<std::string> allowList;
- switch (version->asUint()->value()) {
- // These fields became mandated in version 2.
- case 2:
- checkType(deviceInfo, cppbor::TSTR, "brand");
- checkType(deviceInfo, cppbor::TSTR, "manufacturer");
- checkType(deviceInfo, cppbor::TSTR, "product");
- checkType(deviceInfo, cppbor::TSTR, "model");
- checkType(deviceInfo, cppbor::TSTR, "device");
- // TODO: Refactor the KeyMint code that validates these fields and include it here.
- checkType(deviceInfo, cppbor::TSTR, "vb_state");
- allowList = getAllowedVbStates();
- EXPECT_NE(allowList.find(deviceInfo->get("vb_state")->asTstr()->value()),
- allowList.end());
- checkType(deviceInfo, cppbor::TSTR, "bootloader_state");
- allowList = getAllowedBootloaderStates();
- EXPECT_NE(allowList.find(deviceInfo->get("bootloader_state")->asTstr()->value()),
- allowList.end());
- checkType(deviceInfo, cppbor::BSTR, "vbmeta_digest");
- checkType(deviceInfo, cppbor::UINT, "system_patch_level");
- checkType(deviceInfo, cppbor::UINT, "boot_patch_level");
- checkType(deviceInfo, cppbor::UINT, "vendor_patch_level");
- checkType(deviceInfo, cppbor::UINT, "fused");
- EXPECT_LT(deviceInfo->get("fused")->asUint()->value(), 2); // Must be 0 or 1.
- checkType(deviceInfo, cppbor::TSTR, "security_level");
- allowList = getAllowedSecurityLevels();
- EXPECT_NE(allowList.find(deviceInfo->get("security_level")->asTstr()->value()),
- allowList.end());
- if (deviceInfo->get("security_level")->asTstr()->value() == "tee") {
- checkType(deviceInfo, cppbor::TSTR, "os_version");
- }
- break;
- case 1:
- checkType(deviceInfo, cppbor::TSTR, "security_level");
- allowList = getAllowedSecurityLevels();
- EXPECT_NE(allowList.find(deviceInfo->get("security_level")->asTstr()->value()),
- allowList.end());
- if (version->asUint()->value() == 1) {
- checkType(deviceInfo, cppbor::TSTR, "att_id_state");
- allowList = getAllowedAttIdStates();
- EXPECT_NE(allowList.find(deviceInfo->get("att_id_state")->asTstr()->value()),
- allowList.end());
- }
- break;
- default:
- FAIL() << "Unrecognized version: " << version->asUint()->value();
- }
- }
-
bytevec eekId_;
size_t testEekLength_;
EekChain testEekChain_;
@@ -559,7 +395,10 @@
&protectedData, &keysToSignMac);
ASSERT_TRUE(status.isOk()) << status.getMessage();
- checkProtectedData(deviceInfo, cppbor::Array(), keysToSignMac, protectedData);
+ auto result = verifyProductionProtectedData(
+ deviceInfo, cppbor::Array(), keysToSignMac, protectedData, testEekChain_, eekId_,
+ rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+ ASSERT_TRUE(result) << result.message();
}
}
@@ -581,22 +420,24 @@
&protectedData, &keysToSignMac);
ASSERT_TRUE(status.isOk()) << status.getMessage();
- std::vector<BccEntryData> firstBcc;
- checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData,
- &firstBcc);
+ auto firstBcc = verifyProductionProtectedData(
+ deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_,
+ eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+ ASSERT_TRUE(firstBcc) << firstBcc.message();
status = provisionable_->generateCertificateRequest(
testMode, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo,
&protectedData, &keysToSignMac);
ASSERT_TRUE(status.isOk()) << status.getMessage();
- std::vector<BccEntryData> secondBcc;
- checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData,
- &secondBcc);
+ auto secondBcc = verifyProductionProtectedData(
+ deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_,
+ eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+ ASSERT_TRUE(secondBcc) << secondBcc.message();
// Verify that none of the keys in the first BCC are repeated in the second one.
- for (const auto& i : firstBcc) {
- for (auto& j : secondBcc) {
+ for (const auto& i : *firstBcc) {
+ for (auto& j : *secondBcc) {
ASSERT_THAT(i.pubKey, testing::Not(testing::ElementsAreArray(j.pubKey)))
<< "Found a repeated pubkey in two generateCertificateRequest test mode calls";
}
@@ -639,7 +480,10 @@
&keysToSignMac);
ASSERT_TRUE(status.isOk()) << status.getMessage();
- checkProtectedData(deviceInfo, cborKeysToSign_, keysToSignMac, protectedData);
+ auto result = verifyProductionProtectedData(
+ deviceInfo, cborKeysToSign_, keysToSignMac, protectedData, testEekChain_, eekId_,
+ rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+ ASSERT_TRUE(result) << result.message();
}
}
diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp
index bf2ab02..3f48320 100644
--- a/security/keymint/support/Android.bp
+++ b/security/keymint/support/Android.bp
@@ -65,6 +65,7 @@
],
shared_libs: [
"libbase",
+ "libbinder_ndk",
"libcppbor_external",
"libcppcose_rkp",
"libcrypto",
diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h
index f3b8608..9ea20ac 100644
--- a/security/keymint/support/include/remote_prov/remote_prov_utils.h
+++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h
@@ -16,7 +16,9 @@
#pragma once
+#include <memory>
#include <vector>
+#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h"
#include <keymaster/cppcose/cppcose.h>
@@ -139,4 +141,40 @@
JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name,
const cppbor::Array& csr);
+/**
+ * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to
+ * ensure it contains the minimum required data at the time of manufacturing. This is only a
+ * partial validation, as some fields may not be provisioned yet at the time this information
+ * is parsed in the manufacturing process.
+ */
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateFactoryDeviceInfo(
+ const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable);
+
+/**
+ * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to
+ * ensure it is formatted correctly and that it contains the required values for Remote Key
+ * Provisioning. This is a full validation, and assumes the device is provisioned as if it is
+ * suitable for the end user.
+ */
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateProductionDeviceInfo(
+ const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable);
+
+/**
+ * Verify the protected data as if the device is still early in the factory process and may not
+ * have all device identifiers provisioned yet.
+ */
+ErrMsgOr<std::vector<BccEntryData>> verifyFactoryProtectedData(
+ const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+ const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+ const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+ IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
+/**
+ * Verify the protected data as if the device is a final production sample.
+ */
+ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
+ const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+ const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+ const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+ IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
+
} // namespace aidl::android::hardware::security::keymint::remote_prov
diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp
index 0dbea5b..0651496 100644
--- a/security/keymint/support/remote_prov_utils.cpp
+++ b/security/keymint/support/remote_prov_utils.cpp
@@ -15,7 +15,11 @@
*/
#include <iterator>
+#include <memory>
+#include <set>
+#include <string>
#include <tuple>
+#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h"
#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
#include <android-base/properties.h>
@@ -441,4 +445,293 @@
return JsonOutput::Ok(Json::writeString(factory, json));
}
-} // namespace aidl::android::hardware::security::keymint::remote_prov
+std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType,
+ const std::string& entryName) {
+ const std::unique_ptr<cppbor::Item>& val = devInfo.get(entryName);
+ if (!val) {
+ return entryName + " is missing.\n";
+ }
+ if (val->type() != majorType) {
+ return entryName + " has the wrong type.\n";
+ }
+ if (isFactory) {
+ return "";
+ }
+ switch (majorType) {
+ case cppbor::TSTR:
+ if (val->asTstr()->value().size() <= 0) {
+ return entryName + " is present but the value is empty.\n";
+ }
+ break;
+ case cppbor::BSTR:
+ if (val->asBstr()->value().size() <= 0) {
+ return entryName + " is present but the value is empty.\n";
+ }
+ break;
+ default:
+ break;
+ }
+ return "";
+}
+
+std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType,
+ const std::string& entryName, const cppbor::Array& allowList) {
+ std::string error = checkMapEntry(isFactory, devInfo, majorType, entryName);
+ if (!error.empty()) {
+ return error;
+ }
+
+ if (isFactory) {
+ return "";
+ }
+
+ const std::unique_ptr<cppbor::Item>& val = devInfo.get(entryName);
+ for (auto i = allowList.begin(); i != allowList.end(); ++i) {
+ if (**i == *val) {
+ return "";
+ }
+ }
+ return entryName + " has an invalid value.\n";
+}
+
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
+ const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable,
+ bool isFactory) {
+ const cppbor::Array kValidVbStates = {"green", "yellow", "orange"};
+ const cppbor::Array kValidBootloaderStates = {"locked", "unlocked"};
+ const cppbor::Array kValidSecurityLevels = {"tee", "strongbox"};
+ const cppbor::Array kValidAttIdStates = {"locked", "open"};
+ const cppbor::Array kValidFused = {0, 1};
+
+ struct AttestationIdEntry {
+ const char* id;
+ bool alwaysValidate;
+ };
+ constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false},
+ {"manufacturer", true},
+ {"product", false},
+ {"model", false},
+ {"device", false}};
+
+ auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = cppbor::parse(deviceInfoBytes);
+ if (!parsedVerifiedDeviceInfo) {
+ return errMsg;
+ }
+
+ std::unique_ptr<cppbor::Map> parsed(parsedVerifiedDeviceInfo->asMap());
+ if (!parsed) {
+ return "DeviceInfo must be a CBOR map.";
+ }
+ parsedVerifiedDeviceInfo.release();
+
+ if (parsed->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) {
+ return "DeviceInfo ordering is non-canonical.";
+ }
+ const std::unique_ptr<cppbor::Item>& version = parsed->get("version");
+ if (!version) {
+ return "Device info is missing version";
+ }
+ if (!version->asUint()) {
+ return "version must be an unsigned integer";
+ }
+ RpcHardwareInfo info;
+ provisionable->getHardwareInfo(&info);
+ if (version->asUint()->value() != info.versionNumber) {
+ return "DeviceInfo version (" + std::to_string(version->asUint()->value()) +
+ ") does not match the remotely provisioned component version (" +
+ std::to_string(info.versionNumber) + ").";
+ }
+ std::string error;
+ switch (version->asUint()->value()) {
+ case 2:
+ for (const auto& entry : kAttestationIdEntrySet) {
+ error += checkMapEntry(isFactory && !entry.alwaysValidate, *parsed, cppbor::TSTR,
+ entry.id);
+ }
+ if (!error.empty()) {
+ return error +
+ "Attestation IDs are missing or malprovisioned. If this test is being\n"
+ "run against an early proto or EVT build, this error is probably WAI\n"
+ "and indicates that Device IDs were not provisioned in the factory. If\n"
+ "this error is returned on a DVT or later build revision, then\n"
+ "something is likely wrong with the factory provisioning process.";
+ }
+ // TODO: Refactor the KeyMint code that validates these fields and include it here.
+ error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "vb_state", kValidVbStates);
+ error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "bootloader_state",
+ kValidBootloaderStates);
+ error += checkMapEntry(isFactory, *parsed, cppbor::BSTR, "vbmeta_digest");
+ error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "system_patch_level");
+ error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "boot_patch_level");
+ error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "vendor_patch_level");
+ error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "fused", kValidFused);
+ error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level",
+ kValidSecurityLevels);
+ if (parsed->get("security_level") && parsed->get("security_level")->asTstr() &&
+ parsed->get("security_level")->asTstr()->value() == "tee") {
+ error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "os_version");
+ }
+ break;
+ case 1:
+ error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level",
+ kValidSecurityLevels);
+ error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "att_id_state",
+ kValidAttIdStates);
+ break;
+ default:
+ return "Unrecognized version: " + std::to_string(version->asUint()->value());
+ }
+
+ if (!error.empty()) {
+ return error;
+ }
+
+ return std::move(parsed);
+}
+
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateFactoryDeviceInfo(
+ const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) {
+ return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/true);
+}
+
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateProductionDeviceInfo(
+ const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) {
+ return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/false);
+}
+
+ErrMsgOr<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& senderPubkey,
+ const EekChain& eekChain, int32_t supportedEekCurve) {
+ if (supportedEekCurve == RpcHardwareInfo::CURVE_25519 ||
+ supportedEekCurve == RpcHardwareInfo::CURVE_NONE) {
+ return x25519_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey,
+ senderPubkey->first, false /* senderIsA */);
+ } else {
+ return ECDH_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey, senderPubkey->first,
+ false /* senderIsA */);
+ }
+}
+
+ErrMsgOr<std::vector<BccEntryData>> verifyProtectedData(
+ const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+ const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+ const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+ IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge,
+ bool isFactory) {
+ auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
+ if (!parsedProtectedData) {
+ return protDataErrMsg;
+ }
+ if (!parsedProtectedData->asArray()) {
+ return "Protected data is not a CBOR array.";
+ }
+ if (parsedProtectedData->asArray()->size() != kCoseEncryptEntryCount) {
+ return "The protected data COSE_encrypt structure must have " +
+ std::to_string(kCoseEncryptEntryCount) + " entries, but it only has " +
+ std::to_string(parsedProtectedData->asArray()->size());
+ }
+
+ auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
+ if (!senderPubkey) {
+ return senderPubkey.message();
+ }
+ if (senderPubkey->second != eekId) {
+ return "The COSE_encrypt recipient does not match the expected EEK identifier";
+ }
+
+ auto sessionKey = getSessionKey(senderPubkey, eekChain, supportedEekCurve);
+ if (!sessionKey) {
+ return sessionKey.message();
+ }
+
+ auto protectedDataPayload =
+ decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
+ if (!protectedDataPayload) {
+ return protectedDataPayload.message();
+ }
+
+ auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
+ if (!parsedPayload) {
+ return "Failed to parse payload: " + payloadErrMsg;
+ }
+ if (!parsedPayload->asArray()) {
+ return "The protected data payload must be an Array.";
+ }
+ if (parsedPayload->asArray()->size() != 3U && parsedPayload->asArray()->size() != 2U) {
+ return "The protected data payload must contain SignedMAC and BCC. It may optionally "
+ "contain AdditionalDKSignatures. However, the parsed payload has " +
+ std::to_string(parsedPayload->asArray()->size()) + " entries.";
+ }
+
+ auto& signedMac = parsedPayload->asArray()->get(0);
+ auto& bcc = parsedPayload->asArray()->get(1);
+ if (!signedMac->asArray()) {
+ return "The SignedMAC in the protected data payload is not an Array.";
+ }
+ if (!bcc->asArray()) {
+ return "The BCC in the protected data payload is not an Array.";
+ }
+
+ // BCC is [ pubkey, + BccEntry]
+ auto bccContents = validateBcc(bcc->asArray());
+ if (!bccContents) {
+ return bccContents.message() + "\n" + prettyPrint(bcc.get());
+ }
+ if (bccContents->size() == 0U) {
+ return "The BCC is empty. It must contain at least one entry.";
+ }
+
+ auto deviceInfoResult =
+ parseAndValidateDeviceInfo(deviceInfo.deviceInfo, provisionable, isFactory);
+ if (!deviceInfoResult) {
+ return deviceInfoResult.message();
+ }
+ std::unique_ptr<cppbor::Map> deviceInfoMap = deviceInfoResult.moveValue();
+ auto& signingKey = bccContents->back().pubKey;
+ auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey,
+ cppbor::Array() // SignedMacAad
+ .add(challenge)
+ .add(std::move(deviceInfoMap))
+ .add(keysToSignMac)
+ .encode());
+ if (!macKey) {
+ return macKey.message();
+ }
+
+ auto coseMac0 = cppbor::Array()
+ .add(cppbor::Map() // protected
+ .add(ALGORITHM, HMAC_256)
+ .canonicalize()
+ .encode())
+ .add(cppbor::Map()) // unprotected
+ .add(keysToSign.encode()) // payload (keysToSign)
+ .add(keysToSignMac); // tag
+
+ auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
+ if (!macPayload) {
+ return macPayload.message();
+ }
+
+ return *bccContents;
+}
+
+ErrMsgOr<std::vector<BccEntryData>> verifyFactoryProtectedData(
+ const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+ const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+ const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+ IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
+ return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain,
+ eekId, supportedEekCurve, provisionable, challenge,
+ /*isFactory=*/true);
+}
+
+ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
+ const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+ const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+ const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+ IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
+ return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain,
+ eekId, supportedEekCurve, provisionable, challenge,
+ /*isFactory=*/false);
+}
+
+} // namespace aidl::android::hardware::security::keymint::remote_prov
\ No newline at end of file
diff --git a/sensors/aidl/vts/AndroidTest.xml b/sensors/aidl/vts/AndroidTest.xml
new file mode 100644
index 0000000..99caf28
--- /dev/null
+++ b/sensors/aidl/vts/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs VtsAidlHalSensorsTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.StopServicesSetup"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="VtsAidlHalSensorsTargetTest->/data/local/tmp/VtsAidlHalSensorsTargetTest" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-timeout" value="900000" />
+ <option name="runtime-hint" value="300000"/>
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsAidlHalSensorsTargetTest" />
+ </test>
+</configuration>
diff --git a/sensors/common/vts/utils/include/sensors-vts-utils/SensorsHidlTestBase.h b/sensors/common/vts/utils/include/sensors-vts-utils/SensorsHidlTestBase.h
index f3cbd78..f2c92ae 100644
--- a/sensors/common/vts/utils/include/sensors-vts-utils/SensorsHidlTestBase.h
+++ b/sensors/common/vts/utils/include/sensors-vts-utils/SensorsHidlTestBase.h
@@ -351,11 +351,11 @@
minDelayAverageInterval / 10);
// fastest rate sampling time is close to spec
- EXPECT_LT(std::abs(minDelayAverageInterval - minSamplingPeriodInNs),
+ EXPECT_LE(std::abs(minDelayAverageInterval - minSamplingPeriodInNs),
minSamplingPeriodInNs / 10);
// slowest rate sampling time is close to spec
- EXPECT_LT(std::abs(maxDelayAverageInterval - maxSamplingPeriodInNs),
+ EXPECT_LE(std::abs(maxDelayAverageInterval - maxSamplingPeriodInNs),
maxSamplingPeriodInNs / 10);
}
diff --git a/soundtrigger/2.3/cli/Android.bp b/soundtrigger/2.3/cli/Android.bp
index 27d7b30..883bf2b 100644
--- a/soundtrigger/2.3/cli/Android.bp
+++ b/soundtrigger/2.3/cli/Android.bp
@@ -9,7 +9,7 @@
java_binary {
name: "sthal_cli_2.3",
- wrapper: "sthal_cli_2.3",
+ wrapper: "sthal_cli_2.3.sh",
srcs: ["java/**/*.java"],
static_libs: [
"android.hardware.soundtrigger-V2.3-java",
diff --git a/soundtrigger/2.3/cli/sthal_cli_2.3 b/soundtrigger/2.3/cli/sthal_cli_2.3.sh
similarity index 100%
rename from soundtrigger/2.3/cli/sthal_cli_2.3
rename to soundtrigger/2.3/cli/sthal_cli_2.3.sh
diff --git a/soundtrigger/aidl/Android.bp b/soundtrigger/aidl/Android.bp
index 0658519..27d43d3 100644
--- a/soundtrigger/aidl/Android.bp
+++ b/soundtrigger/aidl/Android.bp
@@ -39,6 +39,20 @@
version: "1",
imports: ["android.media.soundtrigger.types-V1"],
},
+ // IMPORTANT: Update latest_android_hardware_soundtrigger3 every time
+ // you add the latest frozen version to versions_with_info
],
+}
+// Note: This should always be one version ahead of the last frozen version
+latest_android_hardware_soundtrigger3 = "android.hardware.soundtrigger3-V1"
+
+// Modules that depend on android.hardware.soundtrigger3 directly can include
+// the following java_defaults to avoid explicitly managing dependency versions
+// across many scattered files.
+java_defaults {
+ name: "latest_android_hardware_soundtrigger3_java_static",
+ static_libs: [
+ latest_android_hardware_soundtrigger3 + "-java",
+ ],
}
diff --git a/soundtrigger/aidl/cli/Android.bp b/soundtrigger/aidl/cli/Android.bp
index e8999ff..935e438 100644
--- a/soundtrigger/aidl/cli/Android.bp
+++ b/soundtrigger/aidl/cli/Android.bp
@@ -9,7 +9,7 @@
java_binary {
name: "sthal_cli_3",
- wrapper: "sthal_cli_3",
+ wrapper: "sthal_cli_3.sh",
srcs: ["java/**/*.java"],
static_libs: [
"android.hardware.soundtrigger3-V1-java",
diff --git a/soundtrigger/aidl/cli/sthal_cli_3 b/soundtrigger/aidl/cli/sthal_cli_3.sh
similarity index 100%
rename from soundtrigger/aidl/cli/sthal_cli_3
rename to soundtrigger/aidl/cli/sthal_cli_3.sh
diff --git a/tv/input/OWNERS b/tv/input/OWNERS
new file mode 100644
index 0000000..a02291a
--- /dev/null
+++ b/tv/input/OWNERS
@@ -0,0 +1,4 @@
+hgchen@google.com
+shubang@google.com
+quxiangfang@google.com
+yixiaoluo@google.com
diff --git a/vibrator/aidl/default/Android.bp b/vibrator/aidl/default/Android.bp
index c4140be..78bb4ed 100644
--- a/vibrator/aidl/default/Android.bp
+++ b/vibrator/aidl/default/Android.bp
@@ -57,33 +57,12 @@
cc_fuzz {
name: "android.hardware.vibrator-service.example_fuzzer",
host_supported: true,
+ defaults: ["service_fuzzer_defaults"],
static_libs: [
"android.hardware.vibrator-V2-ndk",
- "libbase",
- "libbinder_random_parcel",
- "libcutils",
"liblog",
"libvibratorexampleimpl",
],
- target: {
- android: {
- shared_libs: [
- "libbinder_ndk",
- "libbinder",
- "libutils",
- ],
- },
- host: {
- static_libs: [
- "libbinder_ndk",
- "libbinder",
- "libutils",
- ],
- },
- darwin: {
- enabled: false,
- },
- },
srcs: ["fuzzer.cpp"],
fuzz_config: {
cc: [