Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4ee28d73 authored by Shunkai Yao's avatar Shunkai Yao Committed by Automerger Merge Worker
Browse files

Merge changes from topic "fix-b-273252382-connect-external-device" am: ddd48982 am: 73454c52

parents bfb7cb94 73454c52
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -192,6 +192,19 @@ interface IModule {
     * 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'
+46 −16
Original line number Diff line number Diff line
@@ -456,38 +456,45 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA
        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);
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
        if (ndk::ScopedAStatus status = populateConnectedDevicePort(&connectedPort);
            !status.isOk()) {
            return status;
        }

    connectedPort.id = ++getConfig().nextPortId;
    mConnectedDevicePorts.insert(connectedPort.id);
    LOG(DEBUG) << __func__ << ": template port " << templateId << " external device connected, "
               << "connected port ID " << connectedPort.id;
    } 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;
    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;
    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 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA
            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 @@ ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
        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 @@ ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
    }
    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 @@ ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
        }
    }

    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();
}

+2 −1
Original line number Diff line number Diff line
@@ -33,7 +33,8 @@ struct Configuration {
    std::vector<::aidl::android::media::audio::common::AudioPort> ports;
    std::vector<::aidl::android::media::audio::common::AudioPortConfig> portConfigs;
    std::vector<::aidl::android::media::audio::common::AudioPortConfig> initialConfigs;
    // Port id -> List of profiles to use when the device port state is set to 'connected'.
    // 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;
+4 −2
Original line number Diff line number Diff line
@@ -177,8 +177,10 @@ class Module : public BnModule {
    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.
+36 −0
Original line number Diff line number Diff line
@@ -1780,6 +1780,42 @@ TEST_P(AudioCoreModule, ExternalDevicePortRoutes) {
    }
}

// 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,
Loading