Merge "Emergency Call domain selection HAL API changes."
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index 46d177f..05e22bf 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -67,3 +67,33 @@
],
}
+
+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",
+ ],
+ imports: [
+ "android.hardware.audio.common-V1",
+ "android.media.audio.common.types",
+ ],
+ stability: "vintf",
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ vndk: {
+ enabled: true,
+ },
+ },
+ },
+ versions: [
+ ],
+}
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..1cef4cd
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.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 AudioPatch {
+ int id;
+ int[] sourcePortConfigIds;
+ int[] sinkPortConfigIds;
+}
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..33e8290
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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 {
+ 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.IStreamIn openInputStream(int portConfigId, in android.hardware.audio.common.SinkMetadata sinkMetadata);
+ android.hardware.audio.core.IStreamOut openOutputStream(int portConfigId, in android.hardware.audio.common.SourceMetadata sourceMetadata, in @nullable android.media.audio.common.AudioOffloadInfo offloadInfo);
+ 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);
+}
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/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl
new file mode 100644
index 0000000..48ca214
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/AudioPatch.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.
+ */
+
+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;
+}
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..d47ea3c
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -0,0 +1,281 @@
+/*
+ * 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.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 {
+ /**
+ * 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. For device port representing a connection to some external
+ * device, e.g. over HDMI or USB, currently supported audio profiles and
+ * extra audio descriptors may change.
+ *
+ * For all other audio ports it must be the same configuration as returned
+ * for this port ID by 'getAudioPorts'.
+ *
+ * @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 all audio ports provided by this module.
+ *
+ * Returns a list of all mix ports and device ports provided by this
+ * module. Each returned port must have a unique ID within this module
+ * ('AudioPort.id' field). The returned list must not change during
+ * the lifetime of the IModule instance. For audio ports with dynamic
+ * profiles (changing depending on external devices being connected
+ * to the system) an empty list of profiles must be returned. The list
+ * of currently supported audio profiles is obtained from 'getAudioPort'
+ * method.
+ *
+ * @return The list of audio ports.
+ */
+ AudioPort[] getAudioPorts();
+
+ /**
+ * Return all audio routes of this module.
+ *
+ * Returns a list of audio routes, that is, allowed connections between
+ * audio ports. The returned list must not change during the lifetime of the
+ * IModule instance.
+ *
+ * @return The list of audio routes.
+ */
+ AudioRoute[] getAudioRoutes();
+
+ /**
+ * 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.
+ *
+ * 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.
+ *
+ * @return An opened input stream.
+ * @param portConfigId The ID of the audio mix port config.
+ * @param sinkMetadata Description of the audio that will be recorded.
+ * @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.
+ * @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.
+ */
+ IStreamIn openInputStream(int portConfigId, in SinkMetadata sinkMetadata);
+
+ /**
+ * 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.
+ *
+ * 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.
+ *
+ * @return An opened output stream.
+ * @param portConfigId The ID of the audio mix port config.
+ * @param sourceMetadata Description of the audio that will be played.
+ * @param offloadInfo Additional information for offloaded playback.
+ * @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.
+ * @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.
+ */
+ IStreamOut openOutputStream(int portConfigId, in SourceMetadata sourceMetadata,
+ in @nullable AudioOffloadInfo offloadInfo);
+
+ /**
+ * 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. It is recommended that
+ * updating of an existing audio patch should be performed by the HAL module
+ * in a way that does not interrupt active audio streams involving audio
+ * port configurations of the patch. If the HAL module is unable to avoid
+ * interruption when updating a certain patch, it is permitted to allocate a
+ * new patch ID for the result. The returned audio patch contains all the
+ * information about the new or updated audio patch.
+ *
+ * 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.
+ * An audio patch may be created before or after an audio steam is created
+ * for this configuration.
+ *
+ * @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.
+ * @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.
+ *
+ * @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 and they are currently empty.
+ */
+ 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..b770449
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamIn.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.
+ */
+
+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.
+ * The stream can not be operated after it has been closed. Methods of this
+ * interface throw EX_ILLEGAL_STATE in 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..60212fc
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IStreamOut.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.
+ */
+
+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.
+ * The stream can not be operated after it has been closed. Methods of this
+ * interface throw EX_ILLEGAL_STATE in 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/default/Android.bp b/audio/aidl/default/Android.bp
new file mode 100644
index 0000000..0a6fe60
--- /dev/null
+++ b/audio/aidl/default/Android.bp
@@ -0,0 +1,45 @@
+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_static {
+ name: "libaudioserviceexampleimpl",
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.audio.core-V1-ndk",
+ ],
+ 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"],
+ vendor: true,
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "android.hardware.audio.core-V1-ndk",
+ ],
+ static_libs: [
+ "libaudioserviceexampleimpl",
+ ],
+ srcs: ["main.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..1104caa
--- /dev/null
+++ b/audio/aidl/default/Configuration.cpp
@@ -0,0 +1,196 @@
+/*
+ * 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 <aidl/android/media/audio/common/AudioChannelLayout.h>
+#include <aidl/android/media/audio/common/AudioDeviceType.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 "aidl/android/media/audio/common/AudioFormatDescription.h"
+#include "core-impl/Configuration.h"
+
+using aidl::android::media::audio::common::AudioChannelLayout;
+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;
+
+namespace aidl::android::hardware::audio::core::internal {
+
+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;
+ for (auto layout : channelLayouts) {
+ profile.channelMasks.push_back(
+ AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout));
+ }
+ profile.sampleRates.insert(profile.sampleRates.end(), sampleRates.begin(), sampleRates.end());
+ return profile;
+}
+
+static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags) {
+ AudioPortDeviceExt deviceExt;
+ deviceExt.device.type.type = devType;
+ 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& getNullPrimaryConfiguration() {
+ static Configuration configuration = []() {
+ 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);
+
+ AudioPort primaryOutMix = createPort(c.nextPortId++, "primary output",
+ 1 << static_cast<int32_t>(AudioOutputFlags::PRIMARY),
+ false, createPortMixExt(1, 1));
+ primaryOutMix.profiles.push_back(
+ createProfile(PcmType::INT_16_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000}));
+ primaryOutMix.profiles.push_back(
+ createProfile(PcmType::INT_24_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000}));
+ c.ports.push_back(primaryOutMix);
+
+ c.routes.push_back(createRoute({primaryOutMix.id}, nullOutDevice.id));
+
+ 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 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);
+
+ c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
+
+ AudioPort zeroInDevice =
+ createPort(c.nextPortId++, "Zero", 0, true,
+ createDeviceExt(AudioDeviceType::IN_MICROPHONE,
+ 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
+ c.ports.push_back(zeroInDevice);
+
+ 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);
+
+ c.routes.push_back(createRoute({zeroInDevice.id}, primaryInMix.id));
+
+ 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 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);
+
+ 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/Module.cpp b/audio/aidl/default/Module.cpp
new file mode 100644
index 0000000..e0a68a5
--- /dev/null
+++ b/audio/aidl/default/Module.cpp
@@ -0,0 +1,522 @@
+/*
+ * 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"
+#define LOG_NDEBUG 0
+#include <android-base/logging.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::AudioFormatDescription;
+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;
+
+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});
+}
+
+void Module::cleanUpPatches(int32_t portConfigId) {
+ auto& patches = getConfig().patches;
+ if (patches.size() == 0) return;
+ auto range = mPatches.equal_range(portConfigId);
+ for (auto it = range.first; it != range.second; ++it) {
+ auto patchIt = findById<AudioPatch>(patches, it->second);
+ if (patchIt != patches.end()) {
+ erase_if(patchIt->sourcePortConfigIds,
+ [portConfigId](auto e) { return e == portConfigId; });
+ erase_if(patchIt->sinkPortConfigIds,
+ [portConfigId](auto e) { return e == portConfigId; });
+ }
+ }
+ std::set<int32_t> erasedPatches;
+ for (size_t i = patches.size() - 1; i != 0; --i) {
+ const auto& patch = patches[i];
+ if (patch.sourcePortConfigIds.empty() || patch.sinkPortConfigIds.empty()) {
+ erasedPatches.insert(patch.id);
+ patches.erase(patches.begin() + i);
+ }
+ }
+ erase_all_values(mPatches, erasedPatches);
+}
+
+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);
+}
+
+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::openInputStream(int32_t in_portConfigId,
+ const SinkMetadata& in_sinkMetadata,
+ std::shared_ptr<IStreamIn>* _aidl_return) {
+ 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 (portIt->flags.getTag() != AudioIoFlags::Tag::input ||
+ portIt->ext.getTag() != AudioPortExt::Tag::mix) {
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+ << " does not correspond to an input mix port";
+ 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);
+ }
+ 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);
+ }
+ auto stream = ndk::SharedRefBase::make<StreamIn>(in_sinkMetadata);
+ mStreams.insert(portId, in_portConfigId, StreamWrapper(stream));
+ *_aidl_return = std::move(stream);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::openOutputStream(int32_t in_portConfigId,
+ const SourceMetadata& in_sourceMetadata,
+ const std::optional<AudioOffloadInfo>& in_offloadInfo,
+ std::shared_ptr<IStreamOut>* _aidl_return) {
+ 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 (portIt->flags.getTag() != AudioIoFlags::Tag::output ||
+ portIt->ext.getTag() != AudioPortExt::Tag::mix) {
+ LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
+ << " does not correspond to an output mix port";
+ 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);
+ }
+ 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);
+ }
+ auto stream = ndk::SharedRefBase::make<StreamOut>(in_sourceMetadata, in_offloadInfo);
+ mStreams.insert(portId, in_portConfigId, StreamWrapper(stream));
+ *_aidl_return = std::move(stream);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPatch* _aidl_return) {
+ 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;
+ if (existing == patches.end()) {
+ _aidl_return->id = getConfig().nextPatchId++;
+ patches.push_back(*_aidl_return);
+ existing = patches.begin() + (patches.size() - 1);
+ } else {
+ *existing = *_aidl_return;
+ }
+ registerPatch(*existing);
+ LOG(DEBUG) << __func__ << ": created or updated patch id " << _aidl_return->id;
+ 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);
+ 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..e16b2c6
--- /dev/null
+++ b/audio/aidl/default/Stream.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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"
+#define LOG_NDEBUG 0
+#include <android-base/logging.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 {
+
+StreamIn::StreamIn(const SinkMetadata& sinkMetadata) : mMetadata(sinkMetadata) {}
+
+ndk::ScopedAStatus StreamIn::close() {
+ LOG(DEBUG) << __func__;
+ if (!mIsClosed) {
+ mIsClosed = true;
+ return ndk::ScopedAStatus::ok();
+ } else {
+ LOG(ERROR) << __func__ << ": stream was already closed";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+}
+
+ndk::ScopedAStatus StreamIn::updateMetadata(const SinkMetadata& in_sinkMetadata) {
+ LOG(DEBUG) << __func__;
+ if (!mIsClosed) {
+ mMetadata = in_sinkMetadata;
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": stream was closed";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+StreamOut::StreamOut(const SourceMetadata& sourceMetadata,
+ const std::optional<AudioOffloadInfo>& offloadInfo)
+ : mMetadata(sourceMetadata), mOffloadInfo(offloadInfo) {}
+
+ndk::ScopedAStatus StreamOut::close() {
+ LOG(DEBUG) << __func__;
+ if (!mIsClosed) {
+ mIsClosed = true;
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": stream was already closed";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+ndk::ScopedAStatus StreamOut::updateMetadata(const SourceMetadata& in_sourceMetadata) {
+ LOG(DEBUG) << __func__;
+ if (!mIsClosed) {
+ mMetadata = in_sourceMetadata;
+ return ndk::ScopedAStatus::ok();
+ }
+ LOG(ERROR) << __func__ << ": stream was closed";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+} // namespace aidl::android::hardware::audio::core
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/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..17e342d
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -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.
+ */
+
+#pragma once
+
+#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;
+ 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..359626c
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -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.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+
+#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 {
+ 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 openInputStream(
+ int32_t in_portConfigId,
+ const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata,
+ std::shared_ptr<IStreamIn>* _aidl_return) override;
+ ndk::ScopedAStatus openOutputStream(
+ int32_t in_portConfigId,
+ const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata,
+ const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+ in_offloadInfo,
+ std::shared_ptr<IStreamOut>* _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;
+
+ private:
+ void cleanUpPatch(int32_t patchId);
+ void cleanUpPatches(int32_t portConfigId);
+ internal::Configuration& getConfig();
+ void registerPatch(const AudioPatch& patch);
+
+ std::unique_ptr<internal::Configuration> mConfig;
+ 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..87104dd
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -0,0 +1,103 @@
+/*
+ * 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 <optional>
+#include <variant>
+
+#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/media/audio/common/AudioOffloadInfo.h>
+
+#include "core-impl/utils.h"
+
+namespace aidl::android::hardware::audio::core {
+
+class StreamIn : public BnStreamIn {
+ ndk::ScopedAStatus close() override;
+ ndk::ScopedAStatus updateMetadata(
+ const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata) override;
+
+ public:
+ explicit StreamIn(const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata);
+ bool isClosed() const { return mIsClosed; }
+
+ private:
+ ::aidl::android::hardware::audio::common::SinkMetadata mMetadata;
+ bool mIsClosed = false;
+};
+
+class StreamOut : public BnStreamOut {
+ ndk::ScopedAStatus close() override;
+ ndk::ScopedAStatus updateMetadata(
+ const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata)
+ override;
+
+ public:
+ StreamOut(const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
+ const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+ offloadInfo);
+ bool isClosed() const { return mIsClosed; }
+
+ private:
+ ::aidl::android::hardware::audio::common::SourceMetadata mMetadata;
+ std::optional<::aidl::android::media::audio::common::AudioOffloadInfo> mOffloadInfo;
+ bool mIsClosed = false;
+};
+
+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);
+ }
+
+ 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, sw});
+ }
+
+ 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..7101012
--- /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 map 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(), last = c.end(); it != last;) {
+ 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/main.cpp b/audio/aidl/default/main.cpp
new file mode 100644
index 0000000..0de6047
--- /dev/null
+++ b/audio/aidl/default/main.cpp
@@ -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.
+ */
+
+#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() {
+ 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(status == STATUS_OK);
+
+ // 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(status == STATUS_OK);
+
+ 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..c160d1f
--- /dev/null
+++ b/audio/aidl/vts/Android.bp
@@ -0,0 +1,32 @@
+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",
+ ],
+ srcs: [
+ "ModuleConfig.cpp",
+ "VtsHalAudioCoreTargetTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder",
+ ],
+ static_libs: [
+ "android.hardware.audio.common-V1-cpp",
+ "android.hardware.audio.core-V1-cpp",
+ "android.media.audio.common.types-V1-cpp",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
new file mode 100644
index 0000000..3faa39a
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -0,0 +1,330 @@
+/*
+ * 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 <android/media/audio/common/AudioIoFlags.h>
+#include <android/media/audio/common/AudioOutputFlags.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+
+using android::hardware::audio::core::IModule;
+using android::media::audio::common::AudioChannelLayout;
+using android::media::audio::common::AudioFormatDescription;
+using android::media::audio::common::AudioFormatType;
+using android::media::audio::common::AudioIoFlags;
+using android::media::audio::common::AudioOutputFlags;
+using android::media::audio::common::AudioPort;
+using android::media::audio::common::AudioPortConfig;
+using android::media::audio::common::AudioPortExt;
+using android::media::audio::common::AudioProfile;
+using android::media::audio::common::Int;
+
+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>();
+ const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
+ if (devicePort.device.type.connection.empty()) {
+ // Permanently attached device.
+ if (isInput) {
+ mAttachedSourceDevicePorts.insert(port.id);
+ } else {
+ mAttachedSinkDevicePorts.insert(port.id);
+ }
+ }
+ }
+ if (!mStatus.isOk()) return;
+ mStatus = module->getAudioRoutes(&mRoutes);
+ if (!mStatus.isOk()) return;
+ mStatus = module->getAudioPortConfigs(&mInitialConfigs);
+}
+
+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::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;
+}
+
+static std::vector<AudioPortConfig> combineAudioConfigs(const AudioPort& port,
+ const AudioProfile& profile) {
+ std::vector<AudioPortConfig> configs;
+ configs.reserve(profile.channelMasks.size() * profile.sampleRates.size());
+ 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.ext = port.ext;
+ configs.push_back(config);
+ }
+ }
+ return configs;
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateInputAudioMixPortConfigs(
+ const std::vector<AudioPort>& ports, bool singleProfile) const {
+ std::vector<AudioPortConfig> result;
+ for (const auto& mixPort : ports) {
+ if (getAttachedSourceDevicesPortsForMixPort(mixPort).empty()) {
+ continue; // no attached devices
+ }
+ for (const auto& profile : mixPort.profiles) {
+ if (profile.format.type == AudioFormatType::DEFAULT || profile.sampleRates.empty() ||
+ profile.channelMasks.empty()) {
+ continue; // dynamic profile
+ }
+ auto configs = combineAudioConfigs(mixPort, profile);
+ for (auto& config : configs) {
+ config.flags = mixPort.flags;
+ result.push_back(config);
+ if (singleProfile) return result;
+ }
+ }
+ }
+ return result;
+}
+
+static std::tuple<AudioIoFlags, bool> generateOutFlags(const AudioPort& mixPort) {
+ static const AudioIoFlags offloadFlags = AudioIoFlags::make<AudioIoFlags::Tag::output>(
+ (1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD)) |
+ (1 << static_cast<int>(AudioOutputFlags::DIRECT)));
+ const bool isOffload = (mixPort.flags.get<AudioIoFlags::Tag::output>() &
+ (1 << static_cast<int>(AudioOutputFlags::COMPRESS_OFFLOAD))) != 0;
+ return {isOffload ? offloadFlags : mixPort.flags, isOffload};
+}
+
+std::vector<AudioPortConfig> ModuleConfig::generateOutputAudioMixPortConfigs(
+ const std::vector<AudioPort>& ports, bool singleProfile) const {
+ std::vector<AudioPortConfig> result;
+ for (const auto& mixPort : ports) {
+ if (getAttachedSinkDevicesPortsForMixPort(mixPort).empty()) {
+ continue; // no attached devices
+ }
+ auto [flags, isOffload] = generateOutFlags(mixPort);
+ (void)isOffload;
+ for (const auto& profile : mixPort.profiles) {
+ if (profile.format.type == AudioFormatType::DEFAULT) continue;
+ auto configs = combineAudioConfigs(mixPort, profile);
+ for (auto& config : configs) {
+ // Some combinations of flags declared in the config file require special
+ // treatment.
+ // if (isOffload) {
+ // config.offloadInfo.info(generateOffloadInfo(config.base));
+ // }
+ config.flags = flags;
+ result.push_back(config);
+ if (singleProfile) 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) {
+ auto configs = combineAudioConfigs(devicePort, profile);
+ result.insert(result.end(), configs.begin(), configs.end());
+ if (singleProfile && !result.empty()) return result;
+ }
+ 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..2e86b97
--- /dev/null
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -0,0 +1,131 @@
+/*
+ * 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 <android/hardware/audio/core/AudioRoute.h>
+#include <android/hardware/audio/core/IModule.h>
+#include <android/media/audio/common/AudioPort.h>
+#include <binder/Status.h>
+
+class ModuleConfig {
+ public:
+ using SrcSinkPair = std::pair<android::media::audio::common::AudioPortConfig,
+ android::media::audio::common::AudioPortConfig>;
+ using SrcSinkGroup =
+ std::pair<android::hardware::audio::core::AudioRoute, std::vector<SrcSinkPair>>;
+
+ explicit ModuleConfig(android::hardware::audio::core::IModule* module);
+ android::binder::Status getStatus() const { return mStatus; }
+ std::string getError() const { return mStatus.toString8().c_str(); }
+
+ std::vector<android::media::audio::common::AudioPort> getInputMixPorts() const;
+ std::vector<android::media::audio::common::AudioPort> getOutputMixPorts() const;
+ std::vector<android::media::audio::common::AudioPort> getMixPorts(bool isInput) const {
+ return isInput ? getInputMixPorts() : getOutputMixPorts();
+ }
+
+ std::vector<android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+ bool isInput, const android::media::audio::common::AudioPort& mixPort) const {
+ return isInput ? getAttachedSourceDevicesPortsForMixPort(mixPort)
+ : getAttachedSinkDevicesPortsForMixPort(mixPort);
+ }
+ std::vector<android::media::audio::common::AudioPort> getAttachedSinkDevicesPortsForMixPort(
+ const android::media::audio::common::AudioPort& mixPort) const;
+ std::vector<android::media::audio::common::AudioPort> getAttachedSourceDevicesPortsForMixPort(
+ const android::media::audio::common::AudioPort& mixPort) const;
+ std::optional<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<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts() const {
+ auto inputs = generateInputAudioMixPortConfigs(getInputMixPorts(), false);
+ auto outputs = generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
+ inputs.insert(inputs.end(), outputs.begin(), outputs.end());
+ return inputs;
+ }
+ std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+ bool isInput) const {
+ return isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), false)
+ : generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
+ }
+ std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
+ bool isInput, const android::media::audio::common::AudioPort& port) const {
+ return isInput ? generateInputAudioMixPortConfigs({port}, false)
+ : generateOutputAudioMixPortConfigs({port}, false);
+ }
+ std::optional<android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+ bool isInput) const {
+ const auto config = isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), true)
+ : generateOutputAudioMixPortConfigs(getOutputMixPorts(), true);
+ // TODO: Avoid returning configs for offload since they require an extra
+ // argument to openOutputStream.
+ if (!config.empty()) {
+ return *config.begin();
+ } else {
+ return {};
+ }
+ }
+ std::optional<android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
+ bool isInput, const android::media::audio::common::AudioPort& port) const {
+ const auto config = isInput ? generateInputAudioMixPortConfigs({port}, true)
+ : generateOutputAudioMixPortConfigs({port}, true);
+ if (!config.empty()) {
+ return *config.begin();
+ } else {
+ return {};
+ }
+ }
+
+ android::media::audio::common::AudioPortConfig getSingleConfigForDevicePort(
+ const android::media::audio::common::AudioPort& port) const {
+ for (const auto& config : mInitialConfigs) {
+ if (config.portId == port.id) return config;
+ }
+ const auto config = generateAudioDevicePortConfigs({port}, true);
+ return *config.begin();
+ }
+
+ private:
+ std::vector<android::media::audio::common::AudioPortConfig> generateInputAudioMixPortConfigs(
+ const std::vector<android::media::audio::common::AudioPort>& ports,
+ bool singleProfile) const;
+ std::vector<android::media::audio::common::AudioPortConfig> generateOutputAudioMixPortConfigs(
+ const std::vector<android::media::audio::common::AudioPort>& ports,
+ 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, a vector with an empty config is returned.
+ std::vector<android::media::audio::common::AudioPortConfig> generateAudioDevicePortConfigs(
+ const std::vector<android::media::audio::common::AudioPort>& ports,
+ bool singleProfile) const;
+
+ android::binder::Status mStatus = android::binder::Status::ok();
+ std::vector<android::media::audio::common::AudioPort> mPorts;
+ std::vector<android::media::audio::common::AudioPortConfig> mInitialConfigs;
+ std::set<int32_t> mAttachedSinkDevicePorts;
+ std::set<int32_t> mAttachedSourceDevicePorts;
+ std::vector<android::hardware::audio::core::AudioRoute> mRoutes;
+};
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
new file mode 100644
index 0000000..cadeb0c
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -0,0 +1,1027 @@
+/*
+ * 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 <condition_variable>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <set>
+#include <string>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/properties.h>
+#include <android/hardware/audio/core/IConfig.h>
+#include <android/hardware/audio/core/IModule.h>
+#include <android/media/audio/common/AudioIoFlags.h>
+#include <android/media/audio/common/AudioOutputFlags.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
+#include "ModuleConfig.h"
+
+using namespace android;
+using android::binder::Status;
+using android::hardware::audio::common::PlaybackTrackMetadata;
+using android::hardware::audio::common::RecordTrackMetadata;
+using android::hardware::audio::common::SinkMetadata;
+using android::hardware::audio::common::SourceMetadata;
+using android::hardware::audio::core::AudioPatch;
+using android::hardware::audio::core::AudioRoute;
+using android::hardware::audio::core::IModule;
+using android::hardware::audio::core::IStreamIn;
+using android::hardware::audio::core::IStreamOut;
+using android::media::audio::common::AudioContentType;
+using android::media::audio::common::AudioDevice;
+using android::media::audio::common::AudioDeviceType;
+using android::media::audio::common::AudioIoFlags;
+using android::media::audio::common::AudioOutputFlags;
+using android::media::audio::common::AudioPort;
+using android::media::audio::common::AudioPortConfig;
+using android::media::audio::common::AudioPortDeviceExt;
+using android::media::audio::common::AudioPortExt;
+using android::media::audio::common::AudioSource;
+using android::media::audio::common::AudioUsage;
+
+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;
+}
+
+struct AidlDeathRecipient : IBinder::DeathRecipient {
+ std::mutex mutex;
+ std::condition_variable condition;
+ bool fired = false;
+ wp<IBinder> who;
+
+ void binderDied(const wp<IBinder>& who) override {
+ std::unique_lock<std::mutex> lock(mutex);
+ fired = true;
+ this->who = who;
+ condition.notify_one();
+ };
+
+ bool waitForFired(int timeoutMs) {
+ std::unique_lock<std::mutex> lock(mutex);
+ condition.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return fired; });
+ return fired;
+ }
+};
+
+template <typename T>
+struct IsInput {
+ constexpr operator bool() const;
+};
+
+template <>
+constexpr IsInput<IStreamIn>::operator bool() const {
+ return true;
+}
+template <>
+constexpr IsInput<IStreamOut>::operator bool() const {
+ return false;
+}
+
+class AudioCoreModule : public testing::TestWithParam<std::string> {
+ public:
+ void SetUp() override { ASSERT_NO_FATAL_FAILURE(ConnectToService()); }
+
+ void ConnectToService() {
+ module = android::waitForDeclaredService<IModule>(String16(GetParam().c_str()));
+ ASSERT_NE(module, nullptr);
+ }
+
+ void RestartService() {
+ ASSERT_NE(module, nullptr);
+ moduleConfig.reset();
+ deathHandler = sp<AidlDeathRecipient>::make();
+ ASSERT_EQ(NO_ERROR, IModule::asBinder(module)->linkToDeath(deathHandler));
+ ASSERT_TRUE(base::SetProperty("sys.audio.restart.hal", "1"));
+ EXPECT_TRUE(deathHandler->waitForFired(3000));
+ deathHandler = nullptr;
+ ASSERT_NO_FATAL_FAILURE(ConnectToService());
+ }
+
+ template <typename Entity>
+ void GetAllEntityIds(std::set<int32_t>* entityIds,
+ Status (IModule::*getter)(std::vector<Entity>*),
+ const std::string& errorMessage) {
+ std::vector<Entity> entities;
+ {
+ Status status = (module.get()->*getter)(&entities);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ 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(Status::EX_NONE, moduleConfig->getStatus().exceptionCode())
+ << "ModuleConfig init error: " << moduleConfig->getError();
+ }
+ }
+
+ sp<IModule> module;
+ sp<AidlDeathRecipient> deathHandler;
+ std::unique_ptr<ModuleConfig> moduleConfig;
+};
+
+// 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() {
+ if (mModule != nullptr) {
+ Status status = mModule->resetAudioPortConfig(getId());
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; 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;
+ Status status = module->setAudioPortConfig(mInitialConfig, &suggested, &applied);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; 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;
+};
+
+template <typename Stream>
+class WithStream {
+ public:
+ WithStream() {}
+ explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
+ ~WithStream() {
+ if (mStream != nullptr) {
+ Status status = mStream->close();
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; port config id " << getPortId();
+ }
+ }
+ void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); }
+ Status SetUpNoChecks(IModule* module) { return SetUpNoChecks(module, mPortConfig.get()); }
+ Status SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig);
+ void SetUp(IModule* module) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+ Status status = SetUpNoChecks(module);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; port config id " << getPortId();
+ ASSERT_NE(nullptr, mStream) << "; port config id " << getPortId();
+ }
+ Stream* get() const { return mStream.get(); }
+ const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
+ int32_t getPortId() const { return mPortConfig.getId(); }
+
+ private:
+ WithAudioPortConfig mPortConfig;
+ sp<Stream> mStream;
+};
+
+template <>
+Status WithStream<IStreamIn>::SetUpNoChecks(IModule* module, 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 module->openInputStream(portConfig.id, metadata, &mStream);
+}
+
+template <>
+Status WithStream<IStreamOut>::SetUpNoChecks(IModule* module, 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 module->openOutputStream(portConfig.id, metadata, {}, &mStream);
+}
+
+class WithAudioPatch {
+ public:
+ WithAudioPatch() {}
+ WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
+ : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
+ ~WithAudioPatch() {
+ if (mModule != nullptr && mPatch.id != 0) {
+ Status status = mModule->resetAudioPatch(mPatch.id);
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; patch id " << getId();
+ }
+ }
+ void SetUpPortConfigs(IModule* module) {
+ ASSERT_NO_FATAL_FAILURE(mSrcPortConfig.SetUp(module));
+ ASSERT_NO_FATAL_FAILURE(mSinkPortConfig.SetUp(module));
+ }
+ Status 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));
+ Status status = SetUpNoChecks(module);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; source port config id " << mSrcPortConfig.getId()
+ << "; sink port config id " << mSinkPortConfig.getId();
+ }
+ int32_t getId() const { return mPatch.id; }
+ const AudioPatch& get() const { return mPatch; }
+
+ 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, GetAudioPortsIsStatic) {
+ std::vector<AudioPort> ports1;
+ {
+ Status status = module->getAudioPorts(&ports1);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ std::vector<AudioPort> ports2;
+ {
+ Status status = module->getAudioPorts(&ports2);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ ASSERT_EQ(ports1.size(), ports2.size())
+ << "Sizes of audio port arrays do not match across calls to getAudioPorts";
+ std::sort(ports1.begin(), ports1.end());
+ std::sort(ports2.begin(), ports2.end());
+ EXPECT_EQ(ports1, ports2);
+}
+
+TEST_P(AudioCoreModule, GetAudioRoutesIsStatic) {
+ std::vector<AudioRoute> routes1;
+ {
+ Status status = module->getAudioRoutes(&routes1);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ std::vector<AudioRoute> routes2;
+ {
+ Status status = module->getAudioRoutes(&routes2);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ ASSERT_EQ(routes1.size(), routes2.size())
+ << "Sizes of audio route arrays do not match across 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;
+ {
+ Status status = module->getAudioRoutes(&routes);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ for (const auto& route : routes) {
+ std::set<int32_t> sources(route.sourcePortIds.begin(), route.sourcePortIds.end());
+ EXPECT_NE(0, 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;
+ {
+ Status status = module->getAudioRoutes(&routes);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ for (const auto& route : routes) {
+ EXPECT_EQ(1, portIds.count(route.sinkPortId))
+ << route.sinkPortId << " sink port id is unknown";
+ for (const auto& source : route.sourcePortIds) {
+ EXPECT_EQ(1, portIds.count(source)) << source << " source port id is unknown";
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, CheckDevicePorts) {
+ std::vector<AudioPort> ports;
+ {
+ Status status = module->getAudioPorts(&ports);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ 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(0, 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(0, 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;
+ {
+ Status status = module->getAudioPorts(&ports);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ std::optional<int32_t> primaryMixPort;
+ constexpr int primaryOutputFlag = 1 << static_cast<int>(AudioOutputFlags::PRIMARY);
+ 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 &&
+ ((port.flags.get<AudioIoFlags::Tag::output>() & primaryOutputFlag) != 0)) {
+ 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;
+ Status status = module->getAudioPort(portId, &port);
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ EXPECT_EQ(portId, port.id);
+ }
+ for (const auto portId : getNonExistentIds(portIds)) {
+ AudioPort port;
+ Status status = module->getAudioPort(portId, &port);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for port ID " << portId;
+ }
+}
+
+TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
+ std::set<int32_t> portConfigIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
+ for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+ {
+ sp<IStreamIn> stream;
+ Status status = module->openInputStream(portConfigId, {}, &stream);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " openInputStream returned for port config ID " << portConfigId;
+ EXPECT_EQ(nullptr, stream);
+ }
+ {
+ sp<IStreamOut> stream;
+ Status status = module->openOutputStream(portConfigId, {}, {}, &stream);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " openOutputStream returned for port config ID " << portConfigId;
+ EXPECT_EQ(nullptr, 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;
+ {
+ Status status = module->getAudioPortConfigs(&portConfigs);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ for (const auto& config : portConfigs) {
+ EXPECT_EQ(1, 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)) {
+ Status status = module->resetAudioPortConfig(portConfigId);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for 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;
+ {
+ Status status = module->getAudioPortConfigs(&portConfigsBefore);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ // TODO: Change port configs according to port profiles.
+ for (const auto& c : portConfigsBefore) {
+ Status status = module->resetAudioPortConfig(c.id);
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << " returned for port config ID " << c.id;
+ }
+ std::vector<AudioPortConfig> portConfigsAfter;
+ {
+ Status status = module->getAudioPortConfigs(&portConfigsAfter);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ 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;
+ Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << "; 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, SetAllStaticAudioPortConfigs) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts();
+ for (const auto& config : allPortConfigs) {
+ ASSERT_NE(0, config.portId);
+ WithAudioPortConfig portConfig(config);
+ ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+ EXPECT_EQ(config.portId, portConfig.get().portId);
+ std::vector<AudioPortConfig> retrievedPortConfigs;
+ {
+ Status status = module->getAudioPortConfigs(&retrievedPortConfigs);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ 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 getPortConfigsForMixPorts";
+ if (configIt != retrievedPortConfigs.end()) {
+ EXPECT_EQ(portConfig.get(), *configIt)
+ << "Port config returned by getPortConfigsForMixPorts: " << configIt->toString()
+ << " is not the same as returned by setAudioPortConfig: "
+ << portConfig.get().toString();
+ }
+ }
+}
+
+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;
+ Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for 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;
+ Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for port config ID " << portConfigId;
+ EXPECT_FALSE(suggestedConfig.format.has_value());
+ EXPECT_FALSE(suggestedConfig.channelMask.has_value());
+ EXPECT_FALSE(suggestedConfig.sampleRate.has_value());
+ }
+}
+
+template <typename Stream>
+class AudioStream : public AudioCoreModule {
+ public:
+ static std::string direction(bool capitalize);
+
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ }
+
+ void CloseTwice() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ sp<Stream> heldStream;
+ {
+ WithStream<Stream> stream(portConfig.value());
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+ heldStream = stream.get();
+ }
+ Status status = heldStream->close();
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " when closing the stream twice";
+ }
+
+ void OpenAllConfigs() {
+ const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IsInput<Stream>());
+ for (const auto& portConfig : allPortConfigs) {
+ WithStream<Stream> stream(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
+ }
+ }
+
+ void OpenOverMaxCount() {
+ constexpr bool isInput = IsInput<Stream>();
+ 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()));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
+ Status status = stream.SetUpNoChecks(module.get());
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " open" << direction(true)
+ << "Stream"
+ " returned for port config ID "
+ << stream.getPortId() << ", maxOpenStreamCount is " << maxStreamCount;
+ }
+ }
+ }
+ if (!hasSingleRun) {
+ GTEST_SKIP() << "Not enough " << direction(false)
+ << " ports to test max open stream count";
+ }
+ }
+
+ void OpenInvalidDirection() {
+ // Important! The direction of the port config must be reversed.
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(!IsInput<Stream>());
+ 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()));
+ Status status = stream.SetUpNoChecks(module.get());
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " open" << direction(true) << "Stream returned for port config ID "
+ << stream.getPortId();
+ EXPECT_EQ(nullptr, stream.get());
+ }
+
+ void OpenTwiceSamePortConfig() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
+ if (!portConfig.has_value()) {
+ GTEST_SKIP() << "No mix port for attached devices";
+ }
+ EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value()));
+ }
+
+ void ResetPortConfigWithOpenStream() {
+ const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
+ 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()));
+ Status status = module->resetAudioPortConfig(stream.getPortId());
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " returned for port config ID " << stream.getPortId();
+ }
+
+ void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
+ WithStream<Stream> stream1(portConfig);
+ ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get()));
+ WithStream<Stream> stream2;
+ Status status = stream2.SetUpNoChecks(module.get(), stream1.getPortConfig());
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " when opening " << direction(false)
+ << " stream twice for the same port config ID " << stream1.getPortId();
+ }
+};
+using AudioStreamIn = AudioStream<IStreamIn>;
+using AudioStreamOut = AudioStream<IStreamOut>;
+
+template <>
+std::string AudioStreamIn::direction(bool capitalize) {
+ return capitalize ? "Input" : "input";
+}
+template <>
+std::string AudioStreamOut::direction(bool capitalize) {
+ return capitalize ? "Output" : "output";
+}
+
+#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()); }
+
+TEST_IO_STREAM(CloseTwice);
+TEST_IO_STREAM(OpenAllConfigs);
+TEST_IO_STREAM(OpenInvalidDirection);
+TEST_IO_STREAM(OpenOverMaxCount);
+TEST_IO_STREAM(OpenTwiceSamePortConfig);
+TEST_IO_STREAM(ResetPortConfigWithOpenStream);
+
+TEST_P(AudioStreamOut, OpenTwicePrimary) {
+ const auto mixPorts = moduleConfig->getMixPorts(false);
+ auto primaryPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [](const AudioPort& port) {
+ constexpr int primaryOutputFlag = 1 << static_cast<int>(AudioOutputFlags::PRIMARY);
+ return port.flags.getTag() == AudioIoFlags::Tag::output &&
+ ((port.flags.get<AudioIoFlags::Tag::output>() & primaryOutputFlag) != 0);
+ });
+ 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()));
+}
+
+// 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;
+ Status status = module->setAudioPatch(patch, &patch);
+ ASSERT_EQ(expectedException, status.exceptionCode())
+ << status << ": 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) {
+ Status status = module->resetAudioPortConfig(portConfigId);
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " returned for 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(Status::EX_ILLEGAL_ARGUMENT, {}, {sinkPortConfig.getId()}));
+ EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(
+ Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId(), srcPortConfig.getId()},
+ {sinkPortConfig.getId()}));
+ EXPECT_NO_FATAL_FAILURE(
+ SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {}));
+ EXPECT_NO_FATAL_FAILURE(
+ SetInvalidPatchHelper(Status::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(
+ Status::EX_ILLEGAL_ARGUMENT, {portConfigId}, {sinkPortConfig.getId()}));
+ EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(Status::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()));
+ Status status = patch.SetUpNoChecks(module.get());
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << ": 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<WithAudioPatch> patches;
+ for (const auto& srcSink : srcSinkGroup.second) {
+ if (!route.isExclusive) {
+ patches.emplace_back(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;
+ Status status = module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for 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)) {
+ Status status = module->resetAudioPatch(patchId);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for 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);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ProcessState::self()->setThreadPoolMaxThreadCount(1);
+ ProcessState::self()->startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 09446cd..38d6eff 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -34,6 +34,7 @@
#include <hwbinder/IPCThreadState.h>
+#include <android-base/expected.h>
#include <android-base/logging.h>
#include <system/audio_config.h>
@@ -130,6 +131,33 @@
using IDevice = ::android::hardware::audio::CPP_VERSION::IDevice;
using IDevicesFactory = ::android::hardware::audio::CPP_VERSION::IDevicesFactory;
+ static android::base::expected<std::vector<std::string>, std::string> getAllFactoryInstances() {
+ using ::android::hardware::audio::CPP_VERSION::IDevicesFactory;
+ const std::string factoryDescriptor = IDevicesFactory::descriptor;
+ // Make sure that the instance is the exact minor version.
+ // Using a 7.1 factory for 7.0 test is not always possible because
+ // 7.1 can be configured via the XML config to use features that are
+ // absent in 7.0.
+ auto instances = ::android::hardware::getAllHalInstanceNames(factoryDescriptor);
+ if (instances.empty()) return instances;
+ // Use the default instance for checking the implementation version.
+ auto defaultInstance = IDevicesFactory::getService("default");
+ if (defaultInstance == nullptr) {
+ return ::android::base::unexpected("Failed to obtain IDevicesFactory/default");
+ }
+ std::string actualDescriptor;
+ auto intDescRet = defaultInstance->interfaceDescriptor(
+ [&](const auto& descriptor) { actualDescriptor = descriptor; });
+ if (!intDescRet.isOk()) {
+ return ::android::base::unexpected("Failed to obtain interface descriptor: " +
+ intDescRet.description());
+ }
+ if (factoryDescriptor == actualDescriptor)
+ return instances;
+ else
+ return {};
+ }
+
virtual ~HidlTest() = default;
// public access to avoid annoyances when using this method in template classes
// derived from test classes
@@ -174,9 +202,11 @@
}
TEST(CheckConfig, audioPolicyConfigurationValidation) {
- const auto factories = ::android::hardware::getAllHalInstanceNames(
- ::android::hardware::audio::CPP_VERSION::IDevicesFactory::descriptor);
- if (factories.size() == 0) {
+ const auto factories = HidlTest::getAllFactoryInstances();
+ if (!factories.ok()) {
+ FAIL() << factories.error();
+ }
+ if (factories.value().size() == 0) {
GTEST_SKIP() << "Skipping audioPolicyConfigurationValidation because no factory instances "
"are found.";
}
@@ -205,11 +235,11 @@
const std::vector<DeviceParameter>& getDeviceParameters() {
static std::vector<DeviceParameter> parameters = [] {
std::vector<DeviceParameter> result;
- const auto factories = ::android::hardware::getAllHalInstanceNames(
- ::android::hardware::audio::CPP_VERSION::IDevicesFactory::descriptor);
+ const auto factories = HidlTest::getAllFactoryInstances();
+ if (!factories.ok()) return result;
const auto devices = getCachedPolicyConfig().getModulesWithDevicesNames();
result.reserve(devices.size());
- for (const auto& factoryName : factories) {
+ for (const auto& factoryName : factories.value()) {
for (const auto& deviceName : devices) {
if (DeviceManager::getInstance().get(factoryName, deviceName) != nullptr) {
result.emplace_back(factoryName, deviceName);
@@ -224,9 +254,9 @@
const std::vector<DeviceParameter>& getDeviceParametersForFactoryTests() {
static std::vector<DeviceParameter> parameters = [] {
std::vector<DeviceParameter> result;
- const auto factories = ::android::hardware::getAllHalInstanceNames(
- ::android::hardware::audio::CPP_VERSION::IDevicesFactory::descriptor);
- for (const auto& factoryName : factories) {
+ const auto factories = HidlTest::getAllFactoryInstances();
+ if (!factories.ok()) return result;
+ for (const auto& factoryName : factories.value()) {
result.emplace_back(factoryName,
DeviceManager::getInstance().getPrimary(factoryName) != nullptr
? DeviceManager::kPrimaryDevice
diff --git a/automotive/can/1.0/default/libnl++/Socket.cpp b/automotive/can/1.0/default/libnl++/Socket.cpp
index cc1d839..221063d 100644
--- a/automotive/can/1.0/default/libnl++/Socket.cpp
+++ b/automotive/can/1.0/default/libnl++/Socket.cpp
@@ -47,6 +47,17 @@
}
}
+void Socket::clearPollErr() {
+ sockaddr_nl sa = {};
+ socklen_t saLen = sizeof(sa);
+ const auto bytesReceived = recvfrom(mFd.get(), mReceiveBuffer.data(), mReceiveBuffer.size(), 0,
+ reinterpret_cast<sockaddr*>(&sa), &saLen);
+ if (errno != EINVAL) {
+ PLOG(WARNING) << "clearPollError() caught unexpected error: ";
+ }
+ CHECK_LE(bytesReceived, 0) << "clearPollError() didn't find an error!";
+}
+
bool Socket::send(const Buffer<nlmsghdr>& msg, const sockaddr_nl& sa) {
if constexpr (kSuperVerbose) {
LOG(VERBOSE) << (mFailed ? "(not) " : "") << "sending to " << sa.nl_pid << ": "
@@ -110,6 +121,13 @@
if constexpr (kSuperVerbose) {
LOG(VERBOSE) << "received from " << sa.nl_pid << ": " << toString(msg, mProtocol);
}
+ long headerByteTotal = 0;
+ for (const auto hdr : msg) {
+ headerByteTotal += hdr->nlmsg_len;
+ }
+ if (bytesReceived != headerByteTotal) {
+ LOG(ERROR) << "received " << bytesReceived << " bytes, header claims " << headerByteTotal;
+ }
return {msg, sa};
}
@@ -159,6 +177,7 @@
}
pollfd Socket::preparePoll(short events) {
+ CHECK(mFd.get() > 0) << "Netlink socket fd is invalid!";
return {mFd.get(), events, 0};
}
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h b/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
index 7ec0f7b..996a350 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
@@ -55,6 +55,12 @@
Socket(int protocol, unsigned pid = 0, uint32_t groups = 0);
/**
+ * Attempt to clear POLLERR by recv-ing.
+ * TODO(224850481): determine if this is necessary, or if the socket is locked up anyway.
+ */
+ void clearPollErr();
+
+ /**
* Send Netlink message with incremented sequence number to the Kernel.
*
* \param msg Message to send. Its sequence number will be updated.
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp
index 900560e..3ad101e 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/FamilyTracker.cpp
@@ -30,6 +30,7 @@
const auto familyName = msg.attributes.get<std::string>(CTRL_ATTR_FAMILY_NAME);
const auto familyId = msg.attributes.get<uint16_t>(CTRL_ATTR_FAMILY_ID);
+ // TODO(224845900): NETLINK_GENERIC == 16, and (erroneously?) sets off this warning
if (familyId < GENL_START_ALLOC) {
LOG(WARNING) << "Invalid family ID: " << familyId;
return true;
diff --git a/automotive/evs/1.1/default/Android.bp b/automotive/evs/1.1/default/Android.bp
index 4c08ef3..4172e63 100644
--- a/automotive/evs/1.1/default/Android.bp
+++ b/automotive/evs/1.1/default/Android.bp
@@ -13,6 +13,7 @@
proprietary: true,
relative_install_path: "hw",
srcs: [
+ ":libgui_frame_event_aidl",
"*.cpp",
],
init_rc: ["android.hardware.automotive.evs@1.1-service.rc"],
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index cfbbbd3..9edd2bd 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -374,6 +374,15 @@
{.config =
{
+ .prop = toInt(VehicleProperty::FUEL_VOLUME_DISPLAY_UNITS),
+ .access = VehiclePropertyAccess::READ_WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ .configArray = {(int)VehicleUnit::LITER, (int)VehicleUnit::US_GALLON},
+ },
+ .initialValue = {.int32Values = {(int)VehicleUnit::LITER}}},
+
+ {.config =
+ {
.prop = toInt(VehicleProperty::HW_KEY_INPUT),
.access = VehiclePropertyAccess::READ,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp
index e3c8dd6..d35792d 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/DefaultVhalImpl_test.cpp
@@ -141,7 +141,7 @@
TEST_F(DefaultVhalImplTest, testListProperties) {
std::vector<VehiclePropConfig> configs = mHal->listProperties();
- EXPECT_EQ((size_t)121, configs.size());
+ EXPECT_EQ((size_t)122, configs.size());
}
TEST_F(DefaultVhalImplTest, testGetDefaultPropertyFloat) {
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl
index dc9b876..47fc54b 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl
@@ -90,6 +90,14 @@
* area ID) are not allowed in a single call. This function must return
* {@link StatusCode#INVALID_ARG} for duplicate properties.
*
+ * The {@link VehiclePropValue#timestamp} field in request is ignored. The
+ * {@link VehiclePropValue#timestamp} field in {@link GetValueResult} must
+ * be the system uptime since boot when the value changes for
+ * ON_CHANGE property or when the value is checked according to polling rate
+ * for CONTINUOUS property. Note that for CONTINUOUS property, VHAL client
+ * reading the property multiple times between the polling interval will get
+ * the same timestamp.
+ *
* @param callback A callback interface, whose 'onGetValues' would be called
* after the value is fetched. Caller should use
* {@code android-automotive-large-parcelable} library to parse the
@@ -104,7 +112,7 @@
* Set vehicle property values.
*
* The {@link IVehicleCallback#onSetValues} function would be called after
- * the values set request are sent through vehicle bus or are failed to set.
+ * the values set request are sent through vehicle bus or failed to set.
* If the bus protocol supports confirmation, the callback would be called
* after getting the confirmation.
*
@@ -152,11 +160,36 @@
* Clients must be able to subscribe to multiple properties at a time
* depending on data provided in options argument.
*
- * For one callback, the is only one subscription for one property.
+ * For one callback, there is only one subscription for one property.
* A new subscription with a different sample rate would override the old
* subscription. One property could be subscribed multiple times for
* different callbacks.
*
+ * If error is returned, some of the properties failed to subscribe.
+ * Caller is safe to try again, since subscribing to an already subscribed
+ * property is okay.
+ *
+ * The specified sample rate is just a guidance. It is not guaranteed that
+ * the sample rate is achievable depending on how the polling refresh rate
+ * is. The actual property event rate might be higher/lower than the
+ * specified sampleRate, for example, if the polling rate can be 5 times/s
+ * or 10 times/s, subscribing to a sample rate of 7 might use the 5 times/s
+ * polling rate, thus generating 5 events/s. We only require that on
+ * average, the {@code minSampleRate} and {@code maxSampleRate} can be
+ * achieved, all the sampleRate within min and max would on average
+ * generates events with rate >= {@code minSampleRate} and <=
+ * {@code maxSampleRate}.
+ *
+ * The {@link VehiclePropValue#timestamp} field for each property event must
+ * be the system uptime since boot when the value changes for
+ * ON_CHANGE property or when the value is checked according to polling rate
+ * for CONTINUOUS property. Note that for CONTINUOUS property, VHAL client
+ * reading the property multiple times between the polling interval will get
+ * the same timestamp.
+ * For example, if the polling rate for a property is 10 times/s, no matter
+ * what the sampleRate specified in {@code options}, the timestamp for
+ * the timestamp is updated 10 times/s.
+ *
* @param callback The subscription callbacks.
* {@link IVehicleCallback#onPropertyEvent} would be called when a new
* property event arrives.
@@ -189,8 +222,13 @@
/**
* Unsubscribes from property events.
*
- * If 'callback' is not valid or 'propIds' were not subscribed for this
- * 'callback', this method must return {@link StatusCode#INVALID_ARG}.
+ * If 'callback' is not valid this method must return
+ * {@link StatusCode#INVALID_ARG}. If a specified propId was not subscribed
+ * before, this method must ignore that propId.
+ *
+ * If error is returned, some of the properties failed to unsubscribe.
+ * Caller is safe to try again, since unsubscribing an already unsubscribed
+ * property is okay.
*
* @param callback The callback used in the previous subscription.
* @param propIds The IDs for the properties to unsubscribe.
diff --git a/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h b/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
index fda0da9..01819fb 100644
--- a/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
+++ b/automotive/vehicle/aidl/impl/default_config/include/DefaultConfig.h
@@ -292,8 +292,9 @@
.prop = toInt(VehicleProperty::EV_CHARGE_CURRENT_DRAW_LIMIT),
.access = VehiclePropertyAccess::READ_WRITE,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ .configArray = {/*max current draw allowed by vehicle in amperes=*/20},
},
- .initialValue = {.floatValues = {(float)VehicleUnit::AMPERE}}},
+ .initialValue = {.floatValues = {(float)12.5}}},
{.config =
{
@@ -453,6 +454,15 @@
{.config =
{
+ .prop = toInt(VehicleProperty::FUEL_VOLUME_DISPLAY_UNITS),
+ .access = VehiclePropertyAccess::READ_WRITE,
+ .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+ .configArray = {(int)VehicleUnit::LITER, (int)VehicleUnit::US_GALLON},
+ },
+ .initialValue = {.int32Values = {(int)VehicleUnit::LITER}}},
+
+ {.config =
+ {
.prop = toInt(VehicleProperty::HW_KEY_INPUT),
.access = VehiclePropertyAccess::READ,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
index e799a28..34b2b24 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -17,10 +17,12 @@
#ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_hardware_include_FakeVehicleHardware_H_
#define android_hardware_automotive_vehicle_aidl_impl_fake_impl_hardware_include_FakeVehicleHardware_H_
+#include <ConcurrentQueue.h>
#include <DefaultConfig.h>
#include <FakeObd2Frame.h>
#include <FakeUserHal.h>
#include <IVehicleHardware.h>
+#include <RecurrentTimer.h>
#include <VehicleHalTypes.h>
#include <VehiclePropertyStore.h>
#include <android-base/parseint.h>
@@ -47,6 +49,8 @@
explicit FakeVehicleHardware(std::unique_ptr<VehiclePropValuePool> valuePool);
+ ~FakeVehicleHardware();
+
// Get all the property configs.
std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig>
getAllPropertyConfigs() const override;
@@ -82,6 +86,10 @@
void registerOnPropertySetErrorEvent(
std::unique_ptr<const PropertySetErrorCallback> callback) override;
+ // Update the sample rate for the [propId, areaId] pair.
+ aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
+ int32_t propId, int32_t areaId, float sampleRate) override;
+
protected:
// mValuePool is also used in mServerSidePropStore.
const std::shared_ptr<VehiclePropValuePool> mValuePool;
@@ -97,13 +105,45 @@
// Expose private methods to unit test.
friend class FakeVehicleHardwareTestHelper;
+ template <class CallbackType, class RequestType>
+ struct RequestWithCallback {
+ RequestType request;
+ std::shared_ptr<const CallbackType> callback;
+ };
+
+ template <class CallbackType, class RequestType>
+ class PendingRequestHandler {
+ public:
+ PendingRequestHandler(FakeVehicleHardware* hardware);
+
+ void addRequest(RequestType request, std::shared_ptr<const CallbackType> callback);
+
+ void stop();
+
+ private:
+ FakeVehicleHardware* mHardware;
+ std::thread mThread;
+ ConcurrentQueue<RequestWithCallback<CallbackType, RequestType>> mRequests;
+
+ void handleRequestsOnce();
+ };
+
const std::unique_ptr<obd2frame::FakeObd2Frame> mFakeObd2Frame;
const std::unique_ptr<FakeUserHal> mFakeUserHal;
- std::mutex mCallbackLock;
- std::unique_ptr<const PropertyChangeCallback> mOnPropertyChangeCallback
- GUARDED_BY(mCallbackLock);
- std::unique_ptr<const PropertySetErrorCallback> mOnPropertySetErrorCallback
- GUARDED_BY(mCallbackLock);
+ // RecurrentTimer is thread-safe.
+ std::unique_ptr<RecurrentTimer> mRecurrentTimer;
+ std::mutex mLock;
+ std::unique_ptr<const PropertyChangeCallback> mOnPropertyChangeCallback GUARDED_BY(mLock);
+ std::unique_ptr<const PropertySetErrorCallback> mOnPropertySetErrorCallback GUARDED_BY(mLock);
+ std::unordered_map<PropIdAreaId, std::shared_ptr<RecurrentTimer::Callback>, PropIdAreaIdHash>
+ mRecurrentActions GUARDED_BY(mLock);
+ // PendingRequestHandler is thread-safe.
+ mutable PendingRequestHandler<GetValuesCallback,
+ aidl::android::hardware::automotive::vehicle::GetValueRequest>
+ mPendingGetValueRequests;
+ mutable PendingRequestHandler<SetValuesCallback,
+ aidl::android::hardware::automotive::vehicle::SetValueRequest>
+ mPendingSetValueRequests;
void init();
// Stores the initial value to property store.
@@ -163,6 +203,10 @@
android::base::Result<void> checkArgumentsSize(const std::vector<std::string>& options,
size_t minSize);
+ aidl::android::hardware::automotive::vehicle::GetValueResult handleGetValueRequest(
+ const aidl::android::hardware::automotive::vehicle::GetValueRequest& request);
+ aidl::android::hardware::automotive::vehicle::SetValueResult handleSetValueRequest(
+ const aidl::android::hardware::automotive::vehicle::SetValueRequest& request);
};
} // namespace fake
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
index f8b64f2..7b3de58 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -66,6 +66,7 @@
using ::android::base::Error;
using ::android::base::ParseFloat;
using ::android::base::Result;
+using ::android::base::ScopedLockAssertion;
using ::android::base::StartsWith;
using ::android::base::StringPrintf;
@@ -131,21 +132,24 @@
}
FakeVehicleHardware::FakeVehicleHardware()
- : mValuePool(new VehiclePropValuePool),
- mServerSidePropStore(new VehiclePropertyStore(mValuePool)),
- mFakeObd2Frame(new obd2frame::FakeObd2Frame(mServerSidePropStore)),
- mFakeUserHal(new FakeUserHal(mValuePool)) {
- init();
-}
+ : FakeVehicleHardware(std::make_unique<VehiclePropValuePool>()) {}
FakeVehicleHardware::FakeVehicleHardware(std::unique_ptr<VehiclePropValuePool> valuePool)
: mValuePool(std::move(valuePool)),
mServerSidePropStore(new VehiclePropertyStore(mValuePool)),
mFakeObd2Frame(new obd2frame::FakeObd2Frame(mServerSidePropStore)),
- mFakeUserHal(new FakeUserHal(mValuePool)) {
+ mFakeUserHal(new FakeUserHal(mValuePool)),
+ mRecurrentTimer(new RecurrentTimer()),
+ mPendingGetValueRequests(this),
+ mPendingSetValueRequests(this) {
init();
}
+FakeVehicleHardware::~FakeVehicleHardware() {
+ mPendingGetValueRequests.stop();
+ mPendingSetValueRequests.stop();
+}
+
void FakeVehicleHardware::init() {
for (auto& it : defaultconfig::getDefaultConfigs()) {
VehiclePropConfig cfg = it.config;
@@ -430,37 +434,25 @@
StatusCode FakeVehicleHardware::setValues(std::shared_ptr<const SetValuesCallback> callback,
const std::vector<SetValueRequest>& requests) {
- std::vector<SetValueResult> results;
for (auto& request : requests) {
- const VehiclePropValue& value = request.value;
- int propId = value.prop;
-
if (FAKE_VEHICLEHARDWARE_DEBUG) {
- ALOGD("Set value for property ID: %d", propId);
+ ALOGD("Set value for property ID: %d", request.value.prop);
}
- SetValueResult setValueResult;
- setValueResult.requestId = request.requestId;
-
- if (auto result = setValue(value); !result.ok()) {
- ALOGE("failed to set value, error: %s, code: %d", getErrorMsg(result).c_str(),
- getIntErrorCode(result));
- setValueResult.status = getErrorCode(result);
- } else {
- setValueResult.status = StatusCode::OK;
- }
-
- results.push_back(std::move(setValueResult));
+ // In a real VHAL implementation, you could either send the setValue request to vehicle bus
+ // here in the binder thread, or you could send the request in setValue which runs in
+ // the handler thread. If you decide to send the setValue request here, you should not
+ // wait for the response here and the handler thread should handle the setValue response.
+ mPendingSetValueRequests.addRequest(request, callback);
}
- // In the real vhal, the values will be sent to Car ECU. We just pretend it is done here and
- // send back the updated property values to client.
- (*callback)(std::move(results));
-
return StatusCode::OK;
}
VhalResult<void> FakeVehicleHardware::setValue(const VehiclePropValue& value) {
+ // In a real VHAL implementation, this will send the request to vehicle bus if not already
+ // sent in setValues, and wait for the response from vehicle bus.
+ // Here we are just updating mValuePool.
bool isSpecialValue = false;
auto setSpecialValueResult = maybeSetSpecialValue(value, &isSpecialValue);
@@ -487,41 +479,59 @@
return {};
}
-StatusCode FakeVehicleHardware::getValues(std::shared_ptr<const GetValuesCallback> callback,
- const std::vector<GetValueRequest>& requests) const {
- std::vector<GetValueResult> results;
- for (auto& request : requests) {
- const VehiclePropValue& value = request.prop;
+SetValueResult FakeVehicleHardware::handleSetValueRequest(const SetValueRequest& request) {
+ SetValueResult setValueResult;
+ setValueResult.requestId = request.requestId;
- if (FAKE_VEHICLEHARDWARE_DEBUG) {
- ALOGD("getValues(%d)", value.prop);
- }
-
- GetValueResult getValueResult;
- getValueResult.requestId = request.requestId;
-
- auto result = getValue(value);
- if (!result.ok()) {
- ALOGE("failed to get value, error: %s, code: %d", getErrorMsg(result).c_str(),
- getIntErrorCode(result));
- getValueResult.status = getErrorCode(result);
- } else {
- getValueResult.status = StatusCode::OK;
- getValueResult.prop = *result.value();
- }
- results.push_back(std::move(getValueResult));
+ if (auto result = setValue(request.value); !result.ok()) {
+ ALOGE("failed to set value, error: %s, code: %d", getErrorMsg(result).c_str(),
+ getIntErrorCode(result));
+ setValueResult.status = getErrorCode(result);
+ } else {
+ setValueResult.status = StatusCode::OK;
}
- // In a real VHAL implementation, getValue would be async and we would call the callback after
- // we actually received the values from vehicle bus. Here we are getting the result
- // synchronously so we could call the callback here.
- (*callback)(std::move(results));
+ return setValueResult;
+}
+
+StatusCode FakeVehicleHardware::getValues(std::shared_ptr<const GetValuesCallback> callback,
+ const std::vector<GetValueRequest>& requests) const {
+ for (auto& request : requests) {
+ if (FAKE_VEHICLEHARDWARE_DEBUG) {
+ ALOGD("getValues(%d)", request.prop.prop);
+ }
+
+ // In a real VHAL implementation, you could either send the getValue request to vehicle bus
+ // here in the binder thread, or you could send the request in getValue which runs in
+ // the handler thread. If you decide to send the getValue request here, you should not
+ // wait for the response here and the handler thread should handle the getValue response.
+ mPendingGetValueRequests.addRequest(request, callback);
+ }
return StatusCode::OK;
}
+GetValueResult FakeVehicleHardware::handleGetValueRequest(const GetValueRequest& request) {
+ GetValueResult getValueResult;
+ getValueResult.requestId = request.requestId;
+
+ auto result = getValue(request.prop);
+ if (!result.ok()) {
+ ALOGE("failed to get value, error: %s, code: %d", getErrorMsg(result).c_str(),
+ getIntErrorCode(result));
+ getValueResult.status = getErrorCode(result);
+ } else {
+ getValueResult.status = StatusCode::OK;
+ getValueResult.prop = *result.value();
+ }
+ return getValueResult;
+}
+
FakeVehicleHardware::ValueResultType FakeVehicleHardware::getValue(
const VehiclePropValue& value) const {
+ // In a real VHAL implementation, this will send the request to vehicle bus if not already
+ // sent in getValues, and wait for the response from vehicle bus.
+ // Here we are just reading value from mValuePool.
bool isSpecialValue = false;
auto result = maybeGetSpecialValue(value, &isSpecialValue);
if (isSpecialValue) {
@@ -567,6 +577,12 @@
result.buffer = dumpSpecificProperty(options);
} else if (EqualsIgnoreCase(option, "--set")) {
result.buffer = dumpSetProperties(options);
+ } else if (EqualsIgnoreCase(option, kUserHalDumpOption)) {
+ if (options.size() == 1) {
+ result.buffer = mFakeUserHal->showDumpHelp();
+ } else {
+ result.buffer = mFakeUserHal->dump(options[1]);
+ }
} else {
result.buffer = StringPrintf("Invalid option: %s\n", option.c_str());
}
@@ -584,7 +600,9 @@
"[-b BYTES_VALUE] [-a AREA_ID] : sets the value of property PROP. "
"Notice that the string, bytes and area value can be set just once, while the other can"
" have multiple values (so they're used in the respective array), "
- "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n";
+ "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n\n"
+ "Fake user HAL usage: \n" +
+ mFakeUserHal->showDumpHelp();
}
std::string FakeVehicleHardware::dumpAllProperties() {
@@ -837,18 +855,57 @@
void FakeVehicleHardware::registerOnPropertyChangeEvent(
std::unique_ptr<const PropertyChangeCallback> callback) {
- std::scoped_lock<std::mutex> lockGuard(mCallbackLock);
+ std::scoped_lock<std::mutex> lockGuard(mLock);
mOnPropertyChangeCallback = std::move(callback);
}
void FakeVehicleHardware::registerOnPropertySetErrorEvent(
std::unique_ptr<const PropertySetErrorCallback> callback) {
- std::scoped_lock<std::mutex> lockGuard(mCallbackLock);
+ std::scoped_lock<std::mutex> lockGuard(mLock);
mOnPropertySetErrorCallback = std::move(callback);
}
+StatusCode FakeVehicleHardware::updateSampleRate(int32_t propId, int32_t areaId, float sampleRate) {
+ // DefaultVehicleHal makes sure that sampleRate must be within minSampleRate and maxSampleRate.
+ // For fake implementation, we would write the same value with a new timestamp into propStore
+ // at sample rate.
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+
+ PropIdAreaId propIdAreaId{
+ .propId = propId,
+ .areaId = areaId,
+ };
+ if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
+ mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
+ }
+ if (sampleRate == 0) {
+ return StatusCode::OK;
+ }
+ int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRate);
+ auto action = std::make_shared<RecurrentTimer::Callback>([this, propId, areaId] {
+ // Refresh the property value. In real implementation, this should poll the latest value
+ // from vehicle bus. Here, we are just refreshing the existing value with a new timestamp.
+ auto result = getValue(VehiclePropValue{
+ .prop = propId,
+ .areaId = areaId,
+ });
+ if (!result.ok()) {
+ // Failed to read current value, skip refreshing.
+ return;
+ }
+ result.value()->timestamp = elapsedRealtimeNano();
+ // Must remove the value before writing, otherwise, we would generate no update event since
+ // the value is the same.
+ mServerSidePropStore->removeValue(*result.value());
+ mServerSidePropStore->writeValue(std::move(result.value()));
+ });
+ mRecurrentTimer->registerTimerCallback(interval, action);
+ mRecurrentActions[propIdAreaId] = action;
+ return StatusCode::OK;
+}
+
void FakeVehicleHardware::onValueChangeCallback(const VehiclePropValue& value) {
- std::scoped_lock<std::mutex> lockGuard(mCallbackLock);
+ std::scoped_lock<std::mutex> lockGuard(mLock);
if (mOnPropertyChangeCallback == nullptr) {
return;
@@ -935,6 +992,60 @@
return bytes;
}
+template <class CallbackType, class RequestType>
+FakeVehicleHardware::PendingRequestHandler<CallbackType, RequestType>::PendingRequestHandler(
+ FakeVehicleHardware* hardware)
+ : mHardware(hardware), mThread([this] {
+ while (mRequests.waitForItems()) {
+ handleRequestsOnce();
+ }
+ }) {}
+
+template <class CallbackType, class RequestType>
+void FakeVehicleHardware::PendingRequestHandler<CallbackType, RequestType>::addRequest(
+ RequestType request, std::shared_ptr<const CallbackType> callback) {
+ mRequests.push({
+ request,
+ callback,
+ });
+}
+
+template <class CallbackType, class RequestType>
+void FakeVehicleHardware::PendingRequestHandler<CallbackType, RequestType>::stop() {
+ mRequests.deactivate();
+ if (mThread.joinable()) {
+ mThread.join();
+ }
+}
+
+template <>
+void FakeVehicleHardware::PendingRequestHandler<FakeVehicleHardware::GetValuesCallback,
+ GetValueRequest>::handleRequestsOnce() {
+ std::unordered_map<std::shared_ptr<const GetValuesCallback>, std::vector<GetValueResult>>
+ callbackToResults;
+ for (const auto& rwc : mRequests.flush()) {
+ auto result = mHardware->handleGetValueRequest(rwc.request);
+ callbackToResults[rwc.callback].push_back(std::move(result));
+ }
+ for (const auto& [callback, results] : callbackToResults) {
+ (*callback)(std::move(results));
+ }
+}
+
+template <>
+void FakeVehicleHardware::PendingRequestHandler<FakeVehicleHardware::SetValuesCallback,
+ SetValueRequest>::handleRequestsOnce() {
+ std::unordered_map<std::shared_ptr<const SetValuesCallback>, std::vector<SetValueResult>>
+ callbackToResults;
+ for (const auto& rwc : mRequests.flush()) {
+ auto result = mHardware->handleSetValueRequest(rwc.request);
+ callbackToResults[rwc.callback].push_back(std::move(result));
+ }
+ for (const auto& [callback, results] : callbackToResults) {
+ (*callback)(std::move(results));
+ }
+}
+
} // namespace fake
} // namespace vehicle
} // namespace automotive
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
index 7a7fb37..3e8f634 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -25,12 +25,17 @@
#include <android-base/expected.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/Log.h>
#include <utils/SystemClock.h>
#include <inttypes.h>
+#include <chrono>
+#include <condition_variable>
+#include <unordered_map>
+#include <unordered_set>
#include <vector>
namespace android {
@@ -53,6 +58,7 @@
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::android::base::expected;
+using ::android::base::ScopedLockAssertion;
using ::android::base::StringPrintf;
using ::android::base::unexpected;
using ::testing::ContainerEq;
@@ -60,6 +66,8 @@
using ::testing::Eq;
using ::testing::WhenSortedBy;
+using std::chrono::milliseconds;
+
constexpr int INVALID_PROP_ID = 0;
constexpr char CAR_MAKE[] = "Default Car";
@@ -93,11 +101,51 @@
FakeVehicleHardware* getHardware() { return &mHardware; }
StatusCode setValues(const std::vector<SetValueRequest>& requests) {
- return getHardware()->setValues(mSetValuesCallback, requests);
+ {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ for (const auto& request : requests) {
+ mPendingSetValueRequests.insert(request.requestId);
+ }
+ }
+ if (StatusCode status = getHardware()->setValues(mSetValuesCallback, requests);
+ status != StatusCode::OK) {
+ return status;
+ }
+ std::unique_lock<std::mutex> lk(mLock);
+ // Wait for the onSetValueResults.
+ bool result = mCv.wait_for(lk, milliseconds(1000), [this] {
+ ScopedLockAssertion lockAssertion(mLock);
+ return mPendingSetValueRequests.size() == 0;
+ });
+ if (!result) {
+ ALOGE("wait for callbacks for setValues timed-out");
+ return StatusCode::INTERNAL_ERROR;
+ }
+ return StatusCode::OK;
}
StatusCode getValues(const std::vector<GetValueRequest>& requests) {
- return getHardware()->getValues(mGetValuesCallback, requests);
+ {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ for (const auto& request : requests) {
+ mPendingGetValueRequests.insert(request.requestId);
+ }
+ }
+ if (StatusCode status = getHardware()->getValues(mGetValuesCallback, requests);
+ status != StatusCode::OK) {
+ return status;
+ }
+ std::unique_lock<std::mutex> lk(mLock);
+ // Wait for the onGetValueResults.
+ bool result = mCv.wait_for(lk, milliseconds(1000), [this] {
+ ScopedLockAssertion lockAssertion(mLock);
+ return mPendingGetValueRequests.size() == 0;
+ });
+ if (!result) {
+ ALOGE("wait for callbacks for getValues timed-out");
+ return StatusCode::INTERNAL_ERROR;
+ }
+ return StatusCode::OK;
}
StatusCode setValue(const VehiclePropValue& value) {
@@ -158,30 +206,69 @@
}
void onSetValues(std::vector<SetValueResult> results) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
for (auto& result : results) {
mSetValueResults.push_back(result);
+ mPendingSetValueRequests.erase(result.requestId);
}
+ mCv.notify_all();
}
- const std::vector<SetValueResult>& getSetValueResults() { return mSetValueResults; }
+ const std::vector<SetValueResult>& getSetValueResults() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ return mSetValueResults;
+ }
void onGetValues(std::vector<GetValueResult> results) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
for (auto& result : results) {
mGetValueResults.push_back(result);
+ mPendingGetValueRequests.erase(result.requestId);
}
+ mCv.notify_all();
}
- const std::vector<GetValueResult>& getGetValueResults() { return mGetValueResults; }
+ const std::vector<GetValueResult>& getGetValueResults() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ return mGetValueResults;
+ }
void onPropertyChangeEvent(std::vector<VehiclePropValue> values) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
for (auto& value : values) {
mChangedProperties.push_back(value);
+ PropIdAreaId propIdAreaId{
+ .propId = value.prop,
+ .areaId = value.areaId,
+ };
+ mEventCount[propIdAreaId]++;
}
+ mCv.notify_all();
}
- const std::vector<VehiclePropValue>& getChangedProperties() { return mChangedProperties; }
+ const std::vector<VehiclePropValue>& getChangedProperties() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ return mChangedProperties;
+ }
- void clearChangedProperties() { mChangedProperties.clear(); }
+ bool waitForChangedProperties(int32_t propId, int32_t areaId, size_t count,
+ milliseconds timeout) {
+ PropIdAreaId propIdAreaId{
+ .propId = propId,
+ .areaId = areaId,
+ };
+ std::unique_lock<std::mutex> lk(mLock);
+ return mCv.wait_for(lk, timeout, [this, propIdAreaId, count] {
+ ScopedLockAssertion lockAssertion(mLock);
+ return mEventCount[propIdAreaId] >= count;
+ });
+ }
+
+ void clearChangedProperties() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ mEventCount.clear();
+ mChangedProperties.clear();
+ }
static void addSetValueRequest(std::vector<SetValueRequest>& requests,
std::vector<SetValueResult>& expectedResults, int64_t requestId,
@@ -246,11 +333,16 @@
private:
FakeVehicleHardware mHardware;
- std::vector<SetValueResult> mSetValueResults;
- std::vector<GetValueResult> mGetValueResults;
- std::vector<VehiclePropValue> mChangedProperties;
std::shared_ptr<IVehicleHardware::SetValuesCallback> mSetValuesCallback;
std::shared_ptr<IVehicleHardware::GetValuesCallback> mGetValuesCallback;
+ std::condition_variable mCv;
+ std::mutex mLock;
+ std::unordered_map<PropIdAreaId, size_t, PropIdAreaIdHash> mEventCount GUARDED_BY(mLock);
+ std::vector<SetValueResult> mSetValueResults GUARDED_BY(mLock);
+ std::vector<GetValueResult> mGetValueResults GUARDED_BY(mLock);
+ std::vector<VehiclePropValue> mChangedProperties GUARDED_BY(mLock);
+ std::unordered_set<int64_t> mPendingSetValueRequests GUARDED_BY(mLock);
+ std::unordered_set<int64_t> mPendingGetValueRequests GUARDED_BY(mLock);
};
TEST_F(FakeVehicleHardwareTest, testGetAllPropertyConfigs) {
@@ -1313,6 +1405,28 @@
ASSERT_THAT(result.buffer, ContainsRegex("Invalid option: --invalid"));
}
+TEST_F(FakeVehicleHardwareTest, testDumpFakeUserHalHelp) {
+ std::vector<std::string> options;
+ options.push_back("--user-hal");
+
+ DumpResult result = getHardware()->dump(options);
+ ASSERT_FALSE(result.callerShouldDumpState);
+ ASSERT_NE(result.buffer, "");
+ ASSERT_THAT(result.buffer, ContainsRegex("dumps state used for user management"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpFakeUserHal) {
+ std::vector<std::string> options;
+ options.push_back("--user-hal");
+ // Indent: " ".
+ options.push_back(" ");
+
+ DumpResult result = getHardware()->dump(options);
+ ASSERT_FALSE(result.callerShouldDumpState);
+ ASSERT_NE(result.buffer, "");
+ ASSERT_THAT(result.buffer, ContainsRegex(" No InitialUserInfo response\n"));
+}
+
struct SetPropTestCase {
std::string test_name;
std::vector<std::string> options;
@@ -1510,6 +1624,35 @@
ASSERT_EQ(result.value().value.byteValues, std::vector<uint8_t>({0x04, 0x03, 0x02, 0x01}));
}
+TEST_F(FakeVehicleHardwareTest, testUpdateSampleRate) {
+ int32_t propSpeed = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
+ int32_t propSteering = toInt(VehicleProperty::PERF_STEERING_ANGLE);
+ int32_t areaId = 0;
+ getHardware()->updateSampleRate(propSpeed, areaId, 5);
+
+ ASSERT_TRUE(waitForChangedProperties(propSpeed, areaId, /*count=*/5, milliseconds(1500)))
+ << "not enough events generated for speed";
+
+ getHardware()->updateSampleRate(propSteering, areaId, 10);
+
+ ASSERT_TRUE(waitForChangedProperties(propSteering, areaId, /*count=*/10, milliseconds(1500)))
+ << "not enough events generated for steering";
+
+ int64_t timestamp = elapsedRealtimeNano();
+ // Disable refreshing for propSpeed.
+ getHardware()->updateSampleRate(propSpeed, areaId, 0);
+ clearChangedProperties();
+
+ ASSERT_TRUE(waitForChangedProperties(propSteering, areaId, /*count=*/5, milliseconds(1500)))
+ << "should still receive steering events after disable polling for speed";
+ auto updatedValues = getChangedProperties();
+ for (auto& value : updatedValues) {
+ ASSERT_GE(value.timestamp, timestamp);
+ ASSERT_EQ(value.prop, propSteering);
+ ASSERT_EQ(value.areaId, areaId);
+ }
+}
+
} // namespace fake
} // namespace vehicle
} // namespace automotive
diff --git a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
index 4a38827..759db41 100644
--- a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
@@ -80,6 +80,35 @@
const std::vector<aidl::android::hardware::automotive::vehicle::GetValueRequest>&
requests) const = 0;
+ // Update the sampling rate for the specified property and the specified areaId (0 for global
+ // property) if server supports it. The property must be a continuous property.
+ // {@code sampleRate} means that for this specific property, the server must generate at least
+ // this many OnPropertyChange events per seconds.
+ // A sampleRate of 0 means the property is no longer subscribed and server does not need to
+ // generate any onPropertyEvent for this property.
+ // This would be called if sample rate is updated for a subscriber, a new subscriber is added
+ // or an existing subscriber is removed. For example:
+ // 1. We have no subscriber for speed.
+ // 2. A new subscriber is subscribing speed for 10 times/s, updsateSampleRate would be called
+ // with sampleRate as 10. The impl is now polling vehicle speed from bus 10 times/s.
+ // 3. A new subscriber is subscribing speed for 5 times/s, because it is less than 10
+ // times/sec, updateSampleRate would not be called.
+ // 4. The initial subscriber is removed, updateSampleRate would be called with sampleRate as
+ // 5, because now it only needs to report event 5times/sec. The impl can now poll vehicle
+ // speed 5 times/s. If the impl is still polling at 10 times/s, that is okay as long as
+ // the polling rate is larger than 5times/s. DefaultVehicleHal would ignore the additional
+ // events.
+ // 5. The second subscriber is removed, updateSampleRate would be called with sampleRate as 0.
+ // The impl can optionally disable the polling for vehicle speed.
+ //
+ // If the impl is always polling at {@code maxSampleRate} as specified in config, then this
+ // function can be a no-op.
+ virtual aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
+ [[maybe_unused]] int32_t propId, [[maybe_unused]] int32_t areaId,
+ [[maybe_unused]] float sampleRate) {
+ return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
+ }
+
// Dump debug information in the server.
virtual DumpResult dump(const std::vector<std::string>& options) = 0;
diff --git a/automotive/vehicle/aidl/impl/vhal/include/RecurrentTimer.h b/automotive/vehicle/aidl/impl/utils/common/include/RecurrentTimer.h
similarity index 100%
rename from automotive/vehicle/aidl/impl/vhal/include/RecurrentTimer.h
rename to automotive/vehicle/aidl/impl/utils/common/include/RecurrentTimer.h
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
index 6d7d131..c94bad6 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
@@ -21,6 +21,7 @@
#include <android-base/format.h>
#include <android-base/result.h>
+#include <math/HashCombine.h>
#include <utils/Log.h>
namespace android {
@@ -310,6 +311,24 @@
return toScopedAStatus(result, getErrorCode(result), additionalErrorMsg);
}
+struct PropIdAreaId {
+ int32_t propId;
+ int32_t areaId;
+
+ inline bool operator==(const PropIdAreaId& other) const {
+ return areaId == other.areaId && propId == other.propId;
+ }
+};
+
+struct PropIdAreaIdHash {
+ inline size_t operator()(const PropIdAreaId& propIdAreaId) const {
+ size_t res = 0;
+ hashCombine(res, propIdAreaId.propId);
+ hashCombine(res, propIdAreaId.areaId);
+ return res;
+ }
+};
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/src/RecurrentTimer.cpp b/automotive/vehicle/aidl/impl/utils/common/src/RecurrentTimer.cpp
similarity index 100%
rename from automotive/vehicle/aidl/impl/vhal/src/RecurrentTimer.cpp
rename to automotive/vehicle/aidl/impl/utils/common/src/RecurrentTimer.cpp
diff --git a/automotive/vehicle/aidl/impl/vhal/test/RecurrentTimerTest.cpp b/automotive/vehicle/aidl/impl/utils/common/test/RecurrentTimerTest.cpp
similarity index 100%
rename from automotive/vehicle/aidl/impl/vhal/test/RecurrentTimerTest.cpp
rename to automotive/vehicle/aidl/impl/utils/common/test/RecurrentTimerTest.cpp
diff --git a/automotive/vehicle/aidl/impl/vhal/Android.bp b/automotive/vehicle/aidl/impl/vhal/Android.bp
index 49f48f7..5abcaf6 100644
--- a/automotive/vehicle/aidl/impl/vhal/Android.bp
+++ b/automotive/vehicle/aidl/impl/vhal/Android.bp
@@ -54,7 +54,6 @@
srcs: [
"src/ConnectedClient.cpp",
"src/DefaultVehicleHal.cpp",
- "src/RecurrentTimer.cpp",
"src/SubscriptionManager.cpp",
],
static_libs: [
diff --git a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
index f646b6b..9c29816 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
@@ -17,10 +17,11 @@
#ifndef android_hardware_automotive_vehicle_aidl_impl_vhal_include_DefaultVehicleHal_H_
#define android_hardware_automotive_vehicle_aidl_impl_vhal_include_DefaultVehicleHal_H_
-#include "ConnectedClient.h"
-#include "ParcelableUtils.h"
-#include "PendingRequestPool.h"
-#include "SubscriptionManager.h"
+#include <ConnectedClient.h>
+#include <ParcelableUtils.h>
+#include <PendingRequestPool.h>
+#include <RecurrentTimer.h>
+#include <SubscriptionManager.h>
#include <ConcurrentQueue.h>
#include <IVehicleHardware.h>
@@ -163,7 +164,7 @@
static constexpr int64_t TIMEOUT_IN_NANO = 30'000'000'000;
// heart beat event interval: 3s
static constexpr int64_t HEART_BEAT_INTERVAL_IN_NANO = 3'000'000'000;
- const std::shared_ptr<IVehicleHardware> mVehicleHardware;
+ std::unique_ptr<IVehicleHardware> mVehicleHardware;
// mConfigsByPropId and mConfigFile are only modified during initialization, so no need to
// lock guard them.
@@ -188,6 +189,8 @@
// mBinderImpl is only going to be changed in test.
std::unique_ptr<IBinder> mBinderImpl;
+ // Only initialized once.
+ std::shared_ptr<std::function<void()>> mRecurrentAction;
// RecurrentTimer is thread-safe.
RecurrentTimer mRecurrentTimer;
@@ -243,18 +246,12 @@
std::unordered_map<const AIBinder*, std::shared_ptr<T>>* clients,
const CallbackType& callback, std::shared_ptr<PendingRequestPool> pendingRequestPool);
- static void getValueFromHardwareCallCallback(
- std::weak_ptr<IVehicleHardware> vehicleHardware,
- std::shared_ptr<SubscribeIdByClient> subscribeIdByClient,
- std::shared_ptr<SubscriptionClients> subscriptionClients, const CallbackType& callback,
- const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
-
static void onPropertyChangeEvent(
std::weak_ptr<SubscriptionManager> subscriptionManager,
const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
updatedValues);
- static void checkHealth(std::weak_ptr<IVehicleHardware> hardware,
+ static void checkHealth(IVehicleHardware* hardware,
std::weak_ptr<SubscriptionManager> subscriptionManager);
static void onBinderDied(void* cookie);
diff --git a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
index b0d6701..7c8f1b4 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
@@ -17,15 +17,16 @@
#ifndef android_hardware_automotive_vehicle_aidl_impl_vhal_include_SubscriptionManager_H_
#define android_hardware_automotive_vehicle_aidl_impl_vhal_include_SubscriptionManager_H_
-#include "RecurrentTimer.h"
-
+#include <IVehicleHardware.h>
#include <VehicleHalTypes.h>
+#include <VehicleUtils.h>
#include <aidl/android/hardware/automotive/vehicle/IVehicleCallback.h>
#include <android-base/result.h>
#include <android-base/thread_annotations.h>
#include <mutex>
+#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -35,43 +36,58 @@
namespace automotive {
namespace vehicle {
+// A class to represent all the subscription configs for a continuous [propId, areaId].
+class ContSubConfigs final {
+ public:
+ using ClientIdType = const AIBinder*;
+
+ void addClient(const ClientIdType& clientId, float sampleRate);
+ void removeClient(const ClientIdType& clientId);
+ float getMaxSampleRate();
+
+ private:
+ float mMaxSampleRate = 0.;
+ std::unordered_map<ClientIdType, float> mSampleRates;
+
+ void refreshMaxSampleRate();
+};
+
// A thread-safe subscription manager that manages all VHAL subscriptions.
class SubscriptionManager final {
public:
using ClientIdType = const AIBinder*;
using CallbackType =
std::shared_ptr<aidl::android::hardware::automotive::vehicle::IVehicleCallback>;
- using GetValueFunc = std::function<void(
- const CallbackType& callback,
- const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)>;
- explicit SubscriptionManager(GetValueFunc&& action);
+ explicit SubscriptionManager(IVehicleHardware* hardware);
~SubscriptionManager();
// Subscribes to properties according to {@code SubscribeOptions}. Note that all option must
// contain non-empty areaIds field, which contains all area IDs to subscribe. As a result,
// the options here is different from the options passed from VHAL client.
- // Returns error if any of the subscribe options is not valid. If error is returned, no
- // properties would be subscribed.
+ // Returns error if any of the subscribe options is not valid or one of the properties failed
+ // to subscribe. Part of the properties maybe be subscribed successfully if this function
+ // returns error. Caller is safe to retry since subscribing to an already subscribed property
+ // is okay.
// Returns ok if all the options are parsed correctly and all the properties are subscribed.
- android::base::Result<void> subscribe(
+ VhalResult<void> subscribe(
const CallbackType& callback,
const std::vector<aidl::android::hardware::automotive::vehicle::SubscribeOptions>&
options,
bool isContinuousProperty);
// Unsubscribes from the properties for the client.
- // Returns error if the client was not subscribed before or one of the given property was not
- // subscribed. If error is returned, no property would be unsubscribed.
+ // Returns error if the client was not subscribed before, or one of the given property was not
+ // subscribed, or one of the property failed to unsubscribe. Caller is safe to retry since
+ // unsubscribing to an already unsubscribed property is okay (it would be ignored).
// Returns ok if all the requested properties for the client are unsubscribed.
- android::base::Result<void> unsubscribe(ClientIdType client,
- const std::vector<int32_t>& propIds);
+ VhalResult<void> unsubscribe(ClientIdType client, const std::vector<int32_t>& propIds);
// Unsubscribes from all the properties for the client.
- // Returns error if the client was not subscribed before. If error is returned, no property
- // would be unsubscribed.
+ // Returns error if the client was not subscribed before or one of the subscribed properties
+ // for the client failed to unsubscribe. Caller is safe to retry.
// Returns ok if all the properties for the client are unsubscribed.
- android::base::Result<void> unsubscribe(ClientIdType client);
+ VhalResult<void> unsubscribe(ClientIdType client);
// For a list of updated properties, returns a map that maps clients subscribing to
// the updated properties to a list of updated values. This would only return on-change property
@@ -83,6 +99,11 @@
const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
updatedValues);
+ // Gets the sample rate for the continuous property. Returns {@code std::nullopt} if the
+ // property has not been subscribed before or is not a continuous property.
+ std::optional<float> getSampleRate(const ClientIdType& clientId, int32_t propId,
+ int32_t areaId);
+
// Checks whether the sample rate is valid.
static bool checkSampleRate(float sampleRate);
@@ -90,65 +111,28 @@
// Friend class for testing.
friend class DefaultVehicleHalTest;
- struct PropIdAreaId {
- int32_t propId;
- int32_t areaId;
-
- bool operator==(const PropIdAreaId& other) const;
- };
-
- struct PropIdAreaIdHash {
- size_t operator()(const PropIdAreaId& propIdAreaId) const;
- };
-
- // A class to represent a registered subscription.
- class Subscription {
- public:
- Subscription() = default;
-
- Subscription(const Subscription&) = delete;
-
- virtual ~Subscription() = default;
-
- virtual bool isOnChange();
- };
-
- // A subscription for OnContinuous property. The registered action would be called recurrently
- // until this class is destructed.
- class RecurrentSubscription final : public Subscription {
- public:
- explicit RecurrentSubscription(std::shared_ptr<RecurrentTimer> timer,
- std::function<void()>&& action, int64_t interval);
- ~RecurrentSubscription();
-
- bool isOnChange() override;
-
- private:
- std::shared_ptr<std::function<void()>> mAction;
- std::shared_ptr<RecurrentTimer> mTimer;
- };
-
- // A subscription for OnChange property.
- class OnChangeSubscription final : public Subscription {
- public:
- bool isOnChange() override;
- };
+ IVehicleHardware* mVehicleHardware;
mutable std::mutex mLock;
std::unordered_map<PropIdAreaId, std::unordered_map<ClientIdType, CallbackType>,
PropIdAreaIdHash>
mClientsByPropIdArea GUARDED_BY(mLock);
- std::unordered_map<ClientIdType, std::unordered_map<PropIdAreaId, std::unique_ptr<Subscription>,
- PropIdAreaIdHash>>
- mSubscriptionsByClient GUARDED_BY(mLock);
- // RecurrentTimer is thread-safe.
- std::shared_ptr<RecurrentTimer> mTimer;
- const GetValueFunc mGetValue;
+ std::unordered_map<ClientIdType, std::unordered_set<PropIdAreaId, PropIdAreaIdHash>>
+ mSubscribedPropsByClient GUARDED_BY(mLock);
+ std::unordered_map<PropIdAreaId, ContSubConfigs, PropIdAreaIdHash> mContSubConfigsByPropIdArea
+ GUARDED_BY(mLock);
- static android::base::Result<int64_t> getInterval(float sampleRate);
+ VhalResult<void> updateSampleRateLocked(const ClientIdType& clientId,
+ const PropIdAreaId& propIdAreaId, float sampleRate)
+ REQUIRES(mLock);
+ VhalResult<void> removeSampleRateLocked(const ClientIdType& clientId,
+ const PropIdAreaId& propIdAreaId) REQUIRES(mLock);
// Checks whether the manager is empty. For testing purpose.
bool isEmpty();
+
+ // Get the interval in nanoseconds accroding to sample rate.
+ static android::base::Result<int64_t> getInterval(float sampleRate);
};
} // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index 82f2c1b..b191aef 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -144,15 +144,11 @@
}
mSubscriptionClients = std::make_shared<SubscriptionClients>(mPendingRequestPool);
+ mSubscriptionClients = std::make_shared<SubscriptionClients>(mPendingRequestPool);
auto subscribeIdByClient = std::make_shared<SubscribeIdByClient>();
- // Make a weak copy of IVehicleHardware because subscriptionManager uses IVehicleHardware and
- // IVehicleHardware uses subscriptionManager. We want to avoid cyclic reference.
- std::weak_ptr<IVehicleHardware> hardwareCopy = mVehicleHardware;
- SubscriptionManager::GetValueFunc getValueFunc = std::bind(
- &DefaultVehicleHal::getValueFromHardwareCallCallback, hardwareCopy, subscribeIdByClient,
- mSubscriptionClients, std::placeholders::_1, std::placeholders::_2);
- mSubscriptionManager = std::make_shared<SubscriptionManager>(std::move(getValueFunc));
+ IVehicleHardware* hardwarePtr = mVehicleHardware.get();
+ mSubscriptionManager = std::make_shared<SubscriptionManager>(hardwarePtr);
std::weak_ptr<SubscriptionManager> subscriptionManagerCopy = mSubscriptionManager;
mVehicleHardware->registerOnPropertyChangeEvent(
@@ -162,11 +158,11 @@
}));
// Register heartbeat event.
- mRecurrentTimer.registerTimerCallback(
- HEART_BEAT_INTERVAL_IN_NANO,
- std::make_shared<std::function<void()>>([hardwareCopy, subscriptionManagerCopy]() {
- checkHealth(hardwareCopy, subscriptionManagerCopy);
- }));
+ mRecurrentAction =
+ std::make_shared<std::function<void()>>([hardwarePtr, subscriptionManagerCopy]() {
+ checkHealth(hardwarePtr, subscriptionManagerCopy);
+ });
+ mRecurrentTimer.registerTimerCallback(HEART_BEAT_INTERVAL_IN_NANO, mRecurrentAction);
mBinderImpl = std::make_unique<AIBinderImpl>();
mOnBinderDiedUnlinkedHandlerThread = std::thread([this] { onBinderDiedUnlinkedHandler(); });
@@ -183,6 +179,13 @@
if (mOnBinderDiedUnlinkedHandlerThread.joinable()) {
mOnBinderDiedUnlinkedHandlerThread.join();
}
+ // mRecurrentAction uses pointer to mVehicleHardware, so it has to be unregistered before
+ // mVehicleHardware.
+ mRecurrentTimer.unregisterTimerCallback(mRecurrentAction);
+ // mSubscriptionManager uses pointer to mVehicleHardware, so it has to be destroyed before
+ // mVehicleHardware.
+ mSubscriptionManager.reset();
+ mVehicleHardware.reset();
}
void DefaultVehicleHal::onPropertyChangeEvent(
@@ -294,43 +297,6 @@
std::unordered_map<const AIBinder*, std::shared_ptr<SubscriptionClient>>* clients,
const CallbackType& callback, std::shared_ptr<PendingRequestPool> pendingRequestPool);
-void DefaultVehicleHal::getValueFromHardwareCallCallback(
- std::weak_ptr<IVehicleHardware> vehicleHardware,
- std::shared_ptr<SubscribeIdByClient> subscribeIdByClient,
- std::shared_ptr<SubscriptionClients> subscriptionClients, const CallbackType& callback,
- const VehiclePropValue& value) {
- int64_t subscribeId = subscribeIdByClient->getId(callback);
- auto client = subscriptionClients->getClient(callback);
- if (client == nullptr) {
- ALOGW("subscribe[%" PRId64 "]: the client has died", subscribeId);
- return;
- }
- if (auto addRequestResult = client->addRequests({subscribeId}); !addRequestResult.ok()) {
- ALOGE("subscribe[%" PRId64 "]: too many pending requests, ignore the getValue request",
- subscribeId);
- return;
- }
-
- std::vector<GetValueRequest> hardwareRequests = {{
- .requestId = subscribeId,
- .prop = value,
- }};
-
- std::shared_ptr<IVehicleHardware> hardware = vehicleHardware.lock();
- if (hardware == nullptr) {
- ALOGW("the IVehicleHardware is destroyed, DefaultVehicleHal is ending");
- return;
- }
- if (StatusCode status = hardware->getValues(client->getResultCallback(), hardwareRequests);
- status != StatusCode::OK) {
- // If the hardware returns error, finish all the pending requests for this request because
- // we never expect hardware to call callback for these requests.
- client->tryFinishRequests({subscribeId});
- ALOGE("subscribe[%" PRId64 "]: failed to get value from VehicleHardware, code: %d",
- subscribeId, toInt(status));
- }
-}
-
void DefaultVehicleHal::setTimeout(int64_t timeoutInNano) {
mPendingRequestPool = std::make_unique<PendingRequestPool>(timeoutInNano);
}
@@ -705,12 +671,13 @@
// Since we have already check the sample rates, the following functions must succeed.
if (!onChangeSubscriptions.empty()) {
- mSubscriptionManager->subscribe(callback, onChangeSubscriptions,
- /*isContinuousProperty=*/false);
+ return toScopedAStatus(mSubscriptionManager->subscribe(callback, onChangeSubscriptions,
+ /*isContinuousProperty=*/false));
}
if (!continuousSubscriptions.empty()) {
- mSubscriptionManager->subscribe(callback, continuousSubscriptions,
- /*isContinuousProperty=*/true);
+ return toScopedAStatus(mSubscriptionManager->subscribe(callback,
+ continuousSubscriptions,
+ /*isContinuousProperty=*/true));
}
}
return ScopedAStatus::ok();
@@ -718,8 +685,7 @@
ScopedAStatus DefaultVehicleHal::unsubscribe(const CallbackType& callback,
const std::vector<int32_t>& propIds) {
- return toScopedAStatus(mSubscriptionManager->unsubscribe(callback->asBinder().get(), propIds),
- StatusCode::INVALID_ARG);
+ return toScopedAStatus(mSubscriptionManager->unsubscribe(callback->asBinder().get(), propIds));
}
ScopedAStatus DefaultVehicleHal::returnSharedMemory(const CallbackType&, int64_t) {
@@ -763,15 +729,9 @@
return {};
}
-void DefaultVehicleHal::checkHealth(std::weak_ptr<IVehicleHardware> hardware,
+void DefaultVehicleHal::checkHealth(IVehicleHardware* hardware,
std::weak_ptr<SubscriptionManager> subscriptionManager) {
- auto hardwarePtr = hardware.lock();
- if (hardwarePtr == nullptr) {
- ALOGW("the VehicleHardware is destroyed, DefaultVehicleHal is ending");
- return;
- }
-
- StatusCode status = hardwarePtr->checkHealth();
+ StatusCode status = hardware->checkHealth();
if (status != StatusCode::OK) {
ALOGE("VHAL check health returns non-okay status");
return;
diff --git a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
index 21bfba6..2694401 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
@@ -16,8 +16,11 @@
#include "SubscriptionManager.h"
-#include <math/HashCombine.h>
+#include <android-base/stringprintf.h>
#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+#include <inttypes.h>
namespace android {
namespace hardware {
@@ -31,33 +34,21 @@
} // namespace
using ::aidl::android::hardware::automotive::vehicle::IVehicleCallback;
+using ::aidl::android::hardware::automotive::vehicle::StatusCode;
using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::android::base::Error;
using ::android::base::Result;
+using ::android::base::StringPrintf;
using ::ndk::ScopedAStatus;
-bool SubscriptionManager::PropIdAreaId::operator==(const PropIdAreaId& other) const {
- return areaId == other.areaId && propId == other.propId;
-}
-
-size_t SubscriptionManager::PropIdAreaIdHash::operator()(PropIdAreaId const& propIdAreaId) const {
- size_t res = 0;
- hashCombine(res, propIdAreaId.propId);
- hashCombine(res, propIdAreaId.areaId);
- return res;
-}
-
-SubscriptionManager::SubscriptionManager(GetValueFunc&& action)
- : mTimer(std::make_shared<RecurrentTimer>()), mGetValue(std::move(action)) {}
+SubscriptionManager::SubscriptionManager(IVehicleHardware* hardware) : mVehicleHardware(hardware) {}
SubscriptionManager::~SubscriptionManager() {
std::scoped_lock<std::mutex> lockGuard(mLock);
mClientsByPropIdArea.clear();
- // RecurrentSubscription has reference to mGetValue, so it must be destroyed before mGetValue is
- // destroyed.
- mSubscriptionsByClient.clear();
+ mSubscribedPropsByClient.clear();
}
bool SubscriptionManager::checkSampleRate(float sampleRate) {
@@ -76,9 +67,84 @@
return interval;
}
-Result<void> SubscriptionManager::subscribe(const std::shared_ptr<IVehicleCallback>& callback,
- const std::vector<SubscribeOptions>& options,
- bool isContinuousProperty) {
+void ContSubConfigs::refreshMaxSampleRate() {
+ float maxSampleRate = 0.;
+ // This is not called frequently so a brute-focre is okay. More efficient way exists but this
+ // is simpler.
+ for (const auto& [_, sampleRate] : mSampleRates) {
+ if (sampleRate > maxSampleRate) {
+ maxSampleRate = sampleRate;
+ }
+ }
+ mMaxSampleRate = maxSampleRate;
+}
+
+void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRate) {
+ mSampleRates[clientId] = sampleRate;
+ refreshMaxSampleRate();
+}
+
+void ContSubConfigs::removeClient(const ClientIdType& clientId) {
+ mSampleRates.erase(clientId);
+ refreshMaxSampleRate();
+}
+
+float ContSubConfigs::getMaxSampleRate() {
+ return mMaxSampleRate;
+}
+
+VhalResult<void> SubscriptionManager::updateSampleRateLocked(const ClientIdType& clientId,
+ const PropIdAreaId& propIdAreaId,
+ float sampleRate) {
+ // Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
+ ContSubConfigs infoCopy = mContSubConfigsByPropIdArea[propIdAreaId];
+ infoCopy.addClient(clientId, sampleRate);
+ if (infoCopy.getMaxSampleRate() ==
+ mContSubConfigsByPropIdArea[propIdAreaId].getMaxSampleRate()) {
+ mContSubConfigsByPropIdArea[propIdAreaId] = infoCopy;
+ return {};
+ }
+ float newRate = infoCopy.getMaxSampleRate();
+ int32_t propId = propIdAreaId.propId;
+ int32_t areaId = propIdAreaId.areaId;
+ if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRate);
+ status != StatusCode::OK) {
+ return StatusError(status) << StringPrintf("failed to update sample rate for prop: %" PRId32
+ ", area"
+ ": %" PRId32 ", sample rate: %f",
+ propId, areaId, newRate);
+ }
+ mContSubConfigsByPropIdArea[propIdAreaId] = infoCopy;
+ return {};
+}
+
+VhalResult<void> SubscriptionManager::removeSampleRateLocked(const ClientIdType& clientId,
+ const PropIdAreaId& propIdAreaId) {
+ // Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
+ ContSubConfigs infoCopy = mContSubConfigsByPropIdArea[propIdAreaId];
+ infoCopy.removeClient(clientId);
+ if (infoCopy.getMaxSampleRate() ==
+ mContSubConfigsByPropIdArea[propIdAreaId].getMaxSampleRate()) {
+ mContSubConfigsByPropIdArea[propIdAreaId] = infoCopy;
+ return {};
+ }
+ float newRate = infoCopy.getMaxSampleRate();
+ int32_t propId = propIdAreaId.propId;
+ int32_t areaId = propIdAreaId.areaId;
+ if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRate);
+ status != StatusCode::OK) {
+ return StatusError(status) << StringPrintf("failed to update sample rate for prop: %" PRId32
+ ", area"
+ ": %" PRId32 ", sample rate: %f",
+ propId, areaId, newRate);
+ }
+ mContSubConfigsByPropIdArea[propIdAreaId] = infoCopy;
+ return {};
+}
+
+VhalResult<void> SubscriptionManager::subscribe(const std::shared_ptr<IVehicleCallback>& callback,
+ const std::vector<SubscribeOptions>& options,
+ bool isContinuousProperty) {
std::scoped_lock<std::mutex> lockGuard(mLock);
std::vector<int64_t> intervals;
@@ -88,109 +154,108 @@
if (isContinuousProperty) {
auto intervalResult = getInterval(sampleRate);
if (!intervalResult.ok()) {
- return intervalResult.error();
+ return StatusError(StatusCode::INVALID_ARG) << intervalResult.error().message();
}
- intervals.push_back(intervalResult.value());
}
if (option.areaIds.empty()) {
ALOGE("area IDs to subscribe must not be empty");
- return Error() << "area IDs to subscribe must not be empty";
+ return StatusError(StatusCode::INVALID_ARG)
+ << "area IDs to subscribe must not be empty";
}
}
- size_t intervalIndex = 0;
ClientIdType clientId = callback->asBinder().get();
+
for (const auto& option : options) {
int32_t propId = option.propId;
const std::vector<int32_t>& areaIds = option.areaIds;
- int64_t interval = 0;
- if (isContinuousProperty) {
- interval = intervals[intervalIndex];
- intervalIndex++;
- }
for (int32_t areaId : areaIds) {
PropIdAreaId propIdAreaId = {
.propId = propId,
.areaId = areaId,
};
if (isContinuousProperty) {
- VehiclePropValue propValueRequest{
- .prop = propId,
- .areaId = areaId,
- };
- mSubscriptionsByClient[clientId][propIdAreaId] =
- std::make_unique<RecurrentSubscription>(
- mTimer,
- [this, callback, propValueRequest] {
- mGetValue(callback, propValueRequest);
- },
- interval);
- } else {
- mSubscriptionsByClient[clientId][propIdAreaId] =
- std::make_unique<OnChangeSubscription>();
+ if (auto result = updateSampleRateLocked(clientId, propIdAreaId, option.sampleRate);
+ !result.ok()) {
+ return result;
+ }
}
+
+ mSubscribedPropsByClient[clientId].insert(propIdAreaId);
mClientsByPropIdArea[propIdAreaId][clientId] = callback;
}
}
return {};
}
-Result<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdType clientId,
- const std::vector<int32_t>& propIds) {
+VhalResult<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdType clientId,
+ const std::vector<int32_t>& propIds) {
std::scoped_lock<std::mutex> lockGuard(mLock);
- if (mSubscriptionsByClient.find(clientId) == mSubscriptionsByClient.end()) {
- return Error() << "No property was subscribed for the callback";
+ if (mSubscribedPropsByClient.find(clientId) == mSubscribedPropsByClient.end()) {
+ return StatusError(StatusCode::INVALID_ARG)
+ << "No property was subscribed for the callback";
}
std::unordered_set<int32_t> subscribedPropIds;
- for (auto const& [propIdAreaId, _] : mSubscriptionsByClient[clientId]) {
+ for (auto const& propIdAreaId : mSubscribedPropsByClient[clientId]) {
subscribedPropIds.insert(propIdAreaId.propId);
}
for (int32_t propId : propIds) {
if (subscribedPropIds.find(propId) == subscribedPropIds.end()) {
- return Error() << "property ID: " << propId << " is not subscribed";
+ return StatusError(StatusCode::INVALID_ARG)
+ << "property ID: " << propId << " is not subscribed";
}
}
- auto& subscriptions = mSubscriptionsByClient[clientId];
- auto it = subscriptions.begin();
- while (it != subscriptions.end()) {
- int32_t propId = it->first.propId;
+ auto& propIdAreaIds = mSubscribedPropsByClient[clientId];
+ auto it = propIdAreaIds.begin();
+ while (it != propIdAreaIds.end()) {
+ int32_t propId = it->propId;
if (std::find(propIds.begin(), propIds.end(), propId) != propIds.end()) {
- auto& clients = mClientsByPropIdArea[it->first];
+ if (auto result = removeSampleRateLocked(clientId, *it); !result.ok()) {
+ return result;
+ }
+
+ auto& clients = mClientsByPropIdArea[*it];
clients.erase(clientId);
if (clients.empty()) {
- mClientsByPropIdArea.erase(it->first);
+ mClientsByPropIdArea.erase(*it);
+ mContSubConfigsByPropIdArea.erase(*it);
}
- it = subscriptions.erase(it);
+ it = propIdAreaIds.erase(it);
} else {
it++;
}
}
- if (subscriptions.empty()) {
- mSubscriptionsByClient.erase(clientId);
+ if (propIdAreaIds.empty()) {
+ mSubscribedPropsByClient.erase(clientId);
}
return {};
}
-Result<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdType clientId) {
+VhalResult<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdType clientId) {
std::scoped_lock<std::mutex> lockGuard(mLock);
- if (mSubscriptionsByClient.find(clientId) == mSubscriptionsByClient.end()) {
- return Error() << "No property was subscribed for this client";
+ if (mSubscribedPropsByClient.find(clientId) == mSubscribedPropsByClient.end()) {
+ return StatusError(StatusCode::INVALID_ARG) << "No property was subscribed for this client";
}
- auto& subscriptions = mSubscriptionsByClient[clientId];
- for (auto const& [propIdAreaId, _] : subscriptions) {
+ auto& subscriptions = mSubscribedPropsByClient[clientId];
+ for (auto const& propIdAreaId : subscriptions) {
+ if (auto result = removeSampleRateLocked(clientId, propIdAreaId); !result.ok()) {
+ return result;
+ }
+
auto& clients = mClientsByPropIdArea[propIdAreaId];
clients.erase(clientId);
if (clients.empty()) {
mClientsByPropIdArea.erase(propIdAreaId);
+ mContSubConfigsByPropIdArea.erase(propIdAreaId);
}
}
- mSubscriptionsByClient.erase(clientId);
+ mSubscribedPropsByClient.erase(clientId);
return {};
}
@@ -208,10 +273,8 @@
if (mClientsByPropIdArea.find(propIdAreaId) == mClientsByPropIdArea.end()) {
continue;
}
- for (const auto& [clientId, client] : mClientsByPropIdArea[propIdAreaId]) {
- if (!mSubscriptionsByClient[clientId][propIdAreaId]->isOnChange()) {
- continue;
- }
+
+ for (const auto& [_, client] : mClientsByPropIdArea[propIdAreaId]) {
clients[client].push_back(&value);
}
}
@@ -220,25 +283,7 @@
bool SubscriptionManager::isEmpty() {
std::scoped_lock<std::mutex> lockGuard(mLock);
- return mSubscriptionsByClient.empty() && mClientsByPropIdArea.empty();
-}
-
-SubscriptionManager::RecurrentSubscription::RecurrentSubscription(
- std::shared_ptr<RecurrentTimer> timer, std::function<void()>&& action, int64_t interval)
- : mAction(std::make_shared<std::function<void()>>(action)), mTimer(timer) {
- mTimer->registerTimerCallback(interval, mAction);
-}
-
-SubscriptionManager::RecurrentSubscription::~RecurrentSubscription() {
- mTimer->unregisterTimerCallback(mAction);
-}
-
-bool SubscriptionManager::RecurrentSubscription::isOnChange() {
- return false;
-}
-
-bool SubscriptionManager::OnChangeSubscription::isOnChange() {
- return true;
+ return mSubscribedPropsByClient.empty() && mClientsByPropIdArea.empty();
}
} // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
index 49f5b7e..f48b906 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
@@ -1308,25 +1308,7 @@
TEST_F(DefaultVehicleHalTest, testSubscribeGlobalContinuous) {
VehiclePropValue testValue{
.prop = GLOBAL_CONTINUOUS_PROP,
- .value.int32Values = {0},
};
- // Set responses for all the hardware getValues requests.
- getHardware()->setGetValueResponder(
- [](std::shared_ptr<const IVehicleHardware::GetValuesCallback> callback,
- const std::vector<GetValueRequest>& requests) {
- std::vector<GetValueResult> results;
- for (auto& request : requests) {
- VehiclePropValue prop = request.prop;
- prop.value.int32Values = {0};
- results.push_back({
- .requestId = request.requestId,
- .status = StatusCode::OK,
- .prop = prop,
- });
- }
- (*callback)(results);
- return StatusCode::OK;
- });
std::vector<SubscribeOptions> options = {
{
@@ -1353,28 +1335,6 @@
}
TEST_F(DefaultVehicleHalTest, testSubscribeGlobalContinuousRateOutOfRange) {
- VehiclePropValue testValue{
- .prop = GLOBAL_CONTINUOUS_PROP,
- .value.int32Values = {0},
- };
- // Set responses for all the hardware getValues requests.
- getHardware()->setGetValueResponder(
- [](std::shared_ptr<const IVehicleHardware::GetValuesCallback> callback,
- const std::vector<GetValueRequest>& requests) {
- std::vector<GetValueResult> results;
- for (auto& request : requests) {
- VehiclePropValue prop = request.prop;
- prop.value.int32Values = {0};
- results.push_back({
- .requestId = request.requestId,
- .status = StatusCode::OK,
- .prop = prop,
- });
- }
- (*callback)(results);
- return StatusCode::OK;
- });
-
// The maxSampleRate is 100, so the sample rate should be the default max 100.
std::vector<SubscribeOptions> options = {
{
@@ -1398,24 +1358,6 @@
}
TEST_F(DefaultVehicleHalTest, testSubscribeAreaContinuous) {
- // Set responses for all the hardware getValues requests.
- getHardware()->setGetValueResponder(
- [](std::shared_ptr<const IVehicleHardware::GetValuesCallback> callback,
- const std::vector<GetValueRequest>& requests) {
- std::vector<GetValueResult> results;
- for (auto& request : requests) {
- VehiclePropValue prop = request.prop;
- prop.value.int32Values = {0};
- results.push_back({
- .requestId = request.requestId,
- .status = StatusCode::OK,
- .prop = prop,
- });
- }
- (*callback)(results);
- return StatusCode::OK;
- });
-
std::vector<SubscribeOptions> options = {
{
.propId = AREA_CONTINUOUS_PROP,
@@ -1436,6 +1378,8 @@
// Sleep for 1s, which should generate ~20 events.
std::this_thread::sleep_for(std::chrono::seconds(1));
+ getClient()->unsubscribe(getCallbackClient(), std::vector<int32_t>({AREA_CONTINUOUS_PROP}));
+
std::vector<VehiclePropValue> events;
while (true) {
auto maybeResults = getCallback()->nextOnPropertyEventResults();
@@ -1509,28 +1453,6 @@
}
TEST_F(DefaultVehicleHalTest, testUnsubscribeContinuous) {
- VehiclePropValue testValue{
- .prop = GLOBAL_CONTINUOUS_PROP,
- .value.int32Values = {0},
- };
- // Set responses for all the hardware getValues requests.
- getHardware()->setGetValueResponder(
- [](std::shared_ptr<const IVehicleHardware::GetValuesCallback> callback,
- const std::vector<GetValueRequest>& requests) {
- std::vector<GetValueResult> results;
- for (auto& request : requests) {
- VehiclePropValue prop = request.prop;
- prop.value.int32Values = {0};
- results.push_back({
- .requestId = request.requestId,
- .status = StatusCode::OK,
- .prop = prop,
- });
- }
- (*callback)(results);
- return StatusCode::OK;
- });
-
std::vector<SubscribeOptions> options = {
{
.propId = GLOBAL_CONTINUOUS_PROP,
@@ -1621,12 +1543,6 @@
}
TEST_F(DefaultVehicleHalTest, testOnBinderDiedUnlinked) {
- // First subscribe to a continuous property so that we register a death recipient for our
- // client.
- VehiclePropValue testValue{
- .prop = GLOBAL_CONTINUOUS_PROP,
- .value.int32Values = {0},
- };
// Set responses for all the hardware getValues requests.
getHardware()->setGetValueResponder(
[](std::shared_ptr<const IVehicleHardware::GetValuesCallback> callback,
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
index 66aef7c..4df4e1a 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
@@ -32,9 +32,14 @@
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+MockVehicleHardware::MockVehicleHardware() {
+ mRecurrentTimer = std::make_unique<RecurrentTimer>();
+}
+
MockVehicleHardware::~MockVehicleHardware() {
std::unique_lock<std::mutex> lk(mLock);
mCv.wait(lk, [this] { return mThreadCount == 0; });
+ mRecurrentTimer.reset();
}
std::vector<VehiclePropConfig> MockVehicleHardware::getAllPropertyConfigs() const {
@@ -83,6 +88,42 @@
return StatusCode::OK;
}
+StatusCode MockVehicleHardware::updateSampleRate(int32_t propId, int32_t areaId, float sampleRate) {
+ std::shared_ptr<std::function<void()>> action;
+
+ {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ if (mRecurrentActions[propId][areaId] != nullptr) {
+ // Remove the previous action register for this [propId, areaId].
+ mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propId][areaId]);
+ }
+ if (sampleRate == 0) {
+ return StatusCode::OK;
+ }
+
+ // We are sure 'propertyChangeCallback' would be alive because we would unregister timer
+ // before destroying 'this' which owns mPropertyChangeCallback.
+ const PropertyChangeCallback* propertyChangeCallback = mPropertyChangeCallback.get();
+ action = std::make_shared<std::function<void()>>([propertyChangeCallback, propId, areaId] {
+ std::vector<VehiclePropValue> values = {
+ {
+ .prop = propId,
+ .areaId = areaId,
+ },
+ };
+ (*propertyChangeCallback)(values);
+ });
+ // Store the action in a map so that we could remove the action later.
+ mRecurrentActions[propId][areaId] = action;
+ }
+
+ // In mock implementation, we generate a new property change event for this property at sample
+ // rate.
+ int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRate);
+ mRecurrentTimer->registerTimerCallback(interval, action);
+ return StatusCode::OK;
+}
+
void MockVehicleHardware::registerOnPropertyChangeEvent(
std::unique_ptr<const PropertyChangeCallback> callback) {
std::scoped_lock<std::mutex> lockGuard(mLock);
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
index cb8b6a0..743841c 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
@@ -18,6 +18,7 @@
#define android_hardware_automotive_vehicle_aidl_impl_vhal_test_MockVehicleHardware_H_
#include <IVehicleHardware.h>
+#include <RecurrentTimer.h>
#include <VehicleHalTypes.h>
#include <android-base/thread_annotations.h>
@@ -38,6 +39,8 @@
class MockVehicleHardware final : public IVehicleHardware {
public:
+ MockVehicleHardware();
+
~MockVehicleHardware();
std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig>
@@ -55,6 +58,8 @@
void registerOnPropertyChangeEvent(
std::unique_ptr<const PropertyChangeCallback> callback) override;
void registerOnPropertySetErrorEvent(std::unique_ptr<const PropertySetErrorCallback>) override;
+ aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
+ int32_t propId, int32_t areaId, float sampleRate) override;
// Test functions.
void setPropertyConfigs(
@@ -117,6 +122,11 @@
std::list<std::vector<ResultType>>* storedResponses) const REQUIRES(mLock);
DumpResult mDumpResult;
+
+ // RecurrentTimer is thread-safe.
+ std::shared_ptr<RecurrentTimer> mRecurrentTimer;
+ std::unordered_map<int32_t, std::unordered_map<int32_t, std::shared_ptr<std::function<void()>>>>
+ mRecurrentActions GUARDED_BY(mLock);
};
} // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
index 2a468f6..3f59363 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
@@ -16,6 +16,7 @@
#include "SubscriptionManager.h"
+#include <MockVehicleHardware.h>
#include <VehicleHalTypes.h>
#include <aidl/android/hardware/automotive/vehicle/BnVehicleCallback.h>
@@ -86,19 +87,21 @@
class SubscriptionManagerTest : public testing::Test {
public:
void SetUp() override {
- mManager = std::make_unique<SubscriptionManager>(
- [](const std::shared_ptr<IVehicleCallback>& callback,
- const VehiclePropValue& value) {
- callback->onPropertyEvent(
- VehiclePropValues{
- .payloads = {value},
- },
- 0);
- });
+ mHardware = std::make_shared<MockVehicleHardware>();
+ mManager = std::make_unique<SubscriptionManager>(mHardware.get());
mCallback = ndk::SharedRefBase::make<PropertyCallback>();
// Keep the local binder alive.
mBinder = mCallback->asBinder();
mCallbackClient = IVehicleCallback::fromBinder(mBinder);
+ std::shared_ptr<IVehicleCallback> callbackClient = mCallbackClient;
+ mHardware->registerOnPropertyChangeEvent(
+ std::make_unique<IVehicleHardware::PropertyChangeCallback>(
+ [callbackClient](std::vector<VehiclePropValue> updatedValues) {
+ VehiclePropValues values = {
+ .payloads = std::move(updatedValues),
+ };
+ callbackClient->onPropertyEvent(values, 0);
+ }));
}
SubscriptionManager* getManager() { return mManager.get(); }
@@ -115,6 +118,7 @@
std::unique_ptr<SubscriptionManager> mManager;
std::shared_ptr<PropertyCallback> mCallback;
std::shared_ptr<IVehicleCallback> mCallbackClient;
+ std::shared_ptr<MockVehicleHardware> mHardware;
SpAIBinder mBinder;
};
diff --git a/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp b/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp
index c33f3e9..c431d85 100644
--- a/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp
+++ b/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp
@@ -30,6 +30,7 @@
#include <hidl/ServiceManagement.h>
#include <inttypes.h>
#include <utils/Log.h>
+#include <utils/SystemClock.h>
#include <chrono>
#include <mutex>
@@ -67,6 +68,7 @@
private:
std::mutex mLock;
std::unordered_map<int32_t, size_t> mEventsCount GUARDED_BY(mLock);
+ std::unordered_map<int32_t, std::vector<int64_t>> mEventTimestamps GUARDED_BY(mLock);
std::condition_variable mEventCond;
public:
@@ -74,7 +76,9 @@
{
std::lock_guard<std::mutex> lockGuard(mLock);
for (auto& value : values) {
- mEventsCount[value->getPropId()] += 1;
+ int32_t propId = value->getPropId();
+ mEventsCount[propId] += 1;
+ mEventTimestamps[propId].push_back(value->getTimestamp());
}
}
mEventCond.notify_one();
@@ -94,6 +98,13 @@
});
}
+ std::vector<int64_t> getEventTimestamps(int32_t propId) {
+ {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ return mEventTimestamps[propId];
+ }
+ }
+
void reset() {
std::lock_guard<std::mutex> lockGuard(mLock);
mEventsCount.clear();
@@ -285,19 +296,59 @@
int32_t propId = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
- std::vector<SubscribeOptions> options = {
- SubscribeOptions{.propId = propId, .sampleRate = 10.0}};
+ auto propConfigsResult = mVhalClient->getPropConfigs({propId});
+
+ ASSERT_TRUE(propConfigsResult.ok()) << "Failed to get property config for PERF_VEHICLE_SPEED: "
+ << "error: " << propConfigsResult.error().message();
+ ASSERT_EQ(propConfigsResult.value().size(), 1u)
+ << "Expect to return 1 config for PERF_VEHICLE_SPEED";
+ auto& propConfig = propConfigsResult.value()[0];
+ float minSampleRate = propConfig->getMinSampleRate();
+ float maxSampleRate = propConfig->getMaxSampleRate();
+
+ if (minSampleRate < 1) {
+ GTEST_SKIP() << "Sample rate for vehicle speed < 1 times/sec, skip test since it would "
+ "take too long";
+ }
auto client = mVhalClient->getSubscriptionClient(mCallback);
ASSERT_NE(client, nullptr) << "Failed to get subscription client";
- auto result = client->subscribe(options);
+ auto result = client->subscribe({{.propId = propId, .sampleRate = minSampleRate}});
ASSERT_TRUE(result.ok()) << StringPrintf("Failed to subscribe to property: %" PRId32
", error: %s",
propId, result.error().message().c_str());
- ASSERT_TRUE(mCallback->waitForExpectedEvents(propId, 10, std::chrono::seconds(10)))
- << "Didn't get enough events for subscription";
+
+ if (mVhalClient->isAidlVhal()) {
+ // Skip checking timestamp for HIDL because the behavior for sample rate and timestamp is
+ // only specified clearly for AIDL.
+
+ // Timeout is 2 seconds, which gives a 1 second buffer.
+ ASSERT_TRUE(mCallback->waitForExpectedEvents(propId, std::floor(minSampleRate),
+ std::chrono::seconds(2)))
+ << "Didn't get enough events for subscribing to minSampleRate";
+ }
+
+ result = client->subscribe({{.propId = propId, .sampleRate = maxSampleRate}});
+
+ ASSERT_TRUE(result.ok()) << StringPrintf("Failed to subscribe to property: %" PRId32
+ ", error: %s",
+ propId, result.error().message().c_str());
+
+ if (mVhalClient->isAidlVhal()) {
+ ASSERT_TRUE(mCallback->waitForExpectedEvents(propId, std::floor(maxSampleRate),
+ std::chrono::seconds(2)))
+ << "Didn't get enough events for subscribing to maxSampleRate";
+
+ std::unordered_set<int64_t> timestamps;
+ // Event event should have a different timestamp.
+ for (const int64_t& eventTimestamp : mCallback->getEventTimestamps(propId)) {
+ ASSERT_TRUE(timestamps.find(eventTimestamp) == timestamps.end())
+ << "two events for the same property must not have the same timestamp";
+ timestamps.insert(eventTimestamp);
+ }
+ }
result = client->unsubscribe({propId});
ASSERT_TRUE(result.ok()) << StringPrintf("Failed to unsubscribe to property: %" PRId32
@@ -325,6 +376,49 @@
kInvalidProp);
}
+// Test the timestamp returned in GetValues results is the timestamp when the value is retrieved.
+TEST_P(VtsHalAutomotiveVehicleTargetTest, testGetValuesTimestampAIDL) {
+ if (!mVhalClient->isAidlVhal()) {
+ GTEST_SKIP() << "Skip checking timestamp for HIDL because the behavior is only specified "
+ "for AIDL";
+ }
+
+ int32_t propId = toInt(VehicleProperty::PARKING_BRAKE_ON);
+ auto prop = mVhalClient->createHalPropValue(propId);
+
+ auto result = mVhalClient->getValueSync(*prop);
+
+ ASSERT_TRUE(result.ok()) << StringPrintf("Failed to get value for property: %" PRId32
+ ", error: %s",
+ propId, result.error().message().c_str());
+ ASSERT_NE(result.value(), nullptr) << "Result value must not be null";
+ ASSERT_EQ(result.value()->getInt32Values().size(), 1u) << "Result must contain 1 int value";
+
+ bool parkBrakeOnValue1 = (result.value()->getInt32Values()[0] == 1);
+ int64_t timestampValue1 = result.value()->getTimestamp();
+
+ result = mVhalClient->getValueSync(*prop);
+
+ ASSERT_TRUE(result.ok()) << StringPrintf("Failed to get value for property: %" PRId32
+ ", error: %s",
+ propId, result.error().message().c_str());
+ ASSERT_NE(result.value(), nullptr) << "Result value must not be null";
+ ASSERT_EQ(result.value()->getInt32Values().size(), 1u) << "Result must contain 1 int value";
+
+ bool parkBarkeOnValue2 = (result.value()->getInt32Values()[0] == 1);
+ int64_t timestampValue2 = result.value()->getTimestamp();
+
+ if (parkBarkeOnValue2 == parkBrakeOnValue1) {
+ ASSERT_EQ(timestampValue2, timestampValue1)
+ << "getValue result must contain a timestamp updated when the value was updated, if"
+ "the value does not change, expect the same timestamp";
+ } else {
+ ASSERT_GT(timestampValue2, timestampValue1)
+ << "getValue result must contain a timestamp updated when the value was updated, if"
+ "the value changes, expect the newer value has a larger timestamp";
+ }
+}
+
std::vector<ServiceDescriptor> getDescriptors() {
std::vector<ServiceDescriptor> descriptors;
for (std::string name : getAidlHalInstanceNames(IVehicle::descriptor)) {
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 430bf3c..e6628f2 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -16,6 +16,7 @@
local_include_dirs: ["include"],
srcs: [
"CancellationSignal.cpp",
+ "FakeFingerprintEngine.cpp",
"Fingerprint.cpp",
"Session.cpp",
"WorkerThread.cpp",
@@ -27,6 +28,7 @@
"android.hardware.biometrics.fingerprint-V2-ndk",
"android.hardware.biometrics.common-V2-ndk",
],
+ static_libs: ["android.hardware.biometrics.fingerprint.VirtualProps"],
}
cc_test_host {
@@ -41,3 +43,33 @@
],
test_suites: ["general-tests"],
}
+
+cc_test {
+ name: "android.hardware.biometrics.fingerprint.FakeFingerprintEngineTest",
+ local_include_dirs: ["include"],
+ srcs: [
+ "CancellationSignal.cpp",
+ "tests/FakeFingerprintEngineTest.cpp",
+ "FakeFingerprintEngine.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.biometrics.fingerprint.VirtualProps",
+ "android.hardware.biometrics.fingerprint-V2-ndk",
+ "android.hardware.biometrics.common-V2-ndk",
+ "android.hardware.keymaster-V3-ndk",
+ ],
+ vendor: true,
+ test_suites: ["general-tests"],
+ require_root: true,
+}
+
+sysprop_library {
+ name: "android.hardware.biometrics.fingerprint.VirtualProps",
+ srcs: ["fingerprint.sysprop"],
+ property_owner: "Vendor",
+ vendor: true,
+}
diff --git a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
new file mode 100644
index 0000000..d1fe183
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 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 "FakeFingerprintEngine.h"
+
+#include <fingerprint.sysprop.h>
+#include "CancellationSignal.h"
+
+#include <android-base/logging.h>
+#include <chrono>
+#include <regex>
+#include <thread>
+
+#define SLEEP_MS(x) \
+ if (x > 0) std::this_thread::sleep_for(std::chrono::milliseconds(x))
+#define BEGIN_OP(x) \
+ do { \
+ LOG(INFO) << __func__; \
+ SLEEP_MS(x); \
+ } while (0)
+#define IS_TRUE(x) ((x == "1") || (x == "true"))
+
+// This is for non-test situations, such as casual cuttlefish users, that don't
+// set an explicit value.
+// Some operations (i.e. enroll, authenticate) will be executed in tight loops
+// by parts of the UI or fail if there is no latency. For example, the
+// fingerprint settings page constantly runs auth and the enrollment UI uses a
+// cancel/restart cycle that requires some latency while the activities change.
+#define DEFAULT_LATENCY 2000
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+
+int64_t getSystemNanoTime() {
+ timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_sec * 1000000000LL + now.tv_nsec;
+}
+
+bool hasElapsed(int64_t start, int64_t durationMillis) {
+ auto now = getSystemNanoTime();
+ if (now < start) return true;
+ if (durationMillis <= 0) return true;
+ return ((now - start) / 1000000LL) > durationMillis;
+}
+
+std::vector<std::string> split(const std::string& str, const std::string& sep) {
+ std::regex regex(sep);
+ std::vector<std::string> parts(std::sregex_token_iterator(str.begin(), str.end(), regex, -1),
+ std::sregex_token_iterator());
+ return parts;
+}
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+void FakeFingerprintEngine::generateChallengeImpl(ISessionCallback* cb) {
+ BEGIN_OP(0);
+ std::uniform_int_distribution<int64_t> dist;
+ auto challenge = dist(mRandom);
+ FingerprintHalProperties::challenge(challenge);
+ cb->onChallengeGenerated(challenge);
+}
+
+void FakeFingerprintEngine::revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
+ BEGIN_OP(0);
+ FingerprintHalProperties::challenge({});
+ cb->onChallengeRevoked(challenge);
+}
+
+void FakeFingerprintEngine::enrollImpl(ISessionCallback* cb,
+ const keymaster::HardwareAuthToken& hat,
+ const std::future<void>& cancel) {
+ BEGIN_OP(FingerprintHalProperties::operation_enroll_latency().value_or(DEFAULT_LATENCY));
+
+ // Do proper HAT verification in the real implementation.
+ if (hat.mac.empty()) {
+ LOG(ERROR) << "Fail: hat";
+ cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+ return;
+ }
+
+ if (FingerprintHalProperties::operation_enroll_fails().value_or(false)) {
+ LOG(ERROR) << "Fail: operation_enroll_fails";
+ cb->onError(Error::VENDOR, 0 /* vendorError */);
+ return;
+ }
+
+ // format is "<id>:<progress_ms>,<progress_ms>,...:<result>
+ auto nextEnroll = FingerprintHalProperties::next_enrollment().value_or("");
+ auto parts = split(nextEnroll, ":");
+ if (parts.size() != 3) {
+ LOG(ERROR) << "Fail: invalid next_enrollment";
+ cb->onError(Error::VENDOR, 0 /* vendorError */);
+ return;
+ }
+ auto enrollmentId = std::stoi(parts[0]);
+ auto progress = split(parts[1], ",");
+ for (size_t i = 0; i < progress.size(); i++) {
+ auto left = progress.size() - i - 1;
+ SLEEP_MS(std::stoi(progress[i]));
+
+ if (shouldCancel(cancel)) {
+ LOG(ERROR) << "Fail: cancel";
+ cb->onError(Error::CANCELED, 0 /* vendorCode */);
+ return;
+ }
+
+ cb->onAcquired(AcquiredInfo::GOOD, 0 /* vendorCode */);
+ if (left == 0 && !IS_TRUE(parts[2])) { // end and failed
+ LOG(ERROR) << "Fail: requested by caller: " << nextEnroll;
+ FingerprintHalProperties::next_enrollment({});
+ cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorCode */);
+ } else { // progress and update props if last time
+ if (left == 0) {
+ auto enrollments = FingerprintHalProperties::enrollments();
+ enrollments.emplace_back(enrollmentId);
+ FingerprintHalProperties::enrollments(enrollments);
+ FingerprintHalProperties::next_enrollment({});
+ LOG(INFO) << "Enrolled: " << enrollmentId;
+ }
+ cb->onEnrollmentProgress(enrollmentId, left);
+ }
+ }
+}
+
+void FakeFingerprintEngine::authenticateImpl(ISessionCallback* cb, int64_t /* operationId */,
+ const std::future<void>& cancel) {
+ BEGIN_OP(FingerprintHalProperties::operation_authenticate_latency().value_or(DEFAULT_LATENCY));
+
+ auto now = getSystemNanoTime();
+ int64_t duration = FingerprintHalProperties::operation_authenticate_duration().value_or(0);
+ do {
+ if (FingerprintHalProperties::operation_authenticate_fails().value_or(false)) {
+ LOG(ERROR) << "Fail: operation_authenticate_fails";
+ cb->onError(Error::VENDOR, 0 /* vendorError */);
+ return;
+ }
+
+ if (FingerprintHalProperties::lockout().value_or(false)) {
+ LOG(ERROR) << "Fail: lockout";
+ cb->onLockoutPermanent();
+ cb->onError(Error::HW_UNAVAILABLE, 0 /* vendorError */);
+ return;
+ }
+
+ if (shouldCancel(cancel)) {
+ LOG(ERROR) << "Fail: cancel";
+ cb->onError(Error::CANCELED, 0 /* vendorCode */);
+ return;
+ }
+
+ auto id = FingerprintHalProperties::enrollment_hit().value_or(0);
+ auto enrolls = FingerprintHalProperties::enrollments();
+ auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+ if (id > 0 && isEnrolled) {
+ cb->onAuthenticationSucceeded(id, {} /* hat */);
+ return;
+ }
+
+ SLEEP_MS(100);
+ } while (!hasElapsed(now, duration));
+
+ LOG(ERROR) << "Fail: not enrolled";
+ cb->onAuthenticationFailed();
+ cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+}
+
+void FakeFingerprintEngine::detectInteractionImpl(ISessionCallback* cb,
+ const std::future<void>& cancel) {
+ BEGIN_OP(FingerprintHalProperties::operation_detect_interaction_latency().value_or(
+ DEFAULT_LATENCY));
+
+ if (FingerprintHalProperties::operation_detect_interaction_fails().value_or(false)) {
+ LOG(ERROR) << "Fail: operation_detect_interaction_fails";
+ cb->onError(Error::VENDOR, 0 /* vendorError */);
+ return;
+ }
+
+ if (shouldCancel(cancel)) {
+ LOG(ERROR) << "Fail: cancel";
+ cb->onError(Error::CANCELED, 0 /* vendorCode */);
+ return;
+ }
+
+ auto id = FingerprintHalProperties::enrollment_hit().value_or(0);
+ auto enrolls = FingerprintHalProperties::enrollments();
+ auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end();
+ if (id <= 0 || !isEnrolled) {
+ LOG(ERROR) << "Fail: not enrolled";
+ cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
+ return;
+ }
+
+ cb->onInteractionDetected();
+}
+
+void FakeFingerprintEngine::enumerateEnrollmentsImpl(ISessionCallback* cb) {
+ BEGIN_OP(0);
+
+ std::vector<int32_t> ids;
+ for (auto& enrollment : FingerprintHalProperties::enrollments()) {
+ auto id = enrollment.value_or(0);
+ if (id > 0) {
+ ids.push_back(id);
+ }
+ }
+
+ cb->onEnrollmentsEnumerated(ids);
+}
+
+void FakeFingerprintEngine::removeEnrollmentsImpl(ISessionCallback* cb,
+ const std::vector<int32_t>& enrollmentIds) {
+ BEGIN_OP(0);
+
+ std::vector<std::optional<int32_t>> newEnrollments;
+ std::vector<int32_t> removed;
+ for (auto& enrollment : FingerprintHalProperties::enrollments()) {
+ auto id = enrollment.value_or(0);
+ if (std::find(enrollmentIds.begin(), enrollmentIds.end(), id) != enrollmentIds.end()) {
+ removed.push_back(id);
+ } else if (id > 0) {
+ newEnrollments.emplace_back(id);
+ }
+ }
+ FingerprintHalProperties::enrollments(newEnrollments);
+
+ cb->onEnrollmentsRemoved(enrollmentIds);
+}
+
+void FakeFingerprintEngine::getAuthenticatorIdImpl(ISessionCallback* cb) {
+ BEGIN_OP(0);
+ cb->onAuthenticatorIdRetrieved(FingerprintHalProperties::authenticator_id().value_or(0));
+}
+
+void FakeFingerprintEngine::invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
+ BEGIN_OP(0);
+ auto id = FingerprintHalProperties::authenticator_id().value_or(0);
+ auto newId = id + 1;
+ FingerprintHalProperties::authenticator_id(newId);
+ cb->onAuthenticatorIdInvalidated(newId);
+}
+
+void FakeFingerprintEngine::resetLockoutImpl(ISessionCallback* cb,
+ const keymaster::HardwareAuthToken& /*hat*/) {
+ BEGIN_OP(0);
+ FingerprintHalProperties::lockout(false);
+ cb->onLockoutCleared();
+}
+
+} // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
index 1f14de6..71dc660 100644
--- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp
+++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp
@@ -16,15 +16,19 @@
#include "Fingerprint.h"
+#include <fingerprint.sysprop.h>
#include "Session.h"
+#include <android-base/logging.h>
+
+using namespace ::android::fingerprint::virt;
+
namespace aidl::android::hardware::biometrics::fingerprint {
namespace {
constexpr size_t MAX_WORKER_QUEUE_SIZE = 5;
constexpr int SENSOR_ID = 1;
constexpr common::SensorStrength SENSOR_STRENGTH = common::SensorStrength::STRONG;
constexpr int MAX_ENROLLMENTS_PER_USER = 5;
-constexpr FingerprintSensorType SENSOR_TYPE = FingerprintSensorType::REAR;
constexpr bool SUPPORTS_NAVIGATION_GESTURES = true;
constexpr char HW_COMPONENT_ID[] = "fingerprintSensor";
constexpr char HW_VERSION[] = "vendor/model/revision";
@@ -51,8 +55,18 @@
0 /* sensorLocationY */, 0 /* sensorRadius */,
"" /* display */};
+ FingerprintSensorType sensorType = FingerprintSensorType::UNKNOWN;
+ std::string sensorTypeProp = FingerprintHalProperties::type().value_or("");
+ if (sensorTypeProp == "" || sensorTypeProp == "default" || sensorTypeProp == "rear") {
+ sensorType = FingerprintSensorType::REAR;
+ }
+ if (sensorType == FingerprintSensorType::UNKNOWN) {
+ UNIMPLEMENTED(FATAL) << "unrecognized or unimplemented fingerprint behavior: "
+ << sensorTypeProp;
+ }
+
*out = {{commonProps,
- SENSOR_TYPE,
+ sensorType,
{sensorLocation},
SUPPORTS_NAVIGATION_GESTURES,
false /* supportsDetectInteraction */}};
diff --git a/biometrics/fingerprint/aidl/default/README.md b/biometrics/fingerprint/aidl/default/README.md
new file mode 100644
index 0000000..046602f
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/README.md
@@ -0,0 +1,74 @@
+# Virtual Fingerprint HAL
+
+This is a virtual HAL implementation that is backed by system properties
+instead of actual hardware. It's intended for testing and UI development
+on debuggable builds to allow devices to masquerade as alternative device
+types and for emulators.
+
+## Getting Started
+
+First, set the type of sensor the device should use, enable the virtual
+extensions in the framework, and reboot.
+
+This doesn't work with HIDL and you typically need to have a PIN or password
+set for things to work correctly, so this is a good time to set those too.
+
+```shell
+$ adb root
+$ adb shell settings put secure biometric_virtual_enabled 1
+$ adb shell setprop persist.vendor.fingerprint.virtual.type rear
+$ adb shell locksettings set-pin 0000
+$ adb shell settings put secure com.android.server.biometrics.AuthService.hidlDisabled 1
+$ adb reboot
+```
+
+### Enrollments
+
+Next, setup enrollments on the device. This can either be done through
+the UI, or via adb.
+
+#### UI Enrollment
+
+ 1. Tee up the results of the enrollment before starting the process:
+
+ ```shell
+ $ adb shell setprop vendor.fingerprint.virtual.next_enrollment 1:100,100,100:true
+ ```
+ 2. Navigate to `Settings -> Security -> Fingerprint Unlock` and follow the prompts.
+ 3. Verify the enrollments in the UI:
+
+ ```shell
+ $ adb shell getprop persist.vendor.fingerprint.virtual.enrollments
+ ```
+
+#### Direct Enrollment
+
+To set enrollment directly without the UI:
+
+```shell
+$ adb root
+$ adb shell setprop persist.vendor.fingerprint.virtual.enrollments 1
+$ adb shell cmd fingerprint sync
+```
+
+Note: You may need to do this twice. The templates are checked
+as part of some lazy operations, like user switching and startup, which can
+cause the framework to delete the enrollments before the sync operation runs.
+Until this is fixed, just run the commands twice as a workaround.
+
+### Authenticate
+
+To authenticate successfully set the enrolled id that should succeed. Unset it
+or change the value to make authenticate operations fail:
+
+````shell
+$ adb shell setprop vendor.fingerprint.virtual.enrollment_hit 1
+````
+
+### View HAL State
+
+To view all the properties of the HAL (see `fingerprint.sysprop` for the API):
+
+```shell
+$ adb shell getprop | grep vendor.fingerprint.virtual
+```
diff --git a/biometrics/fingerprint/aidl/default/Session.cpp b/biometrics/fingerprint/aidl/default/Session.cpp
index 452ed12..078d3d9 100644
--- a/biometrics/fingerprint/aidl/default/Session.cpp
+++ b/biometrics/fingerprint/aidl/default/Session.cpp
@@ -101,7 +101,7 @@
if (shouldCancel(cancFuture)) {
mCb->onError(Error::CANCELED, 0 /* vendorCode */);
} else {
- mEngine->enrollImpl(mCb.get(), hat);
+ mEngine->enrollImpl(mCb.get(), hat, cancFuture);
}
enterIdling();
}));
@@ -123,7 +123,7 @@
if (shouldCancel(cancFuture)) {
mCb->onError(Error::CANCELED, 0 /* vendorCode */);
} else {
- mEngine->authenticateImpl(mCb.get(), operationId);
+ mEngine->authenticateImpl(mCb.get(), operationId, cancFuture);
}
enterIdling();
}));
@@ -144,7 +144,7 @@
if (shouldCancel(cancFuture)) {
mCb->onError(Error::CANCELED, 0 /* vendorCode */);
} else {
- mEngine->detectInteractionImpl(mCb.get());
+ mEngine->detectInteractionImpl(mCb.get(), cancFuture);
}
enterIdling();
}));
diff --git a/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt b/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt
new file mode 100644
index 0000000..4724ff4
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/api/android.hardware.biometrics.fingerprint.VirtualProps-current.txt
@@ -0,0 +1,85 @@
+props {
+ owner: Vendor
+ module: "android.fingerprint.virt.FingerprintHalProperties"
+ prop {
+ api_name: "authenticator_id"
+ type: Long
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.authenticator_id"
+ }
+ prop {
+ api_name: "challenge"
+ type: Long
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.challenge"
+ }
+ prop {
+ api_name: "enrollment_hit"
+ type: Integer
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.enrollment_hit"
+ }
+ prop {
+ api_name: "enrollments"
+ type: IntegerList
+ access: ReadWrite
+ prop_name: "persist.vendor.fingerprint.virtual.enrollments"
+ }
+ prop {
+ api_name: "lockout"
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.lockout"
+ }
+ prop {
+ api_name: "next_enrollment"
+ type: String
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.next_enrollment"
+ }
+ prop {
+ api_name: "operation_authenticate_duration"
+ type: Integer
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
+ }
+ prop {
+ api_name: "operation_authenticate_fails"
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_authenticate_fails"
+ }
+ prop {
+ api_name: "operation_authenticate_latency"
+ type: Integer
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
+ }
+ prop {
+ api_name: "operation_detect_interaction_fails"
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_fails"
+ }
+ prop {
+ api_name: "operation_detect_interaction_latency"
+ type: Integer
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
+ }
+ prop {
+ api_name: "operation_enroll_fails"
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_enroll_fails"
+ }
+ prop {
+ api_name: "operation_enroll_latency"
+ type: Integer
+ access: ReadWrite
+ prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
+ }
+ prop {
+ api_name: "type"
+ type: String
+ access: ReadWrite
+ prop_name: "persist.vendor.fingerprint.virtual.type"
+ enum_values: "default|rear|udfps|side"
+ }
+}
diff --git a/biometrics/fingerprint/aidl/default/fingerprint.sysprop b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
new file mode 100644
index 0000000..12c8648
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/fingerprint.sysprop
@@ -0,0 +1,135 @@
+# fingerprint.sysprop
+# module becomes static class (Java) / namespace (C++) for serving API
+module: "android.fingerprint.virt.FingerprintHalProperties"
+owner: Vendor
+
+# type of fingerprint sensor
+prop {
+ prop_name: "persist.vendor.fingerprint.virtual.type"
+ type: String
+ scope: Public
+ access: ReadWrite
+ enum_values: "default|rear|udfps|side"
+ api_name: "type"
+}
+
+# ids of call current enrollments
+prop {
+ prop_name: "persist.vendor.fingerprint.virtual.enrollments"
+ type: IntegerList
+ scope: Public
+ access: ReadWrite
+ api_name: "enrollments"
+}
+
+# authenticate and detectInteraction will succeed with this
+# enrollment id, when present, otherwise they will error
+prop {
+ prop_name: "vendor.fingerprint.virtual.enrollment_hit"
+ type: Integer
+ scope: Public
+ access: ReadWrite
+ api_name: "enrollment_hit"
+}
+
+# the next enrollment in the format: "<id>:<delay>,<delay>,...:<result>"
+# for example: "2:0:true"
+# this property is reset after enroll completes
+prop {
+ prop_name: "vendor.fingerprint.virtual.next_enrollment"
+ type: String
+ scope: Public
+ access: ReadWrite
+ api_name: "next_enrollment"
+}
+
+# value for getAuthenticatorId or 0
+prop {
+ prop_name: "vendor.fingerprint.virtual.authenticator_id"
+ type: Long
+ scope: Public
+ access: ReadWrite
+ api_name: "authenticator_id"
+}
+
+# value for generateChallenge
+prop {
+ prop_name: "vendor.fingerprint.virtual.challenge"
+ type: Long
+ scope: Public
+ access: ReadWrite
+ api_name: "challenge"
+}
+
+# if locked out
+prop {
+ prop_name: "vendor.fingerprint.virtual.lockout"
+ type: Boolean
+ scope: Public
+ access: ReadWrite
+ api_name: "lockout"
+}
+
+# force all authenticate operations to fail
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_authenticate_fails"
+ type: Boolean
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_authenticate_fails"
+}
+
+# force all detectInteraction operations to fail
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_fails"
+ type: Boolean
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_detect_interaction_fails"
+}
+
+# force all enroll operations to fail
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_enroll_fails"
+ type: Boolean
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_enroll_fails"
+}
+
+# add a latency to authentication operations
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_authenticate_latency"
+ type: Integer
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_authenticate_latency"
+}
+
+# add a latency to detectInteraction operations
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_detect_interaction_latency"
+ type: Integer
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_detect_interaction_latency"
+}
+
+# add a latency to enroll operations
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_enroll_latency"
+ type: Integer
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_enroll_latency"
+}
+
+# millisecond duration for authenticate operations
+# (waits for changes to enrollment_hit)
+prop {
+ prop_name: "vendor.fingerprint.virtual.operation_authenticate_duration"
+ type: Integer
+ scope: Public
+ access: ReadWrite
+ api_name: "operation_authenticate_duration"
+}
diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
index b927770..8659b79 100644
--- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
+++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h
@@ -16,71 +16,33 @@
#pragma once
-#include <android-base/logging.h>
+#include <aidl/android/hardware/biometrics/fingerprint/ISessionCallback.h>
+
#include <random>
+#include "CancellationSignal.h"
+
+using namespace ::aidl::android::hardware::biometrics::common;
+
namespace aidl::android::hardware::biometrics::fingerprint {
+// A fake engine that is backed by system properties instead of hardware.
class FakeFingerprintEngine {
public:
FakeFingerprintEngine() : mRandom(std::mt19937::default_seed) {}
- void generateChallengeImpl(ISessionCallback* cb) {
- LOG(INFO) << "generateChallengeImpl";
- std::uniform_int_distribution<int64_t> dist;
- auto challenge = dist(mRandom);
- cb->onChallengeGenerated(challenge);
- }
-
- void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) {
- LOG(INFO) << "revokeChallengeImpl";
- cb->onChallengeRevoked(challenge);
- }
-
- void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat) {
- LOG(INFO) << "enrollImpl";
- // Do proper HAT verification in the real implementation.
- if (hat.mac.empty()) {
- cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */);
- return;
- }
- cb->onEnrollmentProgress(0 /* enrollmentId */, 0 /* remaining */);
- }
-
- void authenticateImpl(ISessionCallback* cb, int64_t /* operationId */) {
- LOG(INFO) << "authenticateImpl";
- cb->onAuthenticationSucceeded(0 /* enrollmentId */, {} /* hat */);
- }
-
- void detectInteractionImpl(ISessionCallback* cb) {
- LOG(INFO) << "detectInteractionImpl";
- cb->onInteractionDetected();
- }
-
- void enumerateEnrollmentsImpl(ISessionCallback* cb) {
- LOG(INFO) << "enumerateEnrollmentsImpl";
- cb->onEnrollmentsEnumerated({} /* enrollmentIds */);
- }
-
- void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds) {
- LOG(INFO) << "removeEnrollmentsImpl";
- cb->onEnrollmentsRemoved(enrollmentIds);
- }
-
- void getAuthenticatorIdImpl(ISessionCallback* cb) {
- LOG(INFO) << "getAuthenticatorIdImpl";
- cb->onAuthenticatorIdRetrieved(0 /* authenticatorId */);
- }
-
- void invalidateAuthenticatorIdImpl(ISessionCallback* cb) {
- LOG(INFO) << "invalidateAuthenticatorIdImpl";
- cb->onAuthenticatorIdInvalidated(0 /* newAuthenticatorId */);
- }
-
- void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/) {
- LOG(INFO) << "resetLockoutImpl";
- cb->onLockoutCleared();
- }
+ void generateChallengeImpl(ISessionCallback* cb);
+ void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge);
+ void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& hat,
+ const std::future<void>& cancel);
+ void authenticateImpl(ISessionCallback* cb, int64_t operationId,
+ const std::future<void>& cancel);
+ void detectInteractionImpl(ISessionCallback* cb, const std::future<void>& cancel);
+ void enumerateEnrollmentsImpl(ISessionCallback* cb);
+ void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector<int32_t>& enrollmentIds);
+ void getAuthenticatorIdImpl(ISessionCallback* cb);
+ void invalidateAuthenticatorIdImpl(ISessionCallback* cb);
+ void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/);
std::mt19937 mRandom;
};
diff --git a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
new file mode 100644
index 0000000..742d933
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2021 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 <android/binder_process.h>
+#include <fingerprint.sysprop.h>
+#include <gtest/gtest.h>
+
+#include <aidl/android/hardware/biometrics/fingerprint/BnSessionCallback.h>
+
+#include "FakeFingerprintEngine.h"
+
+using namespace ::android::fingerprint::virt;
+using namespace ::aidl::android::hardware::biometrics::fingerprint;
+using namespace ::aidl::android::hardware::keymaster;
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+class TestSessionCallback : public BnSessionCallback {
+ public:
+ ndk::ScopedAStatus onChallengeGenerated(int64_t challenge) override {
+ mLastChallenge = challenge;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onChallengeRevoked(int64_t challenge) override {
+ mLastChallengeRevoked = challenge;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onError(fingerprint::Error error, int32_t) override {
+ mError = error;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onEnrollmentProgress(int32_t enrollmentId, int32_t remaining) override {
+ if (remaining == 0) mLastEnrolled = enrollmentId;
+ return ndk::ScopedAStatus::ok();
+ };
+
+ ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t enrollmentId,
+ const keymaster::HardwareAuthToken&) override {
+ mLastAuthenticated = enrollmentId;
+ mAuthenticateFailed = false;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onAuthenticationFailed() override {
+ mLastAuthenticated = 0;
+ mAuthenticateFailed = true;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onInteractionDetected() override {
+ mInteractionDetectedCount++;
+ return ndk::ScopedAStatus::ok();
+ };
+ ndk::ScopedAStatus onAcquired(AcquiredInfo /*info*/, int32_t /*vendorCode*/) override {
+ return ndk::ScopedAStatus::ok();
+ }
+ ::ndk::ScopedAStatus onEnrollmentsEnumerated(
+ const std::vector<int32_t>& enrollmentIds) override {
+ mLastEnrollmentEnumerated = enrollmentIds;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onEnrollmentsRemoved(const std::vector<int32_t>& enrollmentIds) override {
+ mLastEnrollmentRemoved = enrollmentIds;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t authenticatorId) override {
+ mLastAuthenticatorId = authenticatorId;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t authenticatorId) override {
+ mLastAuthenticatorId = authenticatorId;
+ mAuthenticatorIdInvalidated = true;
+ return ndk::ScopedAStatus::ok();
+ };
+ ::ndk::ScopedAStatus onLockoutPermanent() override {
+ mLockoutPermanent = true;
+ return ndk::ScopedAStatus::ok();
+ };
+ ndk::ScopedAStatus onLockoutTimed(int64_t /* timeout */) override {
+ return ndk::ScopedAStatus::ok();
+ }
+ ndk::ScopedAStatus onLockoutCleared() override { return ndk::ScopedAStatus::ok(); }
+ ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); }
+
+ Error mError = Error::UNKNOWN;
+ int64_t mLastChallenge = -1;
+ int64_t mLastChallengeRevoked = -1;
+ int32_t mLastEnrolled = -1;
+ int32_t mLastAuthenticated = -1;
+ int64_t mLastAuthenticatorId = -1;
+ std::vector<int32_t> mLastEnrollmentEnumerated;
+ std::vector<int32_t> mLastEnrollmentRemoved;
+ bool mAuthenticateFailed = false;
+ bool mAuthenticatorIdInvalidated = false;
+ bool mLockoutPermanent = false;
+ int mInteractionDetectedCount = 0;
+};
+
+class FakeFingerprintEngineTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ FingerprintHalProperties::operation_enroll_latency(0);
+ FingerprintHalProperties::operation_authenticate_latency(0);
+ FingerprintHalProperties::operation_detect_interaction_latency(0);
+ mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
+ }
+
+ FakeFingerprintEngine mEngine;
+ std::shared_ptr<TestSessionCallback> mCallback;
+ std::promise<void> mCancel;
+};
+
+TEST_F(FakeFingerprintEngineTest, GenerateChallenge) {
+ mEngine.generateChallengeImpl(mCallback.get());
+ ASSERT_EQ(FingerprintHalProperties::challenge().value(), mCallback->mLastChallenge);
+}
+
+TEST_F(FakeFingerprintEngineTest, RevokeChallenge) {
+ auto challenge = FingerprintHalProperties::challenge().value_or(10);
+ mEngine.revokeChallengeImpl(mCallback.get(), challenge);
+ ASSERT_FALSE(FingerprintHalProperties::challenge().has_value());
+ ASSERT_EQ(challenge, mCallback->mLastChallengeRevoked);
+}
+
+TEST_F(FakeFingerprintEngineTest, ResetLockout) {
+ FingerprintHalProperties::lockout(true);
+ mEngine.resetLockoutImpl(mCallback.get(), {});
+ ASSERT_FALSE(FingerprintHalProperties::lockout().value_or(true));
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticatorId) {
+ FingerprintHalProperties::authenticator_id(50);
+ mEngine.getAuthenticatorIdImpl(mCallback.get());
+ ASSERT_EQ(50, mCallback->mLastAuthenticatorId);
+ ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticatorIdInvalidate) {
+ FingerprintHalProperties::authenticator_id(500);
+ mEngine.invalidateAuthenticatorIdImpl(mCallback.get());
+ ASSERT_NE(500, FingerprintHalProperties::authenticator_id().value());
+ ASSERT_TRUE(mCallback->mAuthenticatorIdInvalidated);
+}
+
+TEST_F(FakeFingerprintEngineTest, Enroll) {
+ FingerprintHalProperties::enrollments({});
+ FingerprintHalProperties::next_enrollment("4:0,0:true");
+ keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+ mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+ ASSERT_FALSE(FingerprintHalProperties::next_enrollment().has_value());
+ ASSERT_EQ(1, FingerprintHalProperties::enrollments().size());
+ ASSERT_EQ(4, FingerprintHalProperties::enrollments()[0].value());
+ ASSERT_EQ(4, mCallback->mLastEnrolled);
+}
+
+TEST_F(FakeFingerprintEngineTest, EnrollCancel) {
+ FingerprintHalProperties::enrollments({});
+ auto next = "4:0,0:true";
+ FingerprintHalProperties::next_enrollment(next);
+ keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+ mCancel.set_value();
+ mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+ ASSERT_EQ(Error::CANCELED, mCallback->mError);
+ ASSERT_EQ(-1, mCallback->mLastEnrolled);
+ ASSERT_EQ(0, FingerprintHalProperties::enrollments().size());
+ ASSERT_EQ(next, FingerprintHalProperties::next_enrollment().value_or(""));
+}
+
+TEST_F(FakeFingerprintEngineTest, EnrollFail) {
+ FingerprintHalProperties::enrollments({});
+ auto next = "2:0,0:false";
+ FingerprintHalProperties::next_enrollment(next);
+ keymaster::HardwareAuthToken hat{.mac = {2, 4}};
+ mEngine.enrollImpl(mCallback.get(), hat, mCancel.get_future());
+ ASSERT_EQ(Error::UNABLE_TO_PROCESS, mCallback->mError);
+ ASSERT_EQ(-1, mCallback->mLastEnrolled);
+ ASSERT_EQ(0, FingerprintHalProperties::enrollments().size());
+ ASSERT_FALSE(FingerprintHalProperties::next_enrollment().has_value());
+}
+
+TEST_F(FakeFingerprintEngineTest, Authenticate) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit(2);
+ mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+ ASSERT_FALSE(mCallback->mAuthenticateFailed);
+ ASSERT_EQ(2, mCallback->mLastAuthenticated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateCancel) {
+ FingerprintHalProperties::enrollments({2});
+ FingerprintHalProperties::enrollment_hit(2);
+ mCancel.set_value();
+ mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+ ASSERT_EQ(Error::CANCELED, mCallback->mError);
+ ASSERT_EQ(-1, mCallback->mLastAuthenticated);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateNotSet) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit({});
+ mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+ ASSERT_TRUE(mCallback->mAuthenticateFailed);
+ ASSERT_EQ(mCallback->mError, Error::UNABLE_TO_PROCESS);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateNotEnrolled) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit(3);
+ mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+ ASSERT_TRUE(mCallback->mAuthenticateFailed);
+ ASSERT_EQ(mCallback->mError, Error::UNABLE_TO_PROCESS);
+}
+
+TEST_F(FakeFingerprintEngineTest, AuthenticateLockout) {
+ FingerprintHalProperties::enrollments({22, 2});
+ FingerprintHalProperties::enrollment_hit(2);
+ FingerprintHalProperties::lockout(true);
+ mEngine.authenticateImpl(mCallback.get(), 0, mCancel.get_future());
+ ASSERT_TRUE(mCallback->mLockoutPermanent);
+ ASSERT_NE(mCallback->mError, Error::UNKNOWN);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetect) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit(2);
+ mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+ ASSERT_EQ(1, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectCancel) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit(2);
+ mCancel.set_value();
+ mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+ ASSERT_EQ(Error::CANCELED, mCallback->mError);
+ ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectNotSet) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit({});
+ mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+ ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, InteractionDetectNotEnrolled) {
+ FingerprintHalProperties::enrollments({1, 2});
+ FingerprintHalProperties::enrollment_hit(25);
+ mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
+ ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
+}
+
+TEST_F(FakeFingerprintEngineTest, EnumerateEnrolled) {
+ FingerprintHalProperties::enrollments({2, 4, 8});
+ mEngine.enumerateEnrollmentsImpl(mCallback.get());
+ ASSERT_EQ(3, mCallback->mLastEnrollmentEnumerated.size());
+ for (auto id : FingerprintHalProperties::enrollments()) {
+ ASSERT_TRUE(std::find(mCallback->mLastEnrollmentEnumerated.begin(),
+ mCallback->mLastEnrollmentEnumerated.end(),
+ id) != mCallback->mLastEnrollmentEnumerated.end());
+ }
+}
+
+TEST_F(FakeFingerprintEngineTest, RemoveEnrolled) {
+ FingerprintHalProperties::enrollments({2, 4, 8, 1});
+ mEngine.removeEnrollmentsImpl(mCallback.get(), {2, 8});
+ auto enrolls = FingerprintHalProperties::enrollments();
+ ASSERT_EQ(2, mCallback->mLastEnrollmentRemoved.size());
+ for (auto id : {2, 8}) {
+ ASSERT_TRUE(std::find(mCallback->mLastEnrollmentRemoved.begin(),
+ mCallback->mLastEnrollmentRemoved.end(),
+ id) != mCallback->mLastEnrollmentRemoved.end());
+ }
+ ASSERT_EQ(2, enrolls.size());
+ for (auto id : {1, 4}) {
+ ASSERT_TRUE(std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end());
+ }
+}
+
+} // namespace aidl::android::hardware::biometrics::fingerprint
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ ABinderProcess_startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/biometrics/fingerprint/aidl/vts/Android.bp b/biometrics/fingerprint/aidl/vts/Android.bp
index 30d5624..a474f66 100644
--- a/biometrics/fingerprint/aidl/vts/Android.bp
+++ b/biometrics/fingerprint/aidl/vts/Android.bp
@@ -15,8 +15,8 @@
],
srcs: ["VtsHalBiometricsFingerprintTargetTest.cpp"],
static_libs: [
- "android.hardware.biometrics.common-V1-ndk",
- "android.hardware.biometrics.fingerprint-V1-ndk",
+ "android.hardware.biometrics.common-V2-ndk",
+ "android.hardware.biometrics.fingerprint-V2-ndk",
"android.hardware.keymaster-V3-ndk",
],
shared_libs: [
diff --git a/bluetooth/1.0/default/test/fuzzer/Android.bp b/bluetooth/1.0/default/test/fuzzer/Android.bp
index 81f328e..691136f 100644
--- a/bluetooth/1.0/default/test/fuzzer/Android.bp
+++ b/bluetooth/1.0/default/test/fuzzer/Android.bp
@@ -46,7 +46,6 @@
"android.hardware.bluetooth-async",
"android.hardware.bluetooth-hci",
"libcutils",
- "libutils",
],
shared_libs: [
"android.hardware.bluetooth@1.0",
@@ -54,6 +53,7 @@
"libhidlbase",
"libbt-vendor-fuzz",
"liblog",
+ "libutils",
],
fuzz_config: {
cc: [
diff --git a/bluetooth/audio/aidl/default/BluetoothAudioProvider.cpp b/bluetooth/audio/aidl/default/BluetoothAudioProvider.cpp
index 0dd8148..2a88959 100644
--- a/bluetooth/audio/aidl/default/BluetoothAudioProvider.cpp
+++ b/bluetooth/audio/aidl/default/BluetoothAudioProvider.cpp
@@ -45,6 +45,7 @@
latency_modes_ = latencyModes;
audio_config_ = std::make_unique<AudioConfiguration>(audio_config);
stack_iface_ = host_if;
+ is_binder_died = false;
AIBinder_linkToDeath(stack_iface_->asBinder().get(), death_recipient_.get(),
this);
@@ -59,8 +60,10 @@
if (stack_iface_ != nullptr) {
BluetoothAudioSessionReport::OnSessionEnded(session_type_);
- AIBinder_unlinkToDeath(stack_iface_->asBinder().get(),
- death_recipient_.get(), this);
+ if (!is_binder_died) {
+ AIBinder_unlinkToDeath(stack_iface_->asBinder().get(),
+ death_recipient_.get(), this);
+ }
} else {
LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
<< " has NO session";
@@ -147,6 +150,7 @@
LOG(ERROR) << __func__ << ": Null AudioProvider HAL died";
return;
}
+ provider->is_binder_died = true;
provider->endSession();
}
diff --git a/bluetooth/audio/aidl/default/BluetoothAudioProvider.h b/bluetooth/audio/aidl/default/BluetoothAudioProvider.h
index a9f830a..dbfff7d 100644
--- a/bluetooth/audio/aidl/default/BluetoothAudioProvider.h
+++ b/bluetooth/audio/aidl/default/BluetoothAudioProvider.h
@@ -62,6 +62,7 @@
std::unique_ptr<AudioConfiguration> audio_config_ = nullptr;
SessionType session_type_;
std::vector<LatencyMode> latency_modes_;
+ bool is_binder_died = false;
};
} // namespace audio
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 8b3830a..ff84558 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -26,6 +26,18 @@
</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.authsecret</name>
<version>1</version>
<interface>
diff --git a/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h b/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h
index ae17c51..1d81f7b 100644
--- a/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h
+++ b/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h
@@ -80,8 +80,9 @@
getDisplayCommand(display).colorTransformMatrix.emplace(std::move(matVec));
}
- void setDisplayBrightness(int64_t display, float brightness) {
- getDisplayCommand(display).brightness.emplace(DisplayBrightness{.brightness = brightness});
+ void setDisplayBrightness(int64_t display, float brightness, float brightnessNits) {
+ getDisplayCommand(display).brightness.emplace(
+ DisplayBrightness{.brightness = brightness, .brightnessNits = brightnessNits});
}
void setClientTarget(int64_t display, uint32_t slot, const native_handle_t* target,
diff --git a/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_ReadbackTest.cpp b/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_ReadbackTest.cpp
index 147a9ee..c081199 100644
--- a/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_ReadbackTest.cpp
+++ b/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_ReadbackTest.cpp
@@ -974,7 +974,7 @@
// Preconditions to successfully run are knowing the max brightness and successfully applying
// the max brightness
ASSERT_GT(maxBrightnessNits, 0.f);
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 1.f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 1.f, maxBrightnessNits);
execute();
ASSERT_TRUE(mReader.takeErrors().empty());
diff --git a/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp b/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp
index 2d08ac6..2cae5a2 100644
--- a/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp
+++ b/graphics/composer/aidl/vts/VtsHalGraphicsComposer3_TargetTest.cpp
@@ -1374,7 +1374,7 @@
bool brightnessSupport = std::find(capabilities.begin(), capabilities.end(),
DisplayCapability::BRIGHTNESS) != capabilities.end();
if (!brightnessSupport) {
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 0.5f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 0.5f, -1.f);
execute();
const auto errors = mReader.takeErrors();
EXPECT_EQ(1, errors.size());
@@ -1383,23 +1383,23 @@
return;
}
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 0.0f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 0.0f, -1.f);
execute();
EXPECT_TRUE(mReader.takeErrors().empty());
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 0.5f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 0.5f, -1.f);
execute();
EXPECT_TRUE(mReader.takeErrors().empty());
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 1.0f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 1.0f, -1.f);
execute();
EXPECT_TRUE(mReader.takeErrors().empty());
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ -1.0f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ -1.0f, -1.f);
execute();
EXPECT_TRUE(mReader.takeErrors().empty());
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 2.0f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ 2.0f, -1.f);
execute();
{
const auto errors = mReader.takeErrors();
@@ -1407,7 +1407,7 @@
EXPECT_EQ(IComposerClient::EX_BAD_PARAMETER, errors[0].errorCode);
}
- mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ -2.0f);
+ mWriter.setDisplayBrightness(getPrimaryDisplayId(), /*brightness*/ -2.0f, -1.f);
execute();
{
const auto errors = mReader.takeErrors();
diff --git a/graphics/mapper/3.0/utils/vts/MapperVts.cpp b/graphics/mapper/3.0/utils/vts/MapperVts.cpp
index de886a9..c470a4a 100644
--- a/graphics/mapper/3.0/utils/vts/MapperVts.cpp
+++ b/graphics/mapper/3.0/utils/vts/MapperVts.cpp
@@ -14,7 +14,9 @@
* limitations under the License.
*/
+#include <android-base/properties.h>
#include <mapper-vts/3.0/MapperVts.h>
+#include "gtest/gtest.h"
namespace android {
namespace hardware {
@@ -94,23 +96,31 @@
std::vector<const native_handle_t*> bufferHandles;
bufferHandles.reserve(count);
mAllocator->allocate(
- descriptor, count,
- [&](const auto& tmpError, const auto& tmpStride, const auto& tmpBuffers) {
- ASSERT_EQ(Error::NONE, tmpError) << "failed to allocate buffers";
- ASSERT_EQ(count, tmpBuffers.size()) << "invalid buffer array";
-
- for (uint32_t i = 0; i < count; i++) {
- if (import) {
- ASSERT_NO_FATAL_FAILURE(bufferHandles.push_back(importBuffer(tmpBuffers[i])));
- } else {
- ASSERT_NO_FATAL_FAILURE(bufferHandles.push_back(cloneBuffer(tmpBuffers[i])));
+ descriptor, count,
+ [&](const auto& tmpError, const auto& tmpStride, const auto& tmpBuffers) {
+ if (tmpError != Error::NONE) {
+ if (base::GetIntProperty("ro.vendor.build.version.sdk", 0, 0, INT_MAX) < 33) {
+ GTEST_SKIP() << "Old vendor grallocs may not support P010";
+ } else {
+ GTEST_FAIL() << "failed to allocate buffers";
+ }
}
- }
+ ASSERT_EQ(count, tmpBuffers.size()) << "invalid buffer array";
- if (outStride) {
- *outStride = tmpStride;
- }
- });
+ for (uint32_t i = 0; i < count; i++) {
+ if (import) {
+ ASSERT_NO_FATAL_FAILURE(
+ bufferHandles.push_back(importBuffer(tmpBuffers[i])));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(
+ bufferHandles.push_back(cloneBuffer(tmpBuffers[i])));
+ }
+ }
+
+ if (outStride) {
+ *outStride = tmpStride;
+ }
+ });
if (::testing::Test::HasFatalFailure()) {
bufferHandles.clear();
@@ -127,7 +137,7 @@
}
auto buffers = allocate(descriptor, 1, import, outStride);
- if (::testing::Test::HasFatalFailure()) {
+ if (::testing::Test::HasFatalFailure() || ::testing::Test::IsSkipped()) {
return nullptr;
}
diff --git a/graphics/mapper/3.0/vts/functional/VtsHalGraphicsMapperV3_0TargetTest.cpp b/graphics/mapper/3.0/vts/functional/VtsHalGraphicsMapperV3_0TargetTest.cpp
index 6c90af4..3b1bfab 100644
--- a/graphics/mapper/3.0/vts/functional/VtsHalGraphicsMapperV3_0TargetTest.cpp
+++ b/graphics/mapper/3.0/vts/functional/VtsHalGraphicsMapperV3_0TargetTest.cpp
@@ -337,6 +337,10 @@
uint32_t stride;
ASSERT_NO_FATAL_FAILURE(bufferHandle = mGralloc->allocate(info, true, &stride));
+ if (::testing::Test::IsSkipped()) {
+ GTEST_SKIP();
+ }
+
ASSERT_NE(nullptr, bufferHandle);
const IMapper::Rect region{0, 0, static_cast<int32_t>(info.width),
diff --git a/graphics/mapper/4.0/utils/vts/MapperVts.cpp b/graphics/mapper/4.0/utils/vts/MapperVts.cpp
index 901f0e3..4a6f68d 100644
--- a/graphics/mapper/4.0/utils/vts/MapperVts.cpp
+++ b/graphics/mapper/4.0/utils/vts/MapperVts.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <android-base/properties.h>
#include <gralloctypes/Gralloc4.h>
#include <mapper-vts/4.0/MapperVts.h>
@@ -95,7 +96,14 @@
return;
}
- ASSERT_EQ(Error::NONE, tmpError) << "failed to allocate buffers";
+ if (tmpError != Error::NONE) {
+ if (base::GetIntProperty("ro.vendor.build.version.sdk", 0, 0,
+ INT_MAX) < 33) {
+ GTEST_SKIP() << "Old vendor grallocs may not support P010";
+ } else {
+ GTEST_FAIL() << "failed to allocate buffers";
+ }
+ }
ASSERT_EQ(count, tmpBuffers.size()) << "invalid buffer array";
for (uint32_t i = 0; i < count; i++) {
@@ -133,11 +141,7 @@
}
auto buffers = allocate(descriptor, 1, import, tolerance, outStride);
- if (::testing::Test::HasFatalFailure()) {
- return nullptr;
- }
-
- if (buffers.size() != 1) {
+ if (::testing::Test::HasFatalFailure() || ::testing::Test::IsSkipped() || buffers.size() != 1) {
return nullptr;
}
return buffers[0];
diff --git a/graphics/mapper/4.0/vts/functional/VtsHalGraphicsMapperV4_0TargetTest.cpp b/graphics/mapper/4.0/vts/functional/VtsHalGraphicsMapperV4_0TargetTest.cpp
index 463b565..8f440e4 100644
--- a/graphics/mapper/4.0/vts/functional/VtsHalGraphicsMapperV4_0TargetTest.cpp
+++ b/graphics/mapper/4.0/vts/functional/VtsHalGraphicsMapperV4_0TargetTest.cpp
@@ -999,10 +999,13 @@
auto info = mDummyDescriptorInfo;
info.format = PixelFormat::YCBCR_P010;
- const native_handle_t* bufferHandle;
uint32_t stride;
- ASSERT_NO_FATAL_FAILURE(
- bufferHandle = mGralloc->allocate(info, true, Tolerance::kToleranceStrict, &stride));
+ const native_handle_t* bufferHandle =
+ mGralloc->allocate(info, true, Tolerance::kToleranceStrict, &stride);
+
+ if (::testing::Test::IsSkipped()) {
+ GTEST_SKIP();
+ }
const IMapper::Rect region{0, 0, static_cast<int32_t>(info.width),
static_cast<int32_t>(info.height)};
diff --git a/health/aidl/OWNERS b/health/aidl/OWNERS
index cd06415..fcad499 100644
--- a/health/aidl/OWNERS
+++ b/health/aidl/OWNERS
@@ -1,2 +1,4 @@
# Bug component: 30545
elsk@google.com
+smoreland@google.com
+stayfan@google.com
diff --git a/keymaster/4.0/support/fuzzer/Android.bp b/keymaster/4.0/support/fuzzer/Android.bp
index 3a3f4d5..8bc681a 100644
--- a/keymaster/4.0/support/fuzzer/Android.bp
+++ b/keymaster/4.0/support/fuzzer/Android.bp
@@ -30,12 +30,12 @@
"libbase",
"liblog",
"libkeymaster4support",
- "libutils",
],
shared_libs: [
"android.hardware.keymaster@4.0",
"libcrypto",
"libhidlbase",
+ "libutils",
],
fuzz_config: {
cc: [
diff --git a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
index 22aa0f9..bf56860 100644
--- a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
+++ b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
@@ -2866,8 +2866,8 @@
EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, params));
string plaintext;
- ErrorCode error = Finish(message, &plaintext);
- if (error == ErrorCode::INVALID_INPUT_LENGTH) {
+ ErrorCode error = Finish(ciphertext, &plaintext);
+ if (error == ErrorCode::INVALID_ARGUMENT) {
// This is the expected error, we can exit the test now.
return;
} else {
diff --git a/media/omx/1.0/vts/functional/store/VtsHalMediaOmxV1_0TargetStoreTest.cpp b/media/omx/1.0/vts/functional/store/VtsHalMediaOmxV1_0TargetStoreTest.cpp
index d9a6363..5fa13e7 100644
--- a/media/omx/1.0/vts/functional/store/VtsHalMediaOmxV1_0TargetStoreTest.cpp
+++ b/media/omx/1.0/vts/functional/store/VtsHalMediaOmxV1_0TargetStoreTest.cpp
@@ -24,6 +24,7 @@
#include <android-base/strings.h>
#include <android/api-level.h>
+#include <VtsCoreUtil.h>
#include <android/hardware/media/omx/1.0/IOmx.h>
#include <android/hardware/media/omx/1.0/IOmxNode.h>
#include <android/hardware/media/omx/1.0/IOmxObserver.h>
@@ -377,6 +378,10 @@
return android::base::GetIntProperty("ro.product.first_api_level", __ANDROID_API_T__);
}
+static bool isTV() {
+ return testing::deviceSupportsFeature("android.software.leanback");
+}
+
// list components and roles.
TEST_P(StoreHidlTest, OmxCodecAllowedTest) {
hidl_vec<IOmx::ComponentInfo> componentInfos = getComponentInfoList(omx);
@@ -384,9 +389,16 @@
for (std::string role : info.mRoles) {
if (role.find("video_decoder") != std::string::npos ||
role.find("video_encoder") != std::string::npos) {
- ASSERT_LT(getFirstApiLevel(), __ANDROID_API_S__)
- << " Component: " << info.mName.c_str() << " Role: " << role.c_str()
- << " not allowed for devices launching with Android S and above";
+ // Codec2 is not mandatory on Android TV devices that launched with Android S
+ if (isTV()) {
+ ASSERT_LT(getFirstApiLevel(), __ANDROID_API_T__)
+ << " Component: " << info.mName.c_str() << " Role: " << role.c_str()
+ << " not allowed for devices launching with Android T and above";
+ } else {
+ ASSERT_LT(getFirstApiLevel(), __ANDROID_API_S__)
+ << " Component: " << info.mName.c_str() << " Role: " << role.c_str()
+ << " not allowed for devices launching with Android S and above";
+ }
}
if (role.find("audio_decoder") != std::string::npos ||
role.find("audio_encoder") != std::string::npos) {
diff --git a/radio/aidl/Android.bp b/radio/aidl/Android.bp
index c328879..b1949dd 100644
--- a/radio/aidl/Android.bp
+++ b/radio/aidl/Android.bp
@@ -15,7 +15,7 @@
stability: "vintf",
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -37,7 +37,7 @@
imports: ["android.hardware.radio"],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -66,7 +66,7 @@
imports: ["android.hardware.radio"],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -95,7 +95,7 @@
imports: ["android.hardware.radio"],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -117,7 +117,7 @@
imports: ["android.hardware.radio"],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -146,7 +146,7 @@
imports: ["android.hardware.radio"],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -178,7 +178,7 @@
],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
@@ -210,7 +210,7 @@
imports: ["android.hardware.radio"],
backend: {
cpp: {
- enabled: false,
+ enabled: true,
},
java: {
sdk_version: "module_current",
diff --git a/radio/aidl/vts/radio_config_test.cpp b/radio/aidl/vts/radio_config_test.cpp
index 5e1c811..258b172 100644
--- a/radio/aidl/vts/radio_config_test.cpp
+++ b/radio/aidl/vts/radio_config_test.cpp
@@ -59,6 +59,7 @@
serial = GetRandomSerialNumber();
ndk::ScopedAStatus res = radio_config->getHalDeviceCapabilities(serial);
ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
ALOGI("getHalDeviceCapabilities, rspInfo.error = %s\n",
toString(radioRsp_config->rspInfo.error).c_str());
}
@@ -70,6 +71,7 @@
serial = GetRandomSerialNumber();
ndk::ScopedAStatus res = radio_config->getSimSlotsStatus(serial);
ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
ALOGI("getSimSlotsStatus, rspInfo.error = %s\n",
toString(radioRsp_config->rspInfo.error).c_str());
}
@@ -166,31 +168,60 @@
* Test IRadioConfig.setSimSlotsMapping() for the response returned.
*/
TEST_P(RadioConfigTest, setSimSlotsMapping) {
- serial = GetRandomSerialNumber();
- SlotPortMapping slotPortMapping;
- slotPortMapping.physicalSlotId = 0;
- slotPortMapping.portId = 0;
- std::vector<SlotPortMapping> slotPortMappingList = {slotPortMapping};
- if (isDsDsEnabled()) {
- slotPortMapping.physicalSlotId = 1;
- slotPortMappingList.push_back(slotPortMapping);
- } else if (isTsTsEnabled()) {
- slotPortMapping.physicalSlotId = 1;
- slotPortMappingList.push_back(slotPortMapping);
- slotPortMapping.physicalSlotId = 2;
- slotPortMappingList.push_back(slotPortMapping);
- }
- ndk::ScopedAStatus res = radio_config->setSimSlotsMapping(serial, slotPortMappingList);
- ASSERT_OK(res);
- EXPECT_EQ(std::cv_status::no_timeout, wait());
- EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_config->rspInfo.type);
- EXPECT_EQ(serial, radioRsp_config->rspInfo.serial);
- ALOGI("setSimSlotsMapping, rspInfo.error = %s\n",
- toString(radioRsp_config->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(radioRsp_config->rspInfo.error, {RadioError::NONE}));
+ // get slot status and set SIM slots mapping based on the result.
+ updateSimSlotStatus();
+ if (radioRsp_config->rspInfo.error == RadioError::NONE) {
+ SlotPortMapping slotPortMapping;
+ // put invalid value at first and adjust by slotStatusResponse.
+ slotPortMapping.physicalSlotId = -1;
+ slotPortMapping.portId = -1;
+ std::vector<SlotPortMapping> slotPortMappingList = {slotPortMapping};
+ if (isDsDsEnabled()) {
+ slotPortMappingList.push_back(slotPortMapping);
+ } else if (isTsTsEnabled()) {
+ slotPortMappingList.push_back(slotPortMapping);
+ slotPortMappingList.push_back(slotPortMapping);
+ }
+ for (size_t i = 0; i < radioRsp_config->simSlotStatus.size(); i++) {
+ ASSERT_TRUE(radioRsp_config->simSlotStatus[i].portInfo.size() > 0);
+ for (size_t j = 0; j < radioRsp_config->simSlotStatus[i].portInfo.size(); j++) {
+ if (radioRsp_config->simSlotStatus[i].portInfo[j].portActive) {
+ int32_t logicalSlotId =
+ radioRsp_config->simSlotStatus[i].portInfo[j].logicalSlotId;
+ // logicalSlotId should be 0 or positive numbers if the port
+ // is active.
+ EXPECT_GE(logicalSlotId, 0);
+ // logicalSlotId should be less than the maximum number of
+ // supported SIM slots.
+ EXPECT_LT(logicalSlotId, slotPortMappingList.size());
+ if (logicalSlotId >= 0 && logicalSlotId < slotPortMappingList.size()) {
+ slotPortMappingList[logicalSlotId].physicalSlotId = i;
+ slotPortMappingList[logicalSlotId].portId = j;
+ }
+ }
+ }
+ }
- // Give some time for modem to fully switch SIM configuration
- sleep(MODEM_SET_SIM_SLOT_MAPPING_DELAY_IN_SECONDS);
+ // set SIM slots mapping
+ for (size_t i = 0; i < slotPortMappingList.size(); i++) {
+ // physicalSlotId and portId should be 0 or positive numbers for the
+ // input of setSimSlotsMapping.
+ EXPECT_GE(slotPortMappingList[i].physicalSlotId, 0);
+ EXPECT_GE(slotPortMappingList[i].portId, 0);
+ }
+ serial = GetRandomSerialNumber();
+ ndk::ScopedAStatus res = radio_config->setSimSlotsMapping(serial, slotPortMappingList);
+ ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_config->rspInfo.type);
+ EXPECT_EQ(serial, radioRsp_config->rspInfo.serial);
+ ALOGI("setSimSlotsMapping, rspInfo.error = %s\n",
+ toString(radioRsp_config->rspInfo.error).c_str());
+ ASSERT_TRUE(CheckAnyOfErrors(radioRsp_config->rspInfo.error, {RadioError::NONE}));
+
+ // Give some time for modem to fully switch SIM configuration
+ sleep(MODEM_SET_SIM_SLOT_MAPPING_DELAY_IN_SECONDS);
+ }
}
/*
@@ -207,12 +238,24 @@
EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_config->rspInfo.type);
EXPECT_EQ(serial, radioRsp_config->rspInfo.serial);
if (radioRsp_config->rspInfo.error == RadioError::NONE) {
+ uint8_t simCount = 0;
// check if cardState is present, portInfo size should be more than 0
for (const SimSlotStatus& slotStatusResponse : radioRsp_config->simSlotStatus) {
if (slotStatusResponse.cardState == CardStatus::STATE_PRESENT) {
ASSERT_TRUE(slotStatusResponse.portInfo.size() > 0);
- ASSERT_TRUE(slotStatusResponse.portInfo[0].portActive);
+ for (const SimPortInfo& simPortInfo : slotStatusResponse.portInfo) {
+ if (simPortInfo.portActive) {
+ simCount++;
+ }
+ }
}
}
+ if (isSsSsEnabled()) {
+ EXPECT_EQ(1, simCount);
+ } else if (isDsDsEnabled()) {
+ EXPECT_EQ(2, simCount);
+ } else if (isTsTsEnabled()) {
+ EXPECT_EQ(3, simCount);
+ }
}
}
diff --git a/radio/aidl/vts/radio_modem_response.cpp b/radio/aidl/vts/radio_modem_response.cpp
index d2715a8..20b44c5 100644
--- a/radio/aidl/vts/radio_modem_response.cpp
+++ b/radio/aidl/vts/radio_modem_response.cpp
@@ -24,6 +24,7 @@
ndk::ScopedAStatus RadioModemResponse::enableModemResponse(const RadioResponseInfo& info) {
rspInfo = info;
+ enableModemResponseToggle = !enableModemResponseToggle;
parent_modem.notify(info.serial);
return ndk::ScopedAStatus::ok();
}
diff --git a/radio/aidl/vts/radio_modem_utils.h b/radio/aidl/vts/radio_modem_utils.h
index 8779e0c..49e1891 100644
--- a/radio/aidl/vts/radio_modem_utils.h
+++ b/radio/aidl/vts/radio_modem_utils.h
@@ -37,7 +37,7 @@
RadioResponseInfo rspInfo;
bool isModemEnabled;
- bool enableModemResponseToggle;
+ bool enableModemResponseToggle = false;
virtual ndk::ScopedAStatus acknowledgeRequest(int32_t serial) override;
diff --git a/radio/aidl/vts/radio_network_test.cpp b/radio/aidl/vts/radio_network_test.cpp
index e1d508d..0bae374 100644
--- a/radio/aidl/vts/radio_network_test.cpp
+++ b/radio/aidl/vts/radio_network_test.cpp
@@ -592,6 +592,89 @@
}
/*
+ * Test IRadioNetwork.setSignalStrengthReportingCriteria() for multi-RANs per request
+ */
+TEST_P(RadioNetworkTest, setSignalStrengthReportingCriteria_multiRansPerRequest) {
+ SignalThresholdInfo signalThresholdInfoGeran;
+ signalThresholdInfoGeran.signalMeasurement = SignalThresholdInfo::SIGNAL_MEASUREMENT_TYPE_RSSI;
+ signalThresholdInfoGeran.hysteresisMs = 5000;
+ signalThresholdInfoGeran.hysteresisDb = 2;
+ signalThresholdInfoGeran.thresholds = {-109, -103, -97, -89};
+ signalThresholdInfoGeran.isEnabled = true;
+ signalThresholdInfoGeran.ran = AccessNetwork::GERAN;
+
+ SignalThresholdInfo signalThresholdInfoUtran;
+ signalThresholdInfoUtran.signalMeasurement = SignalThresholdInfo::SIGNAL_MEASUREMENT_TYPE_RSCP;
+ signalThresholdInfoUtran.hysteresisMs = 5000;
+ signalThresholdInfoUtran.hysteresisDb = 2;
+ signalThresholdInfoUtran.thresholds = {-110, -97, -73, -49, -25};
+ signalThresholdInfoUtran.isEnabled = true;
+ signalThresholdInfoUtran.ran = AccessNetwork::UTRAN;
+
+ SignalThresholdInfo signalThresholdInfoEutran;
+ signalThresholdInfoEutran.signalMeasurement = SignalThresholdInfo::SIGNAL_MEASUREMENT_TYPE_RSRP;
+ signalThresholdInfoEutran.hysteresisMs = 5000;
+ signalThresholdInfoEutran.hysteresisDb = 2;
+ signalThresholdInfoEutran.thresholds = {-128, -108, -88, -68};
+ signalThresholdInfoEutran.isEnabled = true;
+ signalThresholdInfoEutran.ran = AccessNetwork::EUTRAN;
+
+ SignalThresholdInfo signalThresholdInfoCdma2000;
+ signalThresholdInfoCdma2000.signalMeasurement =
+ SignalThresholdInfo::SIGNAL_MEASUREMENT_TYPE_RSSI;
+ signalThresholdInfoCdma2000.hysteresisMs = 5000;
+ signalThresholdInfoCdma2000.hysteresisDb = 2;
+ signalThresholdInfoCdma2000.thresholds = {-105, -90, -75, -65};
+ signalThresholdInfoCdma2000.isEnabled = true;
+ signalThresholdInfoCdma2000.ran = AccessNetwork::CDMA2000;
+
+ SignalThresholdInfo signalThresholdInfoNgran;
+ signalThresholdInfoNgran.signalMeasurement =
+ SignalThresholdInfo::SIGNAL_MEASUREMENT_TYPE_SSRSRP;
+ signalThresholdInfoNgran.hysteresisMs = 5000;
+ signalThresholdInfoNgran.hysteresisDb = 0;
+ signalThresholdInfoNgran.thresholds = {-105, -90, -75, -65};
+ signalThresholdInfoNgran.isEnabled = true;
+ signalThresholdInfoNgran.ran = AccessNetwork::NGRAN;
+
+ const static std::vector<SignalThresholdInfo> candidateSignalThresholdInfos = {
+ signalThresholdInfoGeran, signalThresholdInfoUtran, signalThresholdInfoEutran,
+ signalThresholdInfoCdma2000, signalThresholdInfoNgran};
+
+ std::vector<SignalThresholdInfo> supportedSignalThresholdInfos;
+ for (size_t i = 0; i < candidateSignalThresholdInfos.size(); i++) {
+ serial = GetRandomSerialNumber();
+ ndk::ScopedAStatus res = radio_network->setSignalStrengthReportingCriteria(
+ serial, {candidateSignalThresholdInfos[i]});
+ ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ if (radioRsp_network->rspInfo.error == RadioError::NONE) {
+ supportedSignalThresholdInfos.push_back(signalThresholdInfoGeran);
+ } else {
+ // Refer to IRadioNetworkResponse#setSignalStrengthReportingCriteriaResponse
+ ASSERT_TRUE(CheckAnyOfErrors(
+ radioRsp_network->rspInfo.error,
+ {RadioError::INVALID_ARGUMENTS, RadioError::RADIO_NOT_AVAILABLE}));
+ }
+ }
+
+ ASSERT_FALSE(supportedSignalThresholdInfos.empty());
+
+ serial = GetRandomSerialNumber();
+ ndk::ScopedAStatus res = radio_network->setSignalStrengthReportingCriteria(
+ serial, supportedSignalThresholdInfos);
+ ASSERT_OK(res);
+ EXPECT_EQ(std::cv_status::no_timeout, wait());
+ EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_network->rspInfo.type);
+ EXPECT_EQ(serial, radioRsp_network->rspInfo.serial);
+
+ ALOGI("setSignalStrengthReportingCriteria_multiRansPerRequest, rspInfo.error = %s\n",
+ toString(radioRsp_network->rspInfo.error).c_str());
+
+ ASSERT_TRUE(CheckAnyOfErrors(radioRsp_network->rspInfo.error, {RadioError::NONE}));
+}
+
+/*
* Test IRadioNetwork.setLinkCapacityReportingCriteria() invalid hysteresisDlKbps
*/
TEST_P(RadioNetworkTest, setLinkCapacityReportingCriteria_invalidHysteresisDlKbps) {
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
index ca89555..c30c183 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -242,7 +242,8 @@
* not a multiple of the AES block size, finish() must return
* ErrorCode::INVALID_INPUT_LENGTH. If padding is PaddingMode::PKCS7, pad the data per the
* PKCS#7 specification, including adding an additional padding block if the data is a
- * multiple of the block length.
+ * multiple of the block length. If padding is PaddingMode::PKCS7 and decryption does not
+ * result in valid padding, return ErrorCode::INVALID_ARGUMENT.
*
* o BlockMode::GCM. During encryption, after processing all plaintext, compute the tag
* (Tag::MAC_LENGTH bytes) and append it to the returned ciphertext. During decryption,
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index e73f46c..cbe4512 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -5481,18 +5481,45 @@
EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, params));
string plaintext;
- ErrorCode error = Finish(message, &plaintext);
- if (error == ErrorCode::INVALID_INPUT_LENGTH) {
+ ErrorCode error = Finish(ciphertext, &plaintext);
+ if (error == ErrorCode::INVALID_ARGUMENT) {
// This is the expected error, we can exit the test now.
return;
} else {
// Very small chance we got valid decryption, so try again.
- ASSERT_EQ(error, ErrorCode::OK);
+ ASSERT_EQ(error, ErrorCode::OK)
+ << "Expected INVALID_ARGUMENT or (rarely) OK, got " << error;
}
}
FAIL() << "Corrupt ciphertext should have failed to decrypt by now.";
}
+/*
+ * EncryptionOperationsTest.AesEcbPkcs7CiphertextTooShort
+ *
+ * Verifies that AES decryption fails in the correct way when the padding is corrupted.
+ */
+TEST_P(EncryptionOperationsTest, AesEcbPkcs7CiphertextTooShort) {
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .AesEncryptionKey(128)
+ .Authorization(TAG_BLOCK_MODE, BlockMode::ECB)
+ .Padding(PaddingMode::PKCS7)));
+
+ auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7);
+
+ string message = "a";
+ string ciphertext = EncryptMessage(message, params);
+ EXPECT_EQ(16U, ciphertext.size());
+ EXPECT_NE(ciphertext, message);
+
+ // Shorten the ciphertext.
+ ciphertext.resize(ciphertext.size() - 1);
+ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, params));
+ string plaintext;
+ EXPECT_EQ(ErrorCode::INVALID_INPUT_LENGTH, Finish(ciphertext, &plaintext));
+}
+
vector<uint8_t> CopyIv(const AuthorizationSet& set) {
auto iv = set.GetTagValue(TAG_NONCE);
EXPECT_TRUE(iv);
diff --git a/sensors/aidl/android/hardware/sensors/ISensors.aidl b/sensors/aidl/android/hardware/sensors/ISensors.aidl
index 2ac1884..2c68489 100644
--- a/sensors/aidl/android/hardware/sensors/ISensors.aidl
+++ b/sensors/aidl/android/hardware/sensors/ISensors.aidl
@@ -229,8 +229,7 @@
*
* @param mem shared memory info data structure.
* @param out channelHandle The registered channel handle.
- * @return The direct channel handle, which is positive if successfully registered, and -1
- * otherwise.
+ * @return The direct channel handle, which is positive if successfully registered.
* @return Status::ok on success
* EX_ILLEGAL_ARGUMENT if the shared memory information is not consistent.
* EX_UNSUPPORTED_OPERATION if this functionality is unsupported.
@@ -245,7 +244,7 @@
* @see OperationMode
* @param mode The operation mode.
* @return Status::ok on success
- * EX_UNSUPPORTED_OPERATION if requested mode is not supported.
+ * EX_UNSUPPORTED_OPERATION or EX_ILLEGAL_ARGUMENT if requested mode is not supported.
* EX_SECURITY if the operation is not allowed.
*/
void setOperationMode(in OperationMode mode);
diff --git a/sensors/aidl/default/multihal/HalProxyAidl.cpp b/sensors/aidl/default/multihal/HalProxyAidl.cpp
index 64805e6..e6bcdad 100644
--- a/sensors/aidl/default/multihal/HalProxyAidl.cpp
+++ b/sensors/aidl/default/multihal/HalProxyAidl.cpp
@@ -29,6 +29,7 @@
using ::aidl::android::hardware::sensors::ISensors;
using ::aidl::android::hardware::sensors::ISensorsCallback;
using ::android::hardware::sensors::V2_1::implementation::convertToOldEvent;
+using ::ndk::ScopedAStatus;
namespace aidl {
namespace android {
@@ -36,19 +37,22 @@
namespace sensors {
namespace implementation {
-static binder_status_t resultToBinderStatus(::android::hardware::sensors::V1_0::Result result) {
- switch (result) {
- case ::android::hardware::sensors::V1_0::Result::OK:
- return STATUS_OK;
- case ::android::hardware::sensors::V1_0::Result::PERMISSION_DENIED:
- return STATUS_PERMISSION_DENIED;
- case ::android::hardware::sensors::V1_0::Result::NO_MEMORY:
- return STATUS_NO_MEMORY;
- case ::android::hardware::sensors::V1_0::Result::BAD_VALUE:
- return STATUS_BAD_VALUE;
- case ::android::hardware::sensors::V1_0::Result::INVALID_OPERATION:
- return STATUS_INVALID_OPERATION;
- }
+static ScopedAStatus
+resultToAStatus(::android::hardware::sensors::V1_0::Result result) {
+ switch (result) {
+ case ::android::hardware::sensors::V1_0::Result::OK:
+ return ScopedAStatus::ok();
+ case ::android::hardware::sensors::V1_0::Result::PERMISSION_DENIED:
+ return ScopedAStatus::fromExceptionCode(EX_SECURITY);
+ case ::android::hardware::sensors::V1_0::Result::NO_MEMORY:
+ return ScopedAStatus::fromServiceSpecificError(ISensors::ERROR_NO_MEMORY);
+ case ::android::hardware::sensors::V1_0::Result::BAD_VALUE:
+ return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ case ::android::hardware::sensors::V1_0::Result::INVALID_OPERATION:
+ return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ default:
+ return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED);
+ }
}
static ::android::hardware::sensors::V1_0::RateLevel convertRateLevel(
@@ -62,6 +66,8 @@
return ::android::hardware::sensors::V1_0::RateLevel::FAST;
case ISensors::RateLevel::VERY_FAST:
return ::android::hardware::sensors::V1_0::RateLevel::VERY_FAST;
+ default:
+ assert(false);
}
}
@@ -72,6 +78,8 @@
return ::android::hardware::sensors::V1_0::OperationMode::NORMAL;
case ISensors::OperationMode::DATA_INJECTION:
return ::android::hardware::sensors::V1_0::OperationMode::DATA_INJECTION;
+ default:
+ assert(false);
}
}
@@ -82,6 +90,8 @@
return ::android::hardware::sensors::V1_0::SharedMemType::ASHMEM;
case ISensors::SharedMemInfo::SharedMemType::GRALLOC:
return ::android::hardware::sensors::V1_0::SharedMemType::GRALLOC;
+ default:
+ assert(false);
}
}
@@ -90,6 +100,8 @@
switch (sharedMemFormat) {
case ISensors::SharedMemInfo::SharedMemFormat::SENSORS_EVENT:
return ::android::hardware::sensors::V1_0::SharedMemFormat::SENSORS_EVENT;
+ default:
+ assert(false);
}
}
@@ -104,106 +116,125 @@
return v1SharedMemInfo;
}
-::ndk::ScopedAStatus HalProxyAidl::activate(int32_t in_sensorHandle, bool in_enabled) {
- return ndk::ScopedAStatus::fromStatus(
- resultToBinderStatus(HalProxy::activate(in_sensorHandle, in_enabled)));
+ScopedAStatus HalProxyAidl::activate(int32_t in_sensorHandle, bool in_enabled) {
+ return resultToAStatus(HalProxy::activate(in_sensorHandle, in_enabled));
}
-::ndk::ScopedAStatus HalProxyAidl::batch(int32_t in_sensorHandle, int64_t in_samplingPeriodNs,
- int64_t in_maxReportLatencyNs) {
- return ndk::ScopedAStatus::fromStatus(resultToBinderStatus(
- HalProxy::batch(in_sensorHandle, in_samplingPeriodNs, in_maxReportLatencyNs)));
+ScopedAStatus HalProxyAidl::batch(int32_t in_sensorHandle,
+ int64_t in_samplingPeriodNs,
+ int64_t in_maxReportLatencyNs) {
+ return resultToAStatus(HalProxy::batch(in_sensorHandle, in_samplingPeriodNs,
+ in_maxReportLatencyNs));
}
-::ndk::ScopedAStatus HalProxyAidl::configDirectReport(int32_t in_sensorHandle,
- int32_t in_channelHandle,
- ISensors::RateLevel in_rate,
- int32_t* _aidl_return) {
- binder_status_t binderStatus;
- HalProxy::configDirectReport(
- in_sensorHandle, in_channelHandle, convertRateLevel(in_rate),
- [&binderStatus, _aidl_return](::android::hardware::sensors::V1_0::Result result,
- int32_t reportToken) {
- binderStatus = resultToBinderStatus(result);
- *_aidl_return = reportToken;
- });
- return ndk::ScopedAStatus::fromStatus(binderStatus);
+ScopedAStatus HalProxyAidl::configDirectReport(int32_t in_sensorHandle,
+ int32_t in_channelHandle,
+ ISensors::RateLevel in_rate,
+ int32_t *_aidl_return) {
+ ScopedAStatus status =
+ ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ HalProxy::configDirectReport(
+ in_sensorHandle, in_channelHandle, convertRateLevel(in_rate),
+ [&status, _aidl_return](::android::hardware::sensors::V1_0::Result result,
+ int32_t reportToken) {
+ status = resultToAStatus(result);
+ *_aidl_return = reportToken;
+ });
+
+ return status;
}
-::ndk::ScopedAStatus HalProxyAidl::flush(int32_t in_sensorHandle) {
- return ndk::ScopedAStatus::fromStatus(resultToBinderStatus(HalProxy::flush(in_sensorHandle)));
+ScopedAStatus HalProxyAidl::flush(int32_t in_sensorHandle) {
+ return resultToAStatus(HalProxy::flush(in_sensorHandle));
}
-::ndk::ScopedAStatus HalProxyAidl::getSensorsList(
- std::vector<::aidl::android::hardware::sensors::SensorInfo>* _aidl_return) {
- for (const auto& sensor : HalProxy::getSensors()) {
- _aidl_return->push_back(convertSensorInfo(sensor.second));
- }
- return ndk::ScopedAStatus::ok();
+ScopedAStatus HalProxyAidl::getSensorsList(
+ std::vector<::aidl::android::hardware::sensors::SensorInfo> *_aidl_return) {
+ for (const auto &sensor : HalProxy::getSensors()) {
+ _aidl_return->push_back(convertSensorInfo(sensor.second));
+ }
+ return ScopedAStatus::ok();
}
-::ndk::ScopedAStatus HalProxyAidl::initialize(
- const MQDescriptor<::aidl::android::hardware::sensors::Event, SynchronizedReadWrite>&
- in_eventQueueDescriptor,
- const MQDescriptor<int32_t, SynchronizedReadWrite>& in_wakeLockDescriptor,
- const std::shared_ptr<ISensorsCallback>& in_sensorsCallback) {
- ::android::sp<::android::hardware::sensors::V2_1::implementation::ISensorsCallbackWrapperBase>
- dynamicCallback = new ISensorsCallbackWrapperAidl(in_sensorsCallback);
+ScopedAStatus HalProxyAidl::initialize(
+ const MQDescriptor<::aidl::android::hardware::sensors::Event,
+ SynchronizedReadWrite> &in_eventQueueDescriptor,
+ const MQDescriptor<int32_t, SynchronizedReadWrite> &in_wakeLockDescriptor,
+ const std::shared_ptr<ISensorsCallback> &in_sensorsCallback) {
+ ::android::sp<::android::hardware::sensors::V2_1::implementation::
+ ISensorsCallbackWrapperBase>
+ dynamicCallback = new ISensorsCallbackWrapperAidl(in_sensorsCallback);
- auto aidlEventQueue =
- std::make_unique<::android::AidlMessageQueue<::aidl::android::hardware::sensors::Event,
- SynchronizedReadWrite>>(
- in_eventQueueDescriptor, true /* resetPointers */);
- std::unique_ptr<
- ::android::hardware::sensors::V2_1::implementation::EventMessageQueueWrapperBase>
- eventQueue = std::make_unique<EventMessageQueueWrapperAidl>(aidlEventQueue);
+ auto aidlEventQueue = std::make_unique<::android::AidlMessageQueue<
+ ::aidl::android::hardware::sensors::Event, SynchronizedReadWrite>>(
+ in_eventQueueDescriptor, true /* resetPointers */);
+ std::unique_ptr<::android::hardware::sensors::V2_1::implementation::
+ EventMessageQueueWrapperBase>
+ eventQueue =
+ std::make_unique<EventMessageQueueWrapperAidl>(aidlEventQueue);
- auto aidlWakeLockQueue =
- std::make_unique<::android::AidlMessageQueue<int32_t, SynchronizedReadWrite>>(
- in_wakeLockDescriptor, true /* resetPointers */);
- std::unique_ptr<
- ::android::hardware::sensors::V2_1::implementation::WakeLockMessageQueueWrapperBase>
- wakeLockQueue = std::make_unique<WakeLockMessageQueueWrapperAidl>(aidlWakeLockQueue);
+ auto aidlWakeLockQueue = std::make_unique<
+ ::android::AidlMessageQueue<int32_t, SynchronizedReadWrite>>(
+ in_wakeLockDescriptor, true /* resetPointers */);
+ std::unique_ptr<::android::hardware::sensors::V2_1::implementation::
+ WakeLockMessageQueueWrapperBase>
+ wakeLockQueue =
+ std::make_unique<WakeLockMessageQueueWrapperAidl>(aidlWakeLockQueue);
- return ndk::ScopedAStatus::fromStatus(
- resultToBinderStatus(initializeCommon(eventQueue, wakeLockQueue, dynamicCallback)));
+ return resultToAStatus(
+ initializeCommon(eventQueue, wakeLockQueue, dynamicCallback));
}
-::ndk::ScopedAStatus HalProxyAidl::injectSensorData(
- const ::aidl::android::hardware::sensors::Event& in_event) {
- ::android::hardware::sensors::V2_1::Event hidlEvent;
- convertToHidlEvent(in_event, &hidlEvent);
- return ndk::ScopedAStatus::fromStatus(
- resultToBinderStatus(HalProxy::injectSensorData(convertToOldEvent(hidlEvent))));
+ScopedAStatus HalProxyAidl::injectSensorData(
+ const ::aidl::android::hardware::sensors::Event &in_event) {
+ ::android::hardware::sensors::V2_1::Event hidlEvent;
+ convertToHidlEvent(in_event, &hidlEvent);
+ return resultToAStatus(
+ HalProxy::injectSensorData(convertToOldEvent(hidlEvent)));
}
-::ndk::ScopedAStatus HalProxyAidl::registerDirectChannel(const ISensors::SharedMemInfo& in_mem,
- int32_t* _aidl_return) {
- binder_status_t binderStatus;
- ::android::hardware::sensors::V1_0::SharedMemInfo sharedMemInfo = convertSharedMemInfo(in_mem);
+ScopedAStatus
+HalProxyAidl::registerDirectChannel(const ISensors::SharedMemInfo &in_mem,
+ int32_t *_aidl_return) {
+ ScopedAStatus status =
+ ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ ::android::hardware::sensors::V1_0::SharedMemInfo sharedMemInfo =
+ convertSharedMemInfo(in_mem);
- HalProxy::registerDirectChannel(
- sharedMemInfo,
- [&binderStatus, _aidl_return](::android::hardware::sensors::V1_0::Result result,
- int32_t reportToken) {
- binderStatus = resultToBinderStatus(result);
- *_aidl_return = reportToken;
- });
+ HalProxy::registerDirectChannel(
+ sharedMemInfo,
+ [&status, _aidl_return](::android::hardware::sensors::V1_0::Result result,
+ int32_t reportToken) {
+ status = resultToAStatus(result);
+ *_aidl_return = reportToken;
+ });
- native_handle_delete(
- const_cast<native_handle_t*>(sharedMemInfo.memoryHandle.getNativeHandle()));
- return ndk::ScopedAStatus::fromStatus(binderStatus);
+ native_handle_delete(const_cast<native_handle_t *>(
+ sharedMemInfo.memoryHandle.getNativeHandle()));
+
+ return status;
}
-::ndk::ScopedAStatus HalProxyAidl::setOperationMode(
- ::aidl::android::hardware::sensors::ISensors::OperationMode in_mode) {
- return ndk::ScopedAStatus::fromStatus(
- resultToBinderStatus(HalProxy::setOperationMode(convertOperationMode(in_mode))));
+ScopedAStatus HalProxyAidl::setOperationMode(
+ ::aidl::android::hardware::sensors::ISensors::OperationMode in_mode) {
+ return resultToAStatus(
+ HalProxy::setOperationMode(convertOperationMode(in_mode)));
}
-::ndk::ScopedAStatus HalProxyAidl::unregisterDirectChannel(int32_t in_channelHandle) {
- return ndk::ScopedAStatus::fromStatus(
- resultToBinderStatus(HalProxy::unregisterDirectChannel(in_channelHandle)));
+ScopedAStatus HalProxyAidl::unregisterDirectChannel(int32_t in_channelHandle) {
+ return resultToAStatus(HalProxy::unregisterDirectChannel(in_channelHandle));
+}
+
+binder_status_t HalProxyAidl::dump(int fd, const char ** /* args */,
+ uint32_t /* numArgs */) {
+ native_handle_t *nativeHandle =
+ native_handle_create(1 /* numFds */, 0 /* numInts */);
+ nativeHandle->data[0] = fd;
+
+ HalProxy::debug(nativeHandle, {} /* args */);
+
+ native_handle_delete(nativeHandle);
+ return STATUS_OK;
}
} // namespace implementation
diff --git a/sensors/aidl/default/multihal/include/HalProxyAidl.h b/sensors/aidl/default/multihal/include/HalProxyAidl.h
index 7401726..5c81715 100644
--- a/sensors/aidl/default/multihal/include/HalProxyAidl.h
+++ b/sensors/aidl/default/multihal/include/HalProxyAidl.h
@@ -55,6 +55,8 @@
::ndk::ScopedAStatus setOperationMode(
::aidl::android::hardware::sensors::ISensors::OperationMode in_mode) override;
::ndk::ScopedAStatus unregisterDirectChannel(int32_t in_channelHandle) override;
+
+ binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
};
} // namespace implementation
diff --git a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
index 83d0dc9..d536e29 100644
--- a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
+++ b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
@@ -599,10 +599,12 @@
ASSERT_TRUE(getSensors()->setOperationMode(ISensors::OperationMode::DATA_INJECTION).isOk());
ASSERT_TRUE(getSensors()->setOperationMode(ISensors::OperationMode::NORMAL).isOk());
} else {
- ASSERT_EQ(getSensors()
- ->setOperationMode(ISensors::OperationMode::DATA_INJECTION)
- .getExceptionCode(),
- EX_UNSUPPORTED_OPERATION);
+ int errorCode =
+ getSensors()
+ ->setOperationMode(ISensors::OperationMode::DATA_INJECTION)
+ .getExceptionCode();
+ ASSERT_TRUE((errorCode == EX_UNSUPPORTED_OPERATION) ||
+ (errorCode == EX_ILLEGAL_ARGUMENT));
}
}
@@ -938,10 +940,10 @@
if (isDirectReportRateSupported(sensor, rateLevel)) {
ASSERT_TRUE(status.isOk());
if (rateLevel != ISensors::RateLevel::STOP) {
- ASSERT_GT(*reportToken, 0);
- } else {
- ASSERT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
+ ASSERT_GT(*reportToken, 0);
}
+ } else {
+ ASSERT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
}
}
@@ -982,11 +984,15 @@
::ndk::ScopedAStatus status = registerDirectChannel(mem->getSharedMemInfo(), &channelHandle);
if (supportsSharedMemType) {
ASSERT_TRUE(status.isOk());
- ASSERT_EQ(channelHandle, 0);
+ ASSERT_GT(channelHandle, 0);
+
+ // Verify that the memory has been zeroed
+ for (size_t i = 0; i < mem->getSize(); i++) {
+ ASSERT_EQ(buffer[i], 0x00);
+ }
} else {
int32_t error = supportsAnyDirectChannel ? EX_ILLEGAL_ARGUMENT : EX_UNSUPPORTED_OPERATION;
ASSERT_EQ(status.getExceptionCode(), error);
- ASSERT_EQ(channelHandle, -1);
}
*directChannelHandle = channelHandle;
}
@@ -1038,7 +1044,7 @@
// Verify that a sensor handle of -1 is only acceptable when using RateLevel::STOP
ndk::ScopedAStatus status = configDirectReport(-1 /* sensorHandle */, directChannelHandle,
ISensors::RateLevel::NORMAL, &reportToken);
- ASSERT_EQ(status.getServiceSpecificError(), android::BAD_VALUE);
+ ASSERT_EQ(status.getExceptionCode(), EX_ILLEGAL_ARGUMENT);
status = configDirectReport(-1 /* sensorHandle */, directChannelHandle,
ISensors::RateLevel::STOP, &reportToken);
diff --git a/tv/tuner/aidl/default/tuner-default.rc b/tv/tuner/aidl/default/tuner-default.rc
index d0248c2..661c219 100644
--- a/tv/tuner/aidl/default/tuner-default.rc
+++ b/tv/tuner/aidl/default/tuner-default.rc
@@ -3,5 +3,5 @@
user media
group mediadrm drmrpc
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks
+ task_profiles ProcessCapacityHigh
onrestart restart media.tuner
diff --git a/uwb/aidl/Android.bp b/uwb/aidl/Android.bp
index 2cc1e6a..36cde9b 100755
--- a/uwb/aidl/Android.bp
+++ b/uwb/aidl/Android.bp
@@ -14,6 +14,7 @@
vendor_available: true,
srcs: ["android/hardware/uwb/*.aidl"],
stability: "vintf",
+ host_supported: true,
backend: {
java: {
sdk_version: "module_Tiramisu",
diff --git a/uwb/aidl/android/hardware/uwb/IUwbChip.aidl b/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
index 00cb8e0..6ee5799 100644
--- a/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
+++ b/uwb/aidl/android/hardware/uwb/IUwbChip.aidl
@@ -71,8 +71,8 @@
* The UCI message format is as per UCI protocol and it is
* defined in "FiRa Consortium - UCI Generic Specification_v1.0" specification at FiRa
* consortium.
- * WIP doc link: https://groups.firaconsortium.org/wg/Technical/document/folder/127.
- * TODO(b/196004116): Link to the published specification.
+ *
+ * UCI 1.1 specification: https://groups.firaconsortium.org/wg/members/document/1949.
*
* This method may queue writes and return immediately, or it may block until data is written.
* Implementation must guarantee that writes are executed in order.
diff --git a/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl b/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl
index 75853cd..f31aeba 100755
--- a/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl
+++ b/uwb/aidl/android/hardware/uwb/IUwbClientCallback.aidl
@@ -28,8 +28,7 @@
* can use to pass incoming data to the stack. These include UCI
* responses and notifications from the UWB subsystem.
*
- * WIP doc link: https://groups.firaconsortium.org/wg/Technical/document/folder/127.
- * TODO(b/196004116): Link to the published specification.
+ * UCI 1.1 specification: https://groups.firaconsortium.org/wg/members/document/1949.
*
* @param data UCI packet sent.
*/
diff --git a/vibrator/aidl/default/Android.bp b/vibrator/aidl/default/Android.bp
index acdbdcd..c4140be 100644
--- a/vibrator/aidl/default/Android.bp
+++ b/vibrator/aidl/default/Android.bp
@@ -63,7 +63,6 @@
"libbinder_random_parcel",
"libcutils",
"liblog",
- "libutils",
"libvibratorexampleimpl",
],
target: {
@@ -71,12 +70,14 @@
shared_libs: [
"libbinder_ndk",
"libbinder",
+ "libutils",
],
},
host: {
static_libs: [
"libbinder_ndk",
"libbinder",
+ "libutils",
],
},
darwin: {
diff --git a/wifi/1.6/default/wifi_chip.cpp b/wifi/1.6/default/wifi_chip.cpp
index 7f0e97d..f062409 100644
--- a/wifi/1.6/default/wifi_chip.cpp
+++ b/wifi/1.6/default/wifi_chip.cpp
@@ -1003,14 +1003,14 @@
br_ifaces_ap_instances_[br_ifname] = ap_instances;
if (!iface_util_->createBridge(br_ifname)) {
LOG(ERROR) << "Failed createBridge - br_name=" << br_ifname.c_str();
- invalidateAndClearBridgedAp(br_ifname);
+ deleteApIface(br_ifname);
return {createWifiStatus(WifiStatusCode::ERROR_NOT_AVAILABLE), {}};
}
for (auto const& instance : ap_instances) {
// Bind ap instance interface to AP bridge
if (!iface_util_->addIfaceToBridge(br_ifname, instance)) {
LOG(ERROR) << "Failed add if to Bridge - if_name=" << instance.c_str();
- invalidateAndClearBridgedAp(br_ifname);
+ deleteApIface(br_ifname);
return {createWifiStatus(WifiStatusCode::ERROR_NOT_AVAILABLE), {}};
}
}
@@ -1044,8 +1044,7 @@
// nan/rtt objects over AP iface. But, there is no harm to do it
// here and not make that assumption all over the place.
invalidateAndRemoveDependencies(ifname);
- // Clear the bridge interface and the iface instance.
- invalidateAndClearBridgedAp(ifname);
+ deleteApIface(ifname);
invalidateAndClear(ap_ifaces_, iface);
for (const auto& callback : event_cb_handler_.getCallbacks()) {
if (!callback->onIfaceRemoved(IfaceType::AP, ifname).isOk()) {
@@ -2005,21 +2004,28 @@
br_ifaces_ap_instances_.clear();
}
-void WifiChip::invalidateAndClearBridgedAp(const std::string& br_name) {
- if (br_name.empty()) return;
- // delete managed interfaces
+void WifiChip::deleteApIface(const std::string& if_name) {
+ if (if_name.empty()) return;
+ // delete bridged interfaces if have
for (auto const& it : br_ifaces_ap_instances_) {
- if (it.first == br_name) {
+ if (it.first == if_name) {
for (auto const& iface : it.second) {
- iface_util_->removeIfaceFromBridge(br_name, iface);
+ iface_util_->removeIfaceFromBridge(if_name, iface);
legacy_hal_.lock()->deleteVirtualInterface(iface);
}
- iface_util_->deleteBridge(br_name);
- br_ifaces_ap_instances_.erase(br_name);
- break;
+ iface_util_->deleteBridge(if_name);
+ br_ifaces_ap_instances_.erase(if_name);
+ // ifname is bridged AP, return here.
+ return;
}
}
- return;
+
+ // No bridged AP case, delete AP iface
+ legacy_hal::wifi_error legacy_status = legacy_hal_.lock()->deleteVirtualInterface(if_name);
+ if (legacy_status != legacy_hal::WIFI_SUCCESS) {
+ LOG(ERROR) << "Failed to remove interface: " << if_name << " "
+ << legacyErrorToString(legacy_status);
+ }
}
bool WifiChip::findUsingNameFromBridgedApInstances(const std::string& name) {
diff --git a/wifi/1.6/default/wifi_chip.h b/wifi/1.6/default/wifi_chip.h
index f952a68..e8ddaa6 100644
--- a/wifi/1.6/default/wifi_chip.h
+++ b/wifi/1.6/default/wifi_chip.h
@@ -272,7 +272,7 @@
bool writeRingbufferFilesInternal();
std::string getWlanIfaceNameWithType(IfaceType type, unsigned idx);
void invalidateAndClearBridgedApAll();
- void invalidateAndClearBridgedAp(const std::string& br_name);
+ void deleteApIface(const std::string& if_name);
bool findUsingNameFromBridgedApInstances(const std::string& name);
WifiStatus triggerSubsystemRestartInternal();
std::pair<WifiStatus, sp<V1_6::IWifiRttController>> createRttControllerInternal_1_6(
diff --git a/wifi/1.6/default/wifi_legacy_hal.cpp b/wifi/1.6/default/wifi_legacy_hal.cpp
index 8a75fd8..bb8cf59 100644
--- a/wifi/1.6/default/wifi_legacy_hal.cpp
+++ b/wifi/1.6/default/wifi_legacy_hal.cpp
@@ -1549,13 +1549,14 @@
std::pair<wifi_error, wifi_radio_combination_matrix*>
WifiLegacyHal::getSupportedRadioCombinationsMatrix() {
- std::array<char, kMaxSupportedRadioCombinationsMatrixLength> buffer;
- buffer.fill(0);
+ char* buffer = new char[kMaxSupportedRadioCombinationsMatrixLength];
+ std::fill(buffer, buffer + kMaxSupportedRadioCombinationsMatrixLength, 0);
uint32_t size = 0;
wifi_radio_combination_matrix* radio_combination_matrix_ptr =
- reinterpret_cast<wifi_radio_combination_matrix*>(buffer.data());
+ reinterpret_cast<wifi_radio_combination_matrix*>(buffer);
wifi_error status = global_func_table_.wifi_get_supported_radio_combinations_matrix(
- global_handle_, buffer.size(), &size, radio_combination_matrix_ptr);
+ global_handle_, kMaxSupportedRadioCombinationsMatrixLength, &size,
+ radio_combination_matrix_ptr);
CHECK(size >= 0 && size <= kMaxSupportedRadioCombinationsMatrixLength);
return {status, radio_combination_matrix_ptr};
}
diff --git a/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h b/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h
index ac8653e..32e5a6e 100644
--- a/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h
+++ b/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h
@@ -117,14 +117,13 @@
int clientSocketFd, const char* clientName,
struct android_nlinterceptor_InterceptedSocket* interceptedSocket);
-void android_nlinterceptor_closeSocket(
- const struct android_nlinterceptor_InterceptedSocket* sock);
+void android_nlinterceptor_closeSocket(struct android_nlinterceptor_InterceptedSocket sock);
-bool android_nlinterceptor_subscribe(
- const struct android_nlinterceptor_InterceptedSocket* sock, uint32_t group);
+bool android_nlinterceptor_subscribe(struct android_nlinterceptor_InterceptedSocket sock,
+ uint32_t group);
-bool android_nlinterceptor_unsubscribe(
- const struct android_nlinterceptor_InterceptedSocket* sock, uint32_t group);
+bool android_nlinterceptor_unsubscribe(struct android_nlinterceptor_InterceptedSocket sock,
+ uint32_t group);
#ifdef __cplusplus
}
diff --git a/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp b/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp
index 575f900..aae7a3a 100644
--- a/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp
+++ b/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp
@@ -150,25 +150,18 @@
return true;
}
-extern "C" void android_nlinterceptor_closeSocket(
- const android_nlinterceptor_InterceptedSocket* sock) {
- if (!sock) {
- LOG(ERROR) << "Can't close socket identified by a null pointer!";
- return;
- }
- closeSocket({sock->nlFamily, sock->portId});
+extern "C" void android_nlinterceptor_closeSocket(android_nlinterceptor_InterceptedSocket sock) {
+ closeSocket({sock.nlFamily, sock.portId});
}
-extern "C" bool android_nlinterceptor_subscribe(
- const android_nlinterceptor_InterceptedSocket* sock, uint32_t group) {
- if (!sock) return false;
- return subscribe({sock->nlFamily, sock->portId}, group);
+extern "C" bool android_nlinterceptor_subscribe(android_nlinterceptor_InterceptedSocket sock,
+ uint32_t group) {
+ return subscribe({sock.nlFamily, sock.portId}, group);
}
-extern "C" bool android_nlinterceptor_unsubscribe(
- const android_nlinterceptor_InterceptedSocket* sock, uint32_t group) {
- if (!sock) return false;
- return unsubscribe({sock->nlFamily, sock->portId}, group);
+extern "C" bool android_nlinterceptor_unsubscribe(android_nlinterceptor_InterceptedSocket sock,
+ uint32_t group) {
+ return unsubscribe({sock.nlFamily, sock.portId}, group);
}
} // namespace android::nlinterceptor