audio: Clarify profiles management for external devices
Clarify what should happen to mix port profiles after
connection of an external device. Add a test to verify
this behavior.
Also, add an XML file for the test runner for
VtsHalAudioCoreTargetTest.
Bug: 273252382
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I3381dd29c5922bf31fa3a8ae6fa273597e8333a1
Merged-In: I3381dd29c5922bf31fa3a8ae6fa273597e8333a1
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
index 7830501..7622d9a 100644
--- a/audio/aidl/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -192,6 +192,19 @@
* device address is specified for a point-to-multipoint external device
* connection.
*
+ * Since not all modules have a DSP that could perform sample rate and
+ * format conversions, behavior related to mix port configurations may vary.
+ * For modules with a DSP, mix ports can be pre-configured and have a fixed
+ * set of audio profiles supported by the DSP. For modules without a DSP,
+ * audio profiles of mix ports may change after connecting an external
+ * device. The typical case is that the mix port has an empty set of
+ * profiles when no external devices are connected, and after external
+ * device connection it receives the same set of profiles as the device
+ * ports that they can be routed to. The client will re-query current port
+ * configurations using 'getAudioPorts'. All mix ports that can be routed to
+ * the connected device port must have a non-empty set of audio profiles
+ * after successful connection of an external device.
+ *
* 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'
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 71dc459..6b417a4 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -456,38 +456,45 @@
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);
+ for (auto connectedPortPair : mConnectedDevicePorts) {
+ auto connectedPortIt = findById<AudioPort>(ports, connectedPortPair.first);
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;
+ << " is already connected at the device port id "
+ << connectedPortPair.first;
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";
- // TODO: Check the return value when it is ready for actual devices.
- populateConnectedDevicePort(&connectedPort);
+ if (ndk::ScopedAStatus status = populateConnectedDevicePort(&connectedPort);
+ !status.isOk()) {
+ return status;
+ }
+ } else {
+ auto& connectedProfiles = getConfig().connectedProfiles;
+ if (auto connectedProfilesIt = connectedProfiles.find(templateId);
+ connectedProfilesIt != connectedProfiles.end()) {
+ connectedPort.profiles = connectedProfilesIt->second;
+ }
+ }
+ if (connectedPort.profiles.empty()) {
+ LOG(ERROR) << "Profiles of a connected port still empty after connecting external device "
+ << connectedPort.toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
connectedPort.id = ++getConfig().nextPortId;
- mConnectedDevicePorts.insert(connectedPort.id);
+ auto [connectedPortsIt, _] =
+ mConnectedDevicePorts.insert(std::pair(connectedPort.id, std::vector<int32_t>()));
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);
onExternalDeviceConnectionChanged(connectedPort, true /*connected*/);
- *_aidl_return = std::move(connectedPort);
+ std::vector<int32_t> routablePortIds;
std::vector<AudioRoute> newRoutes;
auto& routes = getConfig().routes;
for (auto& r : routes) {
@@ -497,15 +504,30 @@
newRoute.sinkPortId = connectedPort.id;
newRoute.isExclusive = r.isExclusive;
newRoutes.push_back(std::move(newRoute));
+ routablePortIds.insert(routablePortIds.end(), r.sourcePortIds.begin(),
+ r.sourcePortIds.end());
} else {
auto& srcs = r.sourcePortIds;
if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
srcs.push_back(connectedPort.id);
+ routablePortIds.push_back(r.sinkPortId);
}
}
}
routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
+ // Note: this is a simplistic approach assuming that a mix port can only be populated
+ // from a single device port. Implementing support for stuffing dynamic profiles with a superset
+ // of all profiles from all routable dynamic device ports would be more involved.
+ for (const auto mixPortId : routablePortIds) {
+ auto portsIt = findById<AudioPort>(ports, mixPortId);
+ if (portsIt != ports.end() && portsIt->profiles.empty()) {
+ portsIt->profiles = connectedPort.profiles;
+ connectedPortsIt->second.push_back(portsIt->id);
+ }
+ }
+ *_aidl_return = std::move(connectedPort);
+
return ndk::ScopedAStatus::ok();
}
@@ -520,7 +542,8 @@
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) {
+ auto connectedPortsIt = mConnectedDevicePorts.find(in_portId);
+ if (connectedPortsIt == mConnectedDevicePorts.end()) {
LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a connected device port";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
@@ -541,7 +564,6 @@
}
onExternalDeviceConnectionChanged(*portIt, false /*connected*/);
ports.erase(portIt);
- mConnectedDevicePorts.erase(in_portId);
LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
auto& routes = getConfig().routes;
@@ -556,6 +578,14 @@
}
}
+ for (const auto mixPortId : connectedPortsIt->second) {
+ auto mixPortIt = findById<AudioPort>(ports, mixPortId);
+ if (mixPortIt != ports.end()) {
+ mixPortIt->profiles = {};
+ }
+ }
+ mConnectedDevicePorts.erase(connectedPortsIt);
+
return ndk::ScopedAStatus::ok();
}
diff --git a/audio/aidl/default/include/core-impl/Configuration.h b/audio/aidl/default/include/core-impl/Configuration.h
index 4dd0133..70320e4 100644
--- a/audio/aidl/default/include/core-impl/Configuration.h
+++ b/audio/aidl/default/include/core-impl/Configuration.h
@@ -33,7 +33,8 @@
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'.
+ // Port id -> List of profiles to use when the device port state is set to 'connected'
+ // in connection simulation mode.
std::map<int32_t, std::vector<::aidl::android::media::audio::common::AudioProfile>>
connectedProfiles;
std::vector<AudioRoute> routes;
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 2cbda7d..83ecfaa 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -177,8 +177,10 @@
ChildInterface<IBluetooth> mBluetooth;
ChildInterface<IBluetoothA2dp> mBluetoothA2dp;
ChildInterface<IBluetoothLe> mBluetoothLe;
- // ids of ports created at runtime via 'connectExternalDevice'.
- std::set<int32_t> mConnectedDevicePorts;
+ // ids of device ports created at runtime via 'connectExternalDevice'.
+ // Also stores ids of mix ports with dynamic profiles which got populated from the connected
+ // port.
+ std::map<int32_t, std::vector<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/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index cab1671..0ae8cfc 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -1780,6 +1780,42 @@
}
}
+// Note: This test relies on simulation of external device connections by the HAL module.
+TEST_P(AudioCoreModule, ExternalDeviceMixPortConfigs) {
+ // After an external device has been connected, all mix ports that can be routed
+ // to the device port for the connected device must have non-empty profiles.
+ ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
+ std::vector<AudioPort> externalDevicePorts = moduleConfig->getExternalDevicePorts();
+ if (externalDevicePorts.empty()) {
+ GTEST_SKIP() << "No external devices in the module.";
+ }
+ for (const auto& port : externalDevicePorts) {
+ WithDevicePortConnectedState portConnected(GenerateUniqueDeviceAddress(port));
+ ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+ std::vector<AudioRoute> routes;
+ ASSERT_IS_OK(module->getAudioRoutesForAudioPort(portConnected.getId(), &routes));
+ std::vector<AudioPort> allPorts;
+ ASSERT_IS_OK(module->getAudioPorts(&allPorts));
+ for (const auto& r : routes) {
+ if (r.sinkPortId == portConnected.getId()) {
+ for (const auto& srcPortId : r.sourcePortIds) {
+ const auto srcPortIt = findById(allPorts, srcPortId);
+ ASSERT_NE(allPorts.end(), srcPortIt) << "port ID " << srcPortId;
+ EXPECT_NE(0UL, srcPortIt->profiles.size())
+ << " source port " << srcPortIt->toString() << " must have its profiles"
+ << " populated following external device connection";
+ }
+ } else {
+ const auto sinkPortIt = findById(allPorts, r.sinkPortId);
+ ASSERT_NE(allPorts.end(), sinkPortIt) << "port ID " << r.sinkPortId;
+ EXPECT_NE(0UL, sinkPortIt->profiles.size())
+ << " source port " << sinkPortIt->toString() << " must have its"
+ << " profiles populated following external device connection";
+ }
+ }
+ }
+}
+
TEST_P(AudioCoreModule, MasterMute) {
bool isSupported = false;
EXPECT_NO_FATAL_FAILURE(TestAccessors<bool>(module.get(), &IModule::getMasterMute,
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml b/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml
new file mode 100644
index 0000000..dfc1039
--- /dev/null
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs VtsHalAudioCoreTargetTest.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.StopServicesSetup"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop vts.native_server.on 1"/>
+ <option name="teardown-command" value="setprop vts.native_server.on 0"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="VtsHalAudioCoreTargetTest" />
+ <option name="native-test-timeout" value="10m" />
+ </test>
+</configuration>