Merge "audio HAL: Support for external device connections"
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;