audio HAL: Support for external device connections
Add methods 'IModule.connect/disconnectExternalDevice' which inform
audio HAL about connection / disconnection of an external
non-attached device. Add method 'getAudioRoutesForPort' to
retrieve only routes that include the specified port.
Update the behavior of 'getAudioPorts' and 'getAudioRoutes'
indicating that the result may change due to instantiation
of new device ports for connected external devices.
Clarify behavior of 'IModule.setAudioPortConfig' that it can not
work with device ports with no profiles.
Add debug flags structure 'ModuleDebug' and method
'IModule.setModuleDebug' to control the debugging aspects. VTS
tests use these flags to test HAL behavior which would otherwise
require human intervention.
Update the default implementation and VTS for the AIDL changes.
Bug: 205884982
Test: atest VtsHalAudioCoreTargetTest
Merged-In: Iad5f7009e283729206f88b6278c8992f7f8a92a2
Change-Id: Iad5f7009e283729206f88b6278c8992f7f8a92a2
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index 4927e83..0524110 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -75,6 +75,7 @@
"android/hardware/audio/core/IModule.aidl",
"android/hardware/audio/core/IStreamIn.aidl",
"android/hardware/audio/core/IStreamOut.aidl",
+ "android/hardware/audio/core/ModuleDebug.aidl",
],
imports: [
"android.hardware.audio.common-V1",
@@ -86,6 +87,4 @@
platform_apis: true,
},
},
- versions: [
- ],
}
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
index 33e8290..f8bc2c7 100644
--- 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
@@ -34,11 +34,15 @@
package android.hardware.audio.core;
@VintfStability
interface IModule {
+ void setModuleDebug(in android.hardware.audio.core.ModuleDebug debug);
+ android.media.audio.common.AudioPort connectExternalDevice(in android.media.audio.common.AudioPort templateIdAndAdditionalData);
+ void disconnectExternalDevice(int portId);
android.hardware.audio.core.AudioPatch[] getAudioPatches();
android.media.audio.common.AudioPort getAudioPort(int portId);
android.media.audio.common.AudioPortConfig[] getAudioPortConfigs();
android.media.audio.common.AudioPort[] getAudioPorts();
android.hardware.audio.core.AudioRoute[] getAudioRoutes();
+ android.hardware.audio.core.AudioRoute[] getAudioRoutesForAudioPort(int portId);
android.hardware.audio.core.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);
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
new file mode 100644
index 0000000..80ee185
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.audio.core;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ModuleDebug {
+ boolean simulateDeviceConnections;
+}
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
index d47ea3c..f406cd8 100644
--- a/audio/aidl/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -22,6 +22,7 @@
import android.hardware.audio.core.AudioRoute;
import android.hardware.audio.core.IStreamIn;
import android.hardware.audio.core.IStreamOut;
+import android.hardware.audio.core.ModuleDebug;
import android.media.audio.common.AudioOffloadInfo;
import android.media.audio.common.AudioPort;
import android.media.audio.common.AudioPortConfig;
@@ -44,6 +45,116 @@
@VintfStability
interface IModule {
/**
+ * Sets debugging configuration for the HAL module. This method is only
+ * called during xTS testing and is intended for validating the aspects of
+ * the HAL module behavior that would otherwise require human intervention.
+ *
+ * The HAL module must throw an error if there is an attempt to change
+ * the debug behavior for the aspect which is currently in use.
+ *
+ * @param debug The debug options.
+ * @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality
+ * which is currently in use.
+ */
+ void setModuleDebug(in ModuleDebug debug);
+
+ /**
+ * Set a device port of an external device into connected state.
+ *
+ * This method is used to inform the HAL module that an external device has
+ * been connected to a device port selected using the 'id' field of the
+ * input AudioPort parameter. This device port must have dynamic profiles
+ * (an empty list of profiles). This port is further referenced to as "port
+ * template" because it acts as a template for creating a new instance of a
+ * "connected" device port which gets returned from this method.
+ *
+ * The input AudioPort parameter may contain any additional data obtained by
+ * the system side from other subsystems. The nature of data depends on the
+ * type of the connection. For example, for point-to-multipoint external
+ * device connections, the input parameter may contain the address of the
+ * connected external device. Another example is EDID information for HDMI
+ * connections (ExtraAudioDescriptor), which can be provided by the HDMI-CEC
+ * HAL module.
+ *
+ * It is the responsibility of the HAL module to query audio profiles
+ * supported by the connected device and return them as part of the returned
+ * AudioPort instance. In the case when the HAL is unable to query the
+ * external device, an error must be thrown.
+ *
+ * Thus, the returned audio port instance is the result of combining the
+ * following information:
+ * - a unique port ID generated by the HAL module;
+ * - static information from the port template;
+ * - list of audio profiles supported by the connected device;
+ * - additional data from the input AudioPort parameter.
+ *
+ * The HAL module must also update the list of audio routes to include the
+ * ID of the instantiated connected device port. Normally, the connected
+ * port allows the same routing as the port template.
+ *
+ * Also see notes on 'ModuleDebug.simulateDeviceConnections'.
+ *
+ * The following protocol is used by HAL module client for handling
+ * connection of an external device:
+ * 1. Obtain the list of device ports and their IDs via 'getAudioPorts'
+ * method. Select the appropriate port template using
+ * AudioDeviceDescription ('ext.device' field of AudioPort).
+ * 2. Combine the ID of the port template with any additional data and call
+ * 'connectExternalDevice'. The HAL module returns a new instance of
+ * AudioPort created using the rules explained above. Both
+ * 'getAudioPort' and 'getAudioPorts' methods will be returning the same
+ * information for this port until disconnection.
+ * 3. Configure the connected port with one of supported profiles using
+ * 'setAudioPortConfig'.
+ * 4. Query the list of AudioRoutes for the new AudioPort using
+ * 'getAudioRoutesForAudioPort' or 'getAudioRoutes' methods.
+ *
+ * External devices are distinguished by the connection type and device
+ * address. Calling this method multiple times to inform about connection of
+ * the same external device without disconnecting it first is an error.
+ *
+ * The HAL module must perform validation of the input parameter and throw
+ * an error if it is lacking required information, for example, when no
+ * device address is specified for a point-to-multipoint external device
+ * connection.
+ *
+ * Handling of a disconnect is done in a reverse order:
+ * 1. Reset port configuration using the 'resetAudioPortConfig' method.
+ * 2. Release the connected device port by calling the 'disconnectExternalDevice'
+ * method. This also removes the audio routes associated with this
+ * device port.
+ *
+ * @return New instance of an audio port for the connected external device.
+ * @param templateIdAndAdditionalData Specifies port template ID and any
+ * additional data.
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the template port can not be found by the ID.
+ * - If the template is not a device port, or
+ * it does not have dynamic profiles.
+ * - If the input parameter is lacking required
+ * information.
+ * @throws EX_ILLEGAL_STATE In the following cases:
+ * - If the HAL module is unable to query audio profiles.
+ * - If the external device has already been connected.
+ */
+ AudioPort connectExternalDevice(in AudioPort templateIdAndAdditionalData);
+
+ /**
+ * Set a device port of a an external device into disconnected state.
+ *
+ * This method is used to inform the HAL module that an external device has
+ * been disconnected. The 'portId' must be of a connected device port
+ * instance previously instantiated using the 'connectExternalDevice'
+ * method.
+ *
+ * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+ * - If the port can not be found by the ID.
+ * - If this is not a connected device port.
+ * @throws EX_ILLEGAL_STATE If the port has active configurations.
+ */
+ void disconnectExternalDevice(int portId);
+
+ /**
* Return all audio patches of this module.
*
* Returns a list of audio patches, that is, established connections between
@@ -57,12 +168,10 @@
* 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'.
+ * audio port. The values of the AudioPort structure must be the same as
+ * currently returned by the 'getAudioPorts' method. The 'getAudioPort'
+ * method is provided to reduce overhead in the case when the client needs
+ * to check the state of one port only.
*
* @return The current state of an audio port.
* @param portId The ID of the audio port.
@@ -87,33 +196,45 @@
AudioPortConfig[] getAudioPortConfigs();
/**
- * Return all audio ports provided by this module.
+ * Return the current state of all audio ports provided by this module.
*
- * Returns a list of all mix ports and device ports provided by this
- * 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.
+ * Returns a list of all mix ports and device ports provided by this HAL
+ * module, reflecting their current states. Each returned port must have a
+ * unique ID within this module ('AudioPort.id' field). The list also
+ * includes "connected" ports created using 'connectExternalDevice' method.
*
* @return The list of audio ports.
*/
AudioPort[] getAudioPorts();
/**
- * Return all audio routes of this module.
+ * Return all current 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.
+ * Returns the current list of audio routes, that is, allowed connections
+ * between audio ports. The list can change when new device audio ports
+ * get created as a result of connecting or disconnecting of external
+ * devices.
*
* @return The list of audio routes.
*/
AudioRoute[] getAudioRoutes();
/**
+ * Return audio routes related to the specified audio port.
+ *
+ * Returns the list of audio routes that include the specified port ID
+ * as a source or as a sink. The returned list is a subset of the result
+ * returned by the 'getAudioRoutes' method, filtered by the port ID.
+ * An empty list can be returned, indicating that the audio port can not
+ * be used for creating audio patches.
+ *
+ * @return The list of audio routes.
+ * @param portId The ID of the audio port.
+ * @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID.
+ */
+ AudioRoute[] getAudioRoutesForAudioPort(int portId);
+
+ /**
* Open an input stream using an existing audio mix port configuration.
*
* The audio port configuration ID must be obtained by calling
@@ -230,6 +351,10 @@
* parameter. The framework can then set the suggested configuration on a
* subsequent retry call to this method.
*
+ * Device ports with dynamic audio profiles (an empty list of profiles)
+ * can not be used with this method. The list of profiles must be filled in
+ * as a result of calling 'connectExternalDevice' method.
+ *
* @return Whether the requested configuration has been applied.
* @param requested Requested audio port configuration.
* @param suggested Same as requested configuration, if it was applied.
@@ -241,7 +366,7 @@
* - 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.
+ * profiles.
*/
boolean setAudioPortConfig(in AudioPortConfig requested, out AudioPortConfig suggested);
diff --git a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
new file mode 100644
index 0000000..858a9bd
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.audio.core;
+
+/**
+ * This structure contains flags used for enabling various debugging aspects
+ * in a HAL module. By default, all debugging aspects are turned off. They
+ * can be enabled during xTS tests for functionality that, for example, would
+ * otherwise require human intervention (e.g. connection of external devices).
+ */
+@JavaDerive(equals=true, toString=true)
+@VintfStability
+parcelable ModuleDebug {
+ /**
+ * When set to 'true', HAL module must simulate connection of external
+ * devices. An external device becomes 'connected' after a call to
+ * IModule.connectExternalDevice, simulation of connection requires:
+ * - provision of at least one non-dynamic device port profile on
+ * connection (as if it was retrieved from a connected device);
+ * - simulating successful application of port configurations for reported
+ * profiles.
+ */
+ boolean simulateDeviceConnections;
+}
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 0a6fe60..4728a89 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -13,6 +13,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
+ "android.media.audio.common.types-V1-ndk",
"android.hardware.audio.core-V1-ndk",
],
export_include_dirs: ["include"],
@@ -36,6 +37,7 @@
shared_libs: [
"libbase",
"libbinder_ndk",
+ "android.media.audio.common.types-V1-ndk",
"android.hardware.audio.core-V1-ndk",
],
static_libs: [
diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp
index 1104caa..19d0b3c 100644
--- a/audio/aidl/default/Configuration.cpp
+++ b/audio/aidl/default/Configuration.cpp
@@ -16,14 +16,15 @@
#include <aidl/android/media/audio/common/AudioChannelLayout.h>
#include <aidl/android/media/audio/common/AudioDeviceType.h>
+#include <aidl/android/media/audio/common/AudioFormatDescription.h>
#include <aidl/android/media/audio/common/AudioFormatType.h>
#include <aidl/android/media/audio/common/AudioIoFlags.h>
#include <aidl/android/media/audio/common/AudioOutputFlags.h>
-#include "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::AudioDeviceDescription;
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
@@ -54,9 +55,11 @@
return profile;
}
-static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags) {
+static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags,
+ std::string connection = "") {
AudioPortDeviceExt deviceExt;
deviceExt.device.type.type = devType;
+ deviceExt.device.type.connection = std::move(connection);
deviceExt.flags = flags;
return AudioPortExt::make<AudioPortExt::Tag::device>(deviceExt);
}
@@ -102,8 +105,64 @@
return route;
}
+// Configuration:
+//
+// Device ports:
+// * "Null", OUT_SPEAKER, default
+// - no profiles specified
+// * "Loopback Out", OUT_SUBMIX
+// - profile PCM 24-bit; STEREO; 48000
+// * "USB Out", OUT_DEVICE, CONNECTION_USB
+// - no profiles specified
+// * "Zero", IN_MICROPHONE, default
+// - no profiles specified
+// * "Loopback In", IN_SUBMIX
+// - profile PCM 24-bit; STEREO; 48000
+// * "USB In", IN_DEVICE, CONNECTION_USB
+// - no profiles specified
+//
+// Mix ports:
+// * "primary output", PRIMARY, 1 max open, 1 max active stream
+// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+// * "loopback output", stream count unlimited
+// - profile PCM 24-bit; STEREO; 48000
+// * "primary input", 2 max open, 2 max active streams
+// - profile PCM 16-bit; MONO, STEREO, FRONT_BACK;
+// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO, FRONT_BACK;
+// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+// * "loopback input", stream count unlimited
+// - profile PCM 24-bit; STEREO; 48000
+//
+// Routes:
+// "primary out" -> "Null"
+// "primary out" -> "USB Out"
+// "loopback out" -> "Loopback Out"
+// "Zero", "USB In" -> "primary input"
+// "Loopback In" -> "loopback input"
+//
+// Initial port configs:
+// * "Null" device port: PCM 24-bit; STEREO; 48000
+// * "Zero" device port: PCM 24-bit; MONO; 48000
+//
+// Profiles for device port connected state:
+// * USB Out":
+// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+// * USB In":
+// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
+// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
+//
Configuration& getNullPrimaryConfiguration() {
static Configuration configuration = []() {
+ const std::vector<AudioProfile> standardPcmAudioProfiles = {
+ createProfile(PcmType::INT_16_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000}),
+ createProfile(PcmType::INT_24_BIT,
+ {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
+ {44100, 48000})};
Configuration c;
AudioPort nullOutDevice =
@@ -111,27 +170,19 @@
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 primaryOutMix = createPort(c.nextPortId++, "primary output",
+ 1 << static_cast<int32_t>(AudioOutputFlags::PRIMARY),
+ false, createPortMixExt(1, 1));
+ primaryOutMix.profiles.insert(primaryOutMix.profiles.begin(),
+ standardPcmAudioProfiles.begin(),
+ standardPcmAudioProfiles.end());
+ c.ports.push_back(primaryOutMix);
+
AudioPort loopOutDevice = createPort(c.nextPortId++, "Loopback Out", 0, false,
createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0));
loopOutDevice.profiles.push_back(
@@ -144,13 +195,22 @@
createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
c.ports.push_back(loopOutMix);
- c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
+ AudioPort usbOutDevice =
+ createPort(c.nextPortId++, "USB Out", 0, false,
+ createDeviceExt(AudioDeviceType::OUT_DEVICE, 0,
+ AudioDeviceDescription::CONNECTION_USB));
+ c.ports.push_back(usbOutDevice);
+ c.connectedProfiles[usbOutDevice.id] = standardPcmAudioProfiles;
AudioPort zeroInDevice =
createPort(c.nextPortId++, "Zero", 0, true,
createDeviceExt(AudioDeviceType::IN_MICROPHONE,
1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
c.ports.push_back(zeroInDevice);
+ c.initialConfigs.push_back(
+ createPortConfig(zeroInDevice.id, zeroInDevice.id, PcmType::INT_24_BIT,
+ AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
+ createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
AudioPort primaryInMix =
createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(2, 2));
@@ -166,13 +226,6 @@
{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(
@@ -185,6 +238,16 @@
createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
c.ports.push_back(loopInMix);
+ AudioPort usbInDevice = createPort(c.nextPortId++, "USB In", 0, true,
+ createDeviceExt(AudioDeviceType::IN_DEVICE, 0,
+ AudioDeviceDescription::CONNECTION_USB));
+ c.ports.push_back(usbInDevice);
+ c.connectedProfiles[usbInDevice.id] = standardPcmAudioProfiles;
+
+ c.routes.push_back(createRoute({primaryOutMix.id}, nullOutDevice.id));
+ c.routes.push_back(createRoute({primaryOutMix.id}, usbOutDevice.id));
+ c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
+ c.routes.push_back(createRoute({zeroInDevice.id, usbInDevice.id}, primaryInMix.id));
c.routes.push_back(createRoute({loopInDevice.id}, loopInMix.id));
c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end());
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index e0a68a5..961ee84 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -18,7 +18,6 @@
#include <set>
#define LOG_TAG "AHAL_Module"
-#define LOG_NDEBUG 0
#include <android-base/logging.h>
#include <aidl/android/media/audio/common/AudioOutputFlags.h>
@@ -81,6 +80,7 @@
}
return false;
}
+
} // namespace
void Module::cleanUpPatch(int32_t patchId) {
@@ -135,6 +135,154 @@
do_insert(patch.sinkPortConfigIds);
}
+ndk::ScopedAStatus Module::setModuleDebug(
+ const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) {
+ LOG(DEBUG) << __func__ << ": old flags:" << mDebug.toString()
+ << ", new flags: " << in_debug.toString();
+ if (mDebug.simulateDeviceConnections != in_debug.simulateDeviceConnections &&
+ !mConnectedDevicePorts.empty()) {
+ LOG(ERROR) << __func__ << ": attempting to change device connections simulation "
+ << "while having external devices connected";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ mDebug = in_debug;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdAndAdditionalData,
+ AudioPort* _aidl_return) {
+ const int32_t templateId = in_templateIdAndAdditionalData.id;
+ auto& ports = getConfig().ports;
+ AudioPort connectedPort;
+ { // Scope the template port so that we don't accidentally modify it.
+ auto templateIt = findById<AudioPort>(ports, templateId);
+ if (templateIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << templateId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (templateIt->ext.getTag() != AudioPortExt::Tag::device) {
+ LOG(ERROR) << __func__ << ": port id " << templateId << " is not a device port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (!templateIt->profiles.empty()) {
+ LOG(ERROR) << __func__ << ": port id " << templateId
+ << " does not have dynamic profiles";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& templateDevicePort = templateIt->ext.get<AudioPortExt::Tag::device>();
+ if (templateDevicePort.device.type.connection.empty()) {
+ LOG(ERROR) << __func__ << ": port id " << templateId << " is permanently attached";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ // Postpone id allocation until we ensure that there are no client errors.
+ connectedPort = *templateIt;
+ connectedPort.extraAudioDescriptors = in_templateIdAndAdditionalData.extraAudioDescriptors;
+ const auto& inputDevicePort =
+ in_templateIdAndAdditionalData.ext.get<AudioPortExt::Tag::device>();
+ auto& connectedDevicePort = connectedPort.ext.get<AudioPortExt::Tag::device>();
+ connectedDevicePort.device.address = inputDevicePort.device.address;
+ LOG(DEBUG) << __func__ << ": device port " << connectedPort.id << " device set to "
+ << connectedDevicePort.device.toString();
+ // Check if there is already a connected port with for the same external device.
+ for (auto connectedPortId : mConnectedDevicePorts) {
+ auto connectedPortIt = findById<AudioPort>(ports, connectedPortId);
+ if (connectedPortIt->ext.get<AudioPortExt::Tag::device>().device ==
+ connectedDevicePort.device) {
+ LOG(ERROR) << __func__ << ": device " << connectedDevicePort.device.toString()
+ << " is already connected at the device port id " << connectedPortId;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ }
+ }
+
+ if (!mDebug.simulateDeviceConnections) {
+ // In a real HAL here we would attempt querying the profiles from the device.
+ LOG(ERROR) << __func__ << ": failed to query supported device profiles";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ connectedPort.id = ++getConfig().nextPortId;
+ mConnectedDevicePorts.insert(connectedPort.id);
+ LOG(DEBUG) << __func__ << ": template port " << templateId << " external device connected, "
+ << "connected port ID " << connectedPort.id;
+ auto& connectedProfiles = getConfig().connectedProfiles;
+ if (auto connectedProfilesIt = connectedProfiles.find(templateId);
+ connectedProfilesIt != connectedProfiles.end()) {
+ connectedPort.profiles = connectedProfilesIt->second;
+ }
+ ports.push_back(connectedPort);
+ *_aidl_return = std::move(connectedPort);
+
+ std::vector<AudioRoute> newRoutes;
+ auto& routes = getConfig().routes;
+ for (auto& r : routes) {
+ if (r.sinkPortId == templateId) {
+ AudioRoute newRoute;
+ newRoute.sourcePortIds = r.sourcePortIds;
+ newRoute.sinkPortId = connectedPort.id;
+ newRoute.isExclusive = r.isExclusive;
+ newRoutes.push_back(std::move(newRoute));
+ } else {
+ auto& srcs = r.sourcePortIds;
+ if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
+ srcs.push_back(connectedPort.id);
+ }
+ }
+ }
+ routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
+ auto& ports = getConfig().ports;
+ auto portIt = findById<AudioPort>(ports, in_portId);
+ if (portIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (portIt->ext.getTag() != AudioPortExt::Tag::device) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a device port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ if (mConnectedDevicePorts.count(in_portId) == 0) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a connected device port";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& configs = getConfig().portConfigs;
+ auto& initials = getConfig().initialConfigs;
+ auto configIt = std::find_if(configs.begin(), configs.end(), [&](const auto& config) {
+ if (config.portId == in_portId) {
+ // Check if the configuration was provided by the client.
+ const auto& initialIt = findById<AudioPortConfig>(initials, config.id);
+ return initialIt == initials.end() || config != *initialIt;
+ }
+ return false;
+ });
+ if (configIt != configs.end()) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " has a non-default config with id "
+ << configIt->id;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ ports.erase(portIt);
+ mConnectedDevicePorts.erase(in_portId);
+ LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
+
+ auto& routes = getConfig().routes;
+ for (auto routesIt = routes.begin(); routesIt != routes.end();) {
+ if (routesIt->sinkPortId == in_portId) {
+ routesIt = routes.erase(routesIt);
+ } else {
+ // Note: the list of sourcePortIds can't become empty because there must
+ // be the id of the template port in the route.
+ erase_if(routesIt->sourcePortIds, [in_portId](auto src) { return src == in_portId; });
+ ++routesIt;
+ }
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus Module::getAudioPatches(std::vector<AudioPatch>* _aidl_return) {
*_aidl_return = getConfig().patches;
LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " patches";
@@ -171,6 +319,23 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
+ std::vector<AudioRoute>* _aidl_return) {
+ auto& ports = getConfig().ports;
+ if (auto portIt = findById<AudioPort>(ports, in_portId); portIt == ports.end()) {
+ LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ auto& routes = getConfig().routes;
+ std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
+ [&](const auto& r) {
+ const auto& srcs = r.sourcePortIds;
+ return r.sinkPortId == in_portId ||
+ std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end();
+ });
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus Module::openInputStream(int32_t in_portConfigId,
const SinkMetadata& in_sinkMetadata,
std::shared_ptr<IStreamIn>* _aidl_return) {
diff --git a/audio/aidl/default/include/core-impl/Configuration.h b/audio/aidl/default/include/core-impl/Configuration.h
index 17e342d..d5cd30b 100644
--- a/audio/aidl/default/include/core-impl/Configuration.h
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -16,6 +16,7 @@
#pragma once
+#include <map>
#include <vector>
#include <aidl/android/hardware/audio/core/AudioPatch.h>
@@ -29,6 +30,9 @@
std::vector<::aidl::android::media::audio::common::AudioPort> ports;
std::vector<::aidl::android::media::audio::common::AudioPortConfig> portConfigs;
std::vector<::aidl::android::media::audio::common::AudioPortConfig> initialConfigs;
+ // Port id -> List of profiles to use when the device port state is set to 'connected'.
+ std::map<int32_t, std::vector<::aidl::android::media::audio::common::AudioProfile>>
+ connectedProfiles;
std::vector<AudioRoute> routes;
std::vector<AudioPatch> patches;
int32_t nextPortId = 1;
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 359626c..81a02ba 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -18,6 +18,7 @@
#include <map>
#include <memory>
+#include <set>
#include <aidl/android/hardware/audio/core/BnModule.h>
@@ -27,6 +28,12 @@
namespace aidl::android::hardware::audio::core {
class Module : public BnModule {
+ ndk::ScopedAStatus setModuleDebug(
+ const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
+ ndk::ScopedAStatus connectExternalDevice(
+ const ::aidl::android::media::audio::common::AudioPort& in_templateIdAndAdditionalData,
+ ::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
+ ndk::ScopedAStatus disconnectExternalDevice(int32_t in_portId) override;
ndk::ScopedAStatus getAudioPatches(std::vector<AudioPatch>* _aidl_return) override;
ndk::ScopedAStatus getAudioPort(
int32_t in_portId,
@@ -37,6 +44,9 @@
ndk::ScopedAStatus getAudioPorts(
std::vector<::aidl::android::media::audio::common::AudioPort>* _aidl_return) override;
ndk::ScopedAStatus getAudioRoutes(std::vector<AudioRoute>* _aidl_return) override;
+ ndk::ScopedAStatus getAudioRoutesForAudioPort(
+ int32_t in_portId,
+ std::vector<::aidl::android::hardware::audio::core::AudioRoute>* _aidl_return) override;
ndk::ScopedAStatus openInputStream(
int32_t in_portConfigId,
const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata,
@@ -63,6 +73,9 @@
void registerPatch(const AudioPatch& patch);
std::unique_ptr<internal::Configuration> mConfig;
+ ModuleDebug mDebug;
+ // ids of ports created at runtime via 'connectExternalDevice'.
+ std::set<int32_t> mConnectedDevicePorts;
Streams mStreams;
// Maps port ids and port config ids to patch ids.
// Multimap because both ports and configs can be used by multiple patches.
diff --git a/audio/aidl/default/include/core-impl/utils.h b/audio/aidl/default/include/core-impl/utils.h
index 7101012..9d06f08 100644
--- a/audio/aidl/default/include/core-impl/utils.h
+++ b/audio/aidl/default/include/core-impl/utils.h
@@ -38,11 +38,11 @@
return oldSize - c.size();
}
-// Erase all the elements in the map that satisfy the provided predicate.
+// Erase all the elements in the container that satisfy the provided predicate.
template <typename C, typename P>
auto erase_if(C& c, P pred) {
auto oldSize = c.size();
- for (auto it = c.begin(), last = c.end(); it != last;) {
+ for (auto it = c.begin(); it != c.end();) {
if (pred(*it)) {
it = c.erase(it);
} else {
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 3faa39a..3f8d088 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -46,14 +46,16 @@
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()) {
+ const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
// Permanently attached device.
if (isInput) {
mAttachedSourceDevicePorts.insert(port.id);
} else {
mAttachedSinkDevicePorts.insert(port.id);
}
+ } else if (port.profiles.empty()) {
+ mExternalDevicePorts.insert(port.id);
}
}
if (!mStatus.isOk()) return;
@@ -62,6 +64,22 @@
mStatus = module->getAudioPortConfigs(&mInitialConfigs);
}
+std::vector<AudioPort> ModuleConfig::getAttachedDevicePorts() const {
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+ return mAttachedSinkDevicePorts.count(port.id) != 0 ||
+ mAttachedSourceDevicePorts.count(port.id) != 0;
+ });
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getExternalDevicePorts() const {
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result),
+ [&](const auto& port) { return mExternalDevicePorts.count(port.id) != 0; });
+ return result;
+}
+
std::vector<AudioPort> ModuleConfig::getInputMixPorts() const {
std::vector<AudioPort> result;
std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) {
@@ -229,6 +247,23 @@
return result;
}
+std::string ModuleConfig::toString() const {
+ std::string result;
+ result.append("Ports: ");
+ result.append(android::internal::ToString(mPorts));
+ result.append("Initial configs: ");
+ result.append(android::internal::ToString(mInitialConfigs));
+ result.append("Attached sink device ports: ");
+ result.append(android::internal::ToString(mAttachedSinkDevicePorts));
+ result.append("Attached source device ports: ");
+ result.append(android::internal::ToString(mAttachedSourceDevicePorts));
+ result.append("External device ports: ");
+ result.append(android::internal::ToString(mExternalDevicePorts));
+ result.append("Routes: ");
+ result.append(android::internal::ToString(mRoutes));
+ return result;
+}
+
static std::vector<AudioPortConfig> combineAudioConfigs(const AudioPort& port,
const AudioProfile& profile) {
std::vector<AudioPortConfig> configs;
@@ -319,10 +354,14 @@
if (singleProfile && !result.empty()) return result;
}
if (resultSizeBefore == result.size()) {
- AudioPortConfig empty;
- empty.portId = devicePort.id;
- empty.ext = devicePort.ext;
- result.push_back(empty);
+ std::copy_if(mInitialConfigs.begin(), mInitialConfigs.end(), std::back_inserter(result),
+ [&](const auto& config) { return config.portId == devicePort.id; });
+ if (resultSizeBefore == result.size()) {
+ AudioPortConfig empty;
+ empty.portId = devicePort.id;
+ empty.ext = devicePort.ext;
+ result.push_back(empty);
+ }
}
if (singleProfile) return result;
}
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index 2e86b97..0e2738b 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -37,6 +37,8 @@
android::binder::Status getStatus() const { return mStatus; }
std::string getError() const { return mStatus.toString8().c_str(); }
+ std::vector<android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
+ std::vector<android::media::audio::common::AudioPort> getExternalDevicePorts() const;
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 {
@@ -59,6 +61,10 @@
std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
std::vector<SrcSinkGroup> getRoutableSrcSinkGroups(bool isInput) const;
+ std::vector<android::media::audio::common::AudioPortConfig>
+ getPortConfigsForAttachedDevicePorts() const {
+ return generateAudioDevicePortConfigs(getAttachedDevicePorts(), false);
+ }
std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts() const {
auto inputs = generateInputAudioMixPortConfigs(getInputMixPorts(), false);
auto outputs = generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
@@ -98,15 +104,18 @@
}
}
+ std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForDevicePort(
+ const android::media::audio::common::AudioPort& port) const {
+ return generateAudioDevicePortConfigs({port}, false);
+ }
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();
}
+ std::string toString() const;
+
private:
std::vector<android::media::audio::common::AudioPortConfig> generateInputAudioMixPortConfigs(
const std::vector<android::media::audio::common::AudioPort>& ports,
@@ -117,7 +126,8 @@
// 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.
+ // port, its initial configs are looked up, if there are none,
+ // then an empty config is used, assuming further negotiation via setAudioPortConfig.
std::vector<android::media::audio::common::AudioPortConfig> generateAudioDevicePortConfigs(
const std::vector<android::media::audio::common::AudioPort>& ports,
bool singleProfile) const;
@@ -127,5 +137,6 @@
std::vector<android::media::audio::common::AudioPortConfig> mInitialConfigs;
std::set<int32_t> mAttachedSinkDevicePorts;
std::set<int32_t> mAttachedSourceDevicePorts;
+ std::set<int32_t> mExternalDevicePorts;
std::vector<android::hardware::audio::core::AudioRoute> mRoutes;
};
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
index cadeb0c..824ac8d 100644
--- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -22,6 +22,9 @@
#include <set>
#include <string>
+#define LOG_TAG "VtsHalAudioCore"
+#include <android-base/logging.h>
+
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <android-base/properties.h>
@@ -45,9 +48,12 @@
using android::hardware::audio::core::IModule;
using android::hardware::audio::core::IStreamIn;
using android::hardware::audio::core::IStreamOut;
+using android::hardware::audio::core::ModuleDebug;
using android::media::audio::common::AudioContentType;
using android::media::audio::common::AudioDevice;
+using android::media::audio::common::AudioDeviceAddress;
using android::media::audio::common::AudioDeviceType;
+using android::media::audio::common::AudioFormatType;
using android::media::audio::common::AudioIoFlags;
using android::media::audio::common::AudioOutputFlags;
using android::media::audio::common::AudioPort;
@@ -63,7 +69,7 @@
}
template <typename C>
-std::vector<int32_t> getNonExistentIds(const C& allIds) {
+std::vector<int32_t> GetNonExistentIds(const C& allIds) {
if (allIds.empty()) {
return std::vector<int32_t>{-1, 0, 1};
}
@@ -73,6 +79,12 @@
return nonExistentIds;
}
+AudioDeviceAddress GenerateUniqueDeviceAddress() {
+ static int nextId = 1;
+ // TODO: Use connection-specific ID.
+ return AudioDeviceAddress::make<AudioDeviceAddress::Tag::id>(std::to_string(++nextId));
+}
+
struct AidlDeathRecipient : IBinder::DeathRecipient {
std::mutex mutex;
std::condition_variable condition;
@@ -107,70 +119,28 @@
return false;
}
-class AudioCoreModule : public testing::TestWithParam<std::string> {
+class WithDebugFlags {
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();
+ WithDebugFlags() {}
+ explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
+ explicit WithDebugFlags(const WithDebugFlags& initial)
+ : mInitial(initial.mFlags), mFlags(initial.mFlags) {}
+ ~WithDebugFlags() {
+ if (mModule != nullptr) {
+ Status status = mModule->setModuleDebug(mInitial);
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
}
}
+ void SetUp(IModule* module) {
+ Status status = module->setModuleDebug(mFlags);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ ModuleDebug& flags() { return mFlags; }
- sp<IModule> module;
- sp<AidlDeathRecipient> deathHandler;
- std::unique_ptr<ModuleConfig> moduleConfig;
+ private:
+ ModuleDebug mInitial;
+ ModuleDebug mFlags;
+ IModule* mModule = nullptr;
};
// For consistency, WithAudioPortConfig can start both with a non-existent
@@ -226,6 +196,147 @@
AudioPortConfig mConfig;
};
+class AudioCoreModule : public testing::TestWithParam<std::string> {
+ public:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(ConnectToService());
+ debug.flags().simulateDeviceConnections = true;
+ ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
+ }
+
+ void TearDown() override {
+ if (module != nullptr) {
+ Status status = module->setModuleDebug(ModuleDebug{});
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << " returned when resetting debug flags";
+ }
+ }
+
+ 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());
+ }
+
+ void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
+ for (const auto& config : configs) {
+ ASSERT_NE(0, config.portId);
+ WithAudioPortConfig portConfig(config);
+ ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get())); // calls setAudioPortConfig
+ EXPECT_EQ(config.portId, portConfig.get().portId);
+ std::vector<AudioPortConfig> retrievedPortConfigs;
+ 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 getAudioPortConfigs";
+ if (configIt != retrievedPortConfigs.end()) {
+ EXPECT_EQ(portConfig.get(), *configIt)
+ << "Applied port config returned by setAudioPortConfig: "
+ << portConfig.get().toString()
+ << " is not the same as retrieved via getAudioPortConfigs: "
+ << configIt->toString();
+ }
+ }
+ }
+
+ template <typename Entity>
+ void GetAllEntityIds(std::set<int32_t>* entityIds,
+ 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;
+ WithDebugFlags debug;
+};
+
+class WithDevicePortConnectedState {
+ public:
+ explicit WithDevicePortConnectedState(const AudioPort& idAndData) : mIdAndData(idAndData) {}
+ WithDevicePortConnectedState(const AudioPort& id, const AudioDeviceAddress& address)
+ : mIdAndData(setAudioPortAddress(id, address)) {}
+ ~WithDevicePortConnectedState() {
+ if (mModule != nullptr) {
+ Status status = mModule->disconnectExternalDevice(getId());
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << " returned when disconnecting device port ID " << getId();
+ }
+ }
+ void SetUp(IModule* module) {
+ Status status = module->connectExternalDevice(mIdAndData, &mConnectedPort);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << " returned when connecting device port ID & data "
+ << mIdAndData.toString();
+ ASSERT_NE(mIdAndData.id, getId())
+ << "ID of the connected port must not be the same as the ID of the template port";
+ mModule = module;
+ }
+ int32_t getId() const { return mConnectedPort.id; }
+ const AudioPort& get() { return mConnectedPort; }
+
+ private:
+ static AudioPort setAudioPortAddress(const AudioPort& id, const AudioDeviceAddress& address) {
+ AudioPort result = id;
+ result.ext.get<AudioPortExt::Tag::device>().device.address = address;
+ return result;
+ }
+
+ const AudioPort mIdAndData;
+ IModule* mModule = nullptr;
+ AudioPort mConnectedPort;
+};
+
template <typename Stream>
class WithStream {
public:
@@ -332,7 +443,7 @@
ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
}
-TEST_P(AudioCoreModule, GetAudioPortsIsStatic) {
+TEST_P(AudioCoreModule, GetAudioPortsIsStable) {
std::vector<AudioPort> ports1;
{
Status status = module->getAudioPorts(&ports1);
@@ -344,13 +455,13 @@
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";
+ << "Sizes of audio port arrays do not match across consequent calls to getAudioPorts";
std::sort(ports1.begin(), ports1.end());
std::sort(ports2.begin(), ports2.end());
EXPECT_EQ(ports1, ports2);
}
-TEST_P(AudioCoreModule, GetAudioRoutesIsStatic) {
+TEST_P(AudioCoreModule, GetAudioRoutesIsStable) {
std::vector<AudioRoute> routes1;
{
Status status = module->getAudioRoutes(&routes1);
@@ -362,7 +473,7 @@
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";
+ << "Sizes of audio route arrays do not match across consequent calls to getAudioRoutes";
std::sort(routes1.begin(), routes1.end());
std::sort(routes2.begin(), routes2.end());
EXPECT_EQ(routes1, routes2);
@@ -401,6 +512,32 @@
}
}
+TEST_P(AudioCoreModule, GetAudioRoutesForAudioPort) {
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ if (portIds.empty()) {
+ GTEST_SKIP() << "No ports in the module.";
+ }
+ for (const auto portId : portIds) {
+ std::vector<AudioRoute> routes;
+ Status status = module->getAudioRoutesForAudioPort(portId, &routes);
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ for (const auto& r : routes) {
+ if (r.sinkPortId != portId) {
+ const auto& srcs = r.sourcePortIds;
+ EXPECT_TRUE(std::find(srcs.begin(), srcs.end(), portId) != srcs.end())
+ << " port ID " << portId << " does not used by the route " << r.toString();
+ }
+ }
+ }
+ for (const auto portId : GetNonExistentIds(portIds)) {
+ std::vector<AudioRoute> routes;
+ Status status = module->getAudioRoutesForAudioPort(portId, &routes);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for port ID " << portId;
+ }
+}
+
TEST_P(AudioCoreModule, CheckDevicePorts) {
std::vector<AudioPort> ports;
{
@@ -486,7 +623,7 @@
EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
EXPECT_EQ(portId, port.id);
}
- for (const auto portId : getNonExistentIds(portIds)) {
+ for (const auto portId : GetNonExistentIds(portIds)) {
AudioPort port;
Status status = module->getAudioPort(portId, &port);
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
@@ -494,10 +631,59 @@
}
}
+// Verify that HAL module reports for a connected device port at least one non-dynamic profile,
+// that is, a profile with actual supported configuration.
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ AudioPort portWithData = port;
+ portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+ GenerateUniqueDeviceAddress();
+ WithDevicePortConnectedState portConnected(portWithData);
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ const int32_t connectedPortId = portConnected.getId();
+ ASSERT_NE(portWithData.id, connectedPortId);
+ ASSERT_EQ(portWithData.ext.getTag(), portConnected.get().ext.getTag());
+ EXPECT_EQ(portWithData.ext.get<AudioPortExt::Tag::device>().device,
+ portConnected.get().ext.get<AudioPortExt::Tag::device>().device);
+ // Verify that 'getAudioPort' and 'getAudioPorts' return the same connected port.
+ AudioPort connectedPort;
+ Status status = module->getAudioPort(connectedPortId, &connectedPort);
+ EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << " returned for getAudioPort port ID " << connectedPortId;
+ EXPECT_EQ(portConnected.get(), connectedPort);
+ const auto& portProfiles = connectedPort.profiles;
+ EXPECT_NE(0, portProfiles.size())
+ << "Connected port has no profiles: " << connectedPort.toString();
+ const auto dynamicProfileIt =
+ std::find_if(portProfiles.begin(), portProfiles.end(), [](const auto& profile) {
+ return profile.format.type == AudioFormatType::DEFAULT;
+ });
+ EXPECT_EQ(portProfiles.end(), dynamicProfileIt) << "Connected port contains dynamic "
+ << "profiles: " << connectedPort.toString();
+
+ std::vector<AudioPort> allPorts;
+ {
+ Status status = module->getAudioPorts(&allPorts);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ const auto allPortsIt = findById(allPorts, connectedPortId);
+ EXPECT_NE(allPorts.end(), allPortsIt);
+ if (allPortsIt != allPorts.end()) {
+ EXPECT_EQ(portConnected.get(), *allPortsIt);
+ }
+ }
+}
+
TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
std::set<int32_t> portConfigIds;
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
- for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+ for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
{
sp<IStreamIn> stream;
Status status = module->openInputStream(portConfigId, {}, &stream);
@@ -537,7 +723,7 @@
TEST_P(AudioCoreModule, ResetAudioPortConfigInvalidId) {
std::set<int32_t> portConfigIds;
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
- for (const auto portConfigId : getNonExistentIds(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;
@@ -608,39 +794,35 @@
EXPECT_EQ(suggestedConfig.flags.value(), appliedConfig.flags.value());
}
+TEST_P(AudioCoreModule, SetAllAttachedDevicePortConfigs) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForAttachedDevicePorts()));
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, SetAllExternalDevicePortConfigs) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ ASSERT_NO_FATAL_FAILURE(
+ ApplyEveryConfig(moduleConfig->getPortConfigsForDevicePort(portConnected.get())));
+ }
+}
+
TEST_P(AudioCoreModule, SetAllStaticAudioPortConfigs) {
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
- 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();
- }
- }
+ ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForMixPorts()));
}
TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) {
std::set<int32_t> portIds;
ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
- for (const auto portId : getNonExistentIds(portIds)) {
+ for (const auto portId : GetNonExistentIds(portIds)) {
AudioPortConfig portConfig, suggestedConfig;
bool applied;
portConfig.portId = portId;
@@ -656,7 +838,7 @@
TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) {
std::set<int32_t> portConfigIds;
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
- for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
+ for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
AudioPortConfig portConfig, suggestedConfig;
bool applied;
portConfig.id = portConfigId;
@@ -669,6 +851,203 @@
}
}
+TEST_P(AudioCoreModule, TryConnectMissingDevice) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ AudioPort ignored;
+ WithDebugFlags doNotSimulateConnections(debug);
+ doNotSimulateConnections.flags().simulateDeviceConnections = false;
+ ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
+ for (const auto& port : ports) {
+ AudioPort portWithData = port;
+ portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+ GenerateUniqueDeviceAddress();
+ Status status = module->connectExternalDevice(portWithData, &ignored);
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " returned for static port " << portWithData.toString();
+ }
+}
+
+TEST_P(AudioCoreModule, TryChangingConnectionSimulationMidway) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ ModuleDebug midwayDebugChange = debug.flags();
+ midwayDebugChange.simulateDeviceConnections = false;
+ Status status = module->setModuleDebug(midwayDebugChange);
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " returned when trying to disable connections simulation "
+ << "while having a connected device";
+}
+
+TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceInvalidPorts) {
+ AudioPort ignored;
+ std::set<int32_t> portIds;
+ ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
+ for (const auto portId : GetNonExistentIds(portIds)) {
+ AudioPort invalidPort;
+ invalidPort.id = portId;
+ Status status = module->connectExternalDevice(invalidPort, &ignored);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for port ID " << portId << " when setting CONNECTED state";
+ status = module->disconnectExternalDevice(portId);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for port ID " << portId
+ << " when setting DISCONNECTED state";
+ }
+
+ std::vector<AudioPort> ports;
+ {
+ Status status = module->getAudioPorts(&ports);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ for (const auto& port : ports) {
+ if (port.ext.getTag() != AudioPortExt::Tag::device) {
+ Status status = module->connectExternalDevice(port, &ignored);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for non-device port ID " << port.id
+ << " when setting CONNECTED state";
+ status = module->disconnectExternalDevice(port.id);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for non-device port ID " << port.id
+ << " when setting DISCONNECTED state";
+ } else {
+ const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
+ if (devicePort.device.type.connection.empty()) {
+ Status status = module->connectExternalDevice(port, &ignored);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for permanently attached device port ID " << port.id
+ << " when setting CONNECTED state";
+ status = module->disconnectExternalDevice(port.id);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned for permanently attached device port ID " << port.id
+ << " when setting DISCONNECTED state";
+ }
+ }
+ }
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceTwice) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ AudioPort ignored;
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ Status status = module->disconnectExternalDevice(port.id);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned when disconnecting already disconnected device port ID "
+ << port.id;
+ AudioPort portWithData = port;
+ portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+ GenerateUniqueDeviceAddress();
+ WithDevicePortConnectedState portConnected(portWithData);
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ status = module->connectExternalDevice(portConnected.get(), &ignored);
+ EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned when trying to connect a connected device port "
+ << portConnected.get().toString();
+ status = module->connectExternalDevice(portWithData, &ignored);
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " returned when connecting again the external device "
+ << portWithData.ext.get<AudioPortExt::Tag::device>().device.toString();
+ if (status.exceptionCode() == Status::EX_NONE) {
+ ADD_FAILURE() << "Returned connected port " << ignored.toString() << " for template "
+ << portWithData.toString();
+ }
+ }
+}
+
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, DisconnectExternalDeviceNonResetPortConfig) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ const auto portConfig = moduleConfig->getSingleConfigForDevicePort(portConnected.get());
+ {
+ WithAudioPortConfig config(portConfig);
+ // Note: if SetUp fails, check the status of 'GetAudioPortWithExternalDevices' test.
+ // Our test assumes that 'getAudioPort' returns at least one profile, and it
+ // is not a dynamic profile.
+ ASSERT_NO_FATAL_FAILURE(config.SetUp(module.get()));
+ Status status = module->disconnectExternalDevice(portConnected.getId());
+ EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
+ << status << " returned when trying to disconnect device port ID " << port.id
+ << " with active configuration " << config.getId();
+ }
+ }
+}
+
+TEST_P(AudioCoreModule, ExternalDevicePortRoutes) {
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
+ if (ports.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : ports) {
+ std::vector<AudioRoute> routesBefore;
+ {
+ Status status = module->getAudioRoutes(&routesBefore);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+
+ int32_t connectedPortId;
+ {
+ WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ connectedPortId = portConnected.getId();
+ std::vector<AudioRoute> connectedPortRoutes;
+ {
+ Status status =
+ module->getAudioRoutesForAudioPort(connectedPortId, &connectedPortRoutes);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
+ << status << " returned when retrieving routes for connected port id "
+ << connectedPortId;
+ }
+ // There must be routes for the port to be useful.
+ if (connectedPortRoutes.empty()) {
+ std::vector<AudioRoute> allRoutes;
+ Status status = module->getAudioRoutes(&allRoutes);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ ADD_FAILURE() << " no routes returned for the connected port "
+ << portConnected.get().toString()
+ << "; all routes: " << android::internal::ToString(allRoutes);
+ }
+ }
+ std::vector<AudioRoute> ignored;
+ Status status = module->getAudioRoutesForAudioPort(connectedPortId, &ignored);
+ ASSERT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
+ << status << " returned when retrieving routes for released connected port id "
+ << connectedPortId;
+
+ std::vector<AudioRoute> routesAfter;
+ {
+ Status status = module->getAudioRoutes(&routesAfter);
+ ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
+ }
+ ASSERT_EQ(routesBefore.size(), routesAfter.size())
+ << "Sizes of audio route arrays do not match after creating and "
+ << "releasing a connected port";
+ std::sort(routesBefore.begin(), routesBefore.end());
+ std::sort(routesAfter.begin(), routesAfter.end());
+ EXPECT_EQ(routesBefore, routesAfter);
+ }
+}
+
template <typename Stream>
class AudioStream : public AudioCoreModule {
public:
@@ -899,7 +1278,7 @@
std::set<int32_t> portConfigIds;
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
- for (const auto portConfigId : getNonExistentIds(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,
@@ -969,7 +1348,7 @@
// 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)) {
+ for (const auto patchId : GetNonExistentIds(patchIds)) {
AudioPatch patchWithNonExistendId = patch.get();
patchWithNonExistendId.id = patchId;
Status status = module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId);
@@ -995,7 +1374,7 @@
TEST_P(AudioModulePatch, ResetInvalidPatchId) {
std::set<int32_t> patchIds;
ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
- for (const auto patchId : getNonExistentIds(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;