/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_TAG "VtsHalAudioCore.Module" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AudioHalBinderServiceUtil.h" #include "ModuleConfig.h" #include "TestUtils.h" using namespace android; using aidl::android::hardware::audio::common::PlaybackTrackMetadata; using aidl::android::hardware::audio::common::RecordTrackMetadata; using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::hardware::audio::core::AudioMode; using aidl::android::hardware::audio::core::AudioPatch; using aidl::android::hardware::audio::core::AudioRoute; using aidl::android::hardware::audio::core::IModule; using aidl::android::hardware::audio::core::ISoundDose; using aidl::android::hardware::audio::core::IStreamCommon; using aidl::android::hardware::audio::core::IStreamIn; using aidl::android::hardware::audio::core::IStreamOut; using aidl::android::hardware::audio::core::ITelephony; using aidl::android::hardware::audio::core::MicrophoneDynamicInfo; using aidl::android::hardware::audio::core::MicrophoneInfo; using aidl::android::hardware::audio::core::ModuleDebug; using aidl::android::hardware::audio::core::StreamDescriptor; using aidl::android::hardware::audio::core::VendorParameter; using aidl::android::hardware::common::fmq::SynchronizedReadWrite; using aidl::android::media::audio::common::AudioContentType; using aidl::android::media::audio::common::AudioDevice; using aidl::android::media::audio::common::AudioDeviceAddress; using aidl::android::media::audio::common::AudioDeviceType; using aidl::android::media::audio::common::AudioFormatType; using aidl::android::media::audio::common::AudioIoFlags; using aidl::android::media::audio::common::AudioOutputFlags; using aidl::android::media::audio::common::AudioPort; using aidl::android::media::audio::common::AudioPortConfig; using aidl::android::media::audio::common::AudioPortDeviceExt; using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::AudioSource; using aidl::android::media::audio::common::AudioUsage; using aidl::android::media::audio::common::Void; using android::hardware::audio::common::isBitPositionFlagSet; using android::hardware::audio::common::isTelephonyDeviceType; using android::hardware::audio::common::StreamLogic; using android::hardware::audio::common::StreamWorker; using ndk::enum_range; using ndk::ScopedAStatus; template auto findById(std::vector& v, int32_t id) { return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; }); } template std::vector GetNonExistentIds(const C& allIds) { if (allIds.empty()) { return std::vector{-1, 0, 1}; } std::vector nonExistentIds; nonExistentIds.push_back(*std::min_element(allIds.begin(), allIds.end()) - 1); nonExistentIds.push_back(*std::max_element(allIds.begin(), allIds.end()) + 1); return nonExistentIds; } AudioDeviceAddress GenerateUniqueDeviceAddress() { static int nextId = 1; // TODO: Use connection-specific ID. return AudioDeviceAddress::make(std::to_string(++nextId)); } // All 'With*' classes are move-only because they are associated with some // resource or state of a HAL module. class WithDebugFlags { public: static WithDebugFlags createNested(const WithDebugFlags& parent) { return WithDebugFlags(parent.mFlags); } WithDebugFlags() {} explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {} WithDebugFlags(const WithDebugFlags&) = delete; WithDebugFlags& operator=(const WithDebugFlags&) = delete; ~WithDebugFlags() { if (mModule != nullptr) { EXPECT_IS_OK(mModule->setModuleDebug(mInitial)); } } void SetUp(IModule* module) { ASSERT_IS_OK(module->setModuleDebug(mFlags)); } ModuleDebug& flags() { return mFlags; } private: ModuleDebug mInitial; ModuleDebug mFlags; IModule* mModule = nullptr; }; // For consistency, WithAudioPortConfig can start both with a non-existent // port config, and with an existing one. Existence is determined by the // id of the provided config. If it's not 0, then WithAudioPortConfig is // essentially a no-op wrapper. class WithAudioPortConfig { public: WithAudioPortConfig() {} explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {} WithAudioPortConfig(const WithAudioPortConfig&) = delete; WithAudioPortConfig& operator=(const WithAudioPortConfig&) = delete; ~WithAudioPortConfig() { if (mModule != nullptr) { EXPECT_IS_OK(mModule->resetAudioPortConfig(getId())) << "port config id " << getId(); } } void SetUp(IModule* module) { ASSERT_NE(AudioPortExt::Tag::unspecified, mInitialConfig.ext.getTag()) << "config: " << mInitialConfig.toString(); // Negotiation is allowed for device ports because the HAL module is // allowed to provide an empty profiles list for attached devices. ASSERT_NO_FATAL_FAILURE( SetUpImpl(module, mInitialConfig.ext.getTag() == AudioPortExt::Tag::device)); } int32_t getId() const { return mConfig.id; } const AudioPortConfig& get() const { return mConfig; } private: void SetUpImpl(IModule* module, bool negotiate) { if (mInitialConfig.id == 0) { AudioPortConfig suggested; bool applied = false; ASSERT_IS_OK(module->setAudioPortConfig(mInitialConfig, &suggested, &applied)) << "Config: " << mInitialConfig.toString(); if (!applied && negotiate) { mInitialConfig = suggested; ASSERT_NO_FATAL_FAILURE(SetUpImpl(module, false)) << " while applying suggested config: " << suggested.toString(); } else { ASSERT_TRUE(applied) << "Suggested: " << suggested.toString(); mConfig = suggested; mModule = module; } } else { mConfig = mInitialConfig; } } AudioPortConfig mInitialConfig; IModule* mModule = nullptr; AudioPortConfig mConfig; }; template void TestAccessors(Instance* inst, Getter getter, Setter setter, const std::vector& validValues, const std::vector& invalidValues, bool* isSupported) { PropType initialValue{}; ScopedAStatus status = (inst->*getter)(&initialValue); if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { *isSupported = false; return; } ASSERT_TRUE(status.isOk()) << "Unexpected status from a getter: " << status; *isSupported = true; for (const auto v : validValues) { EXPECT_IS_OK((inst->*setter)(v)) << "for valid value: " << v; PropType currentValue{}; EXPECT_IS_OK((inst->*getter)(¤tValue)); EXPECT_EQ(v, currentValue); } for (const auto v : invalidValues) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, (inst->*setter)(v)) << "for invalid value: " << v; } EXPECT_IS_OK((inst->*setter)(initialValue)) << "Failed to restore the initial value"; } template void TestGetVendorParameters(Instance* inst, bool* isSupported) { static const std::vector> kIdsLists = {{}, {"zero"}, {"one", "two"}}; static const auto kStatuses = {EX_ILLEGAL_ARGUMENT, EX_ILLEGAL_STATE, EX_UNSUPPORTED_OPERATION}; for (const auto& ids : kIdsLists) { std::vector params; if (ndk::ScopedAStatus status = inst->getVendorParameters(ids, ¶ms); status.isOk()) { EXPECT_EQ(ids.size(), params.size()) << "Size of the returned parameters list must " << "match the size of the provided ids list"; for (const auto& param : params) { EXPECT_NE(ids.end(), std::find(ids.begin(), ids.end(), param.id)) << "Returned parameter id \"" << param.id << "\" is unexpected"; } for (const auto& id : ids) { EXPECT_NE(params.end(), std::find_if(params.begin(), params.end(), [&](const auto& param) { return param.id == id; })) << "Requested parameter with id \"" << id << "\" was not returned"; } } else { EXPECT_STATUS(kStatuses, status); if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { *isSupported = false; return; } } } *isSupported = true; } template void TestSetVendorParameters(Instance* inst, bool* isSupported) { static const auto kStatuses = {EX_NONE, EX_ILLEGAL_ARGUMENT, EX_ILLEGAL_STATE, EX_UNSUPPORTED_OPERATION}; static const std::vector> kParamsLists = { {}, {VendorParameter{"zero"}}, {VendorParameter{"one"}, VendorParameter{"two"}}}; for (const auto& params : kParamsLists) { ndk::ScopedAStatus status = inst->setVendorParameters(params, false); if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { *isSupported = false; return; } EXPECT_STATUS(kStatuses, status) << ::android::internal::ToString(params) << ", async: false"; EXPECT_STATUS(kStatuses, inst->setVendorParameters(params, true)) << ::android::internal::ToString(params) << ", async: true"; } *isSupported = true; } // Can be used as a base for any test here, does not depend on the fixture GTest parameters. class AudioCoreModuleBase { public: // The default buffer size is used mostly for negative tests. static constexpr int kDefaultBufferSizeFrames = 256; void SetUpImpl(const std::string& moduleName) { ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName)); debug.flags().simulateDeviceConnections = true; ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get())); } void TearDownImpl() { if (module != nullptr) { EXPECT_IS_OK(module->setModuleDebug(ModuleDebug{})); } } void ConnectToService(const std::string& moduleName) { module = IModule::fromBinder(binderUtil.connectToService(moduleName)); ASSERT_NE(module, nullptr); } void RestartService() { ASSERT_NE(module, nullptr); moduleConfig.reset(); module = IModule::fromBinder(binderUtil.restartService()); ASSERT_NE(module, nullptr); } void ApplyEveryConfig(const std::vector& 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 retrievedPortConfigs; ASSERT_IS_OK(module->getAudioPortConfigs(&retrievedPortConfigs)); 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 void GetAllEntityIds(std::set* entityIds, ScopedAStatus (IModule::*getter)(std::vector*), const std::string& errorMessage) { std::vector entities; { ASSERT_IS_OK((module.get()->*getter)(&entities)); } 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* patchIds) { return GetAllEntityIds( patchIds, &IModule::getAudioPatches, "IDs of audio patches returned by IModule.getAudioPatches are not unique"); } void GetAllPortIds(std::set* portIds) { return GetAllEntityIds( portIds, &IModule::getAudioPorts, "IDs of audio ports returned by IModule.getAudioPorts are not unique"); } void GetAllPortConfigIds(std::set* portConfigIds) { return GetAllEntityIds( portConfigIds, &IModule::getAudioPortConfigs, "IDs of audio port configs returned by IModule.getAudioPortConfigs are not unique"); } void SetUpModuleConfig() { if (moduleConfig == nullptr) { moduleConfig = std::make_unique(module.get()); ASSERT_EQ(EX_NONE, moduleConfig->getStatus().getExceptionCode()) << "ModuleConfig init error: " << moduleConfig->getError(); } } std::shared_ptr module; std::unique_ptr moduleConfig; AudioHalBinderServiceUtil binderUtil; WithDebugFlags debug; }; class AudioCoreModule : public AudioCoreModuleBase, public testing::TestWithParam { public: void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam())); } void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); } }; class WithDevicePortConnectedState { public: explicit WithDevicePortConnectedState(const AudioPort& idAndData) : mIdAndData(idAndData) {} WithDevicePortConnectedState(const AudioPort& id, const AudioDeviceAddress& address) : mIdAndData(setAudioPortAddress(id, address)) {} WithDevicePortConnectedState(const WithDevicePortConnectedState&) = delete; WithDevicePortConnectedState& operator=(const WithDevicePortConnectedState&) = delete; ~WithDevicePortConnectedState() { if (mModule != nullptr) { EXPECT_IS_OK(mModule->disconnectExternalDevice(getId())) << "when disconnecting device port ID " << getId(); } } void SetUp(IModule* module) { ASSERT_IS_OK(module->connectExternalDevice(mIdAndData, &mConnectedPort)) << "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().device.address = address; return result; } const AudioPort mIdAndData; IModule* mModule = nullptr; AudioPort mConnectedPort; }; class StreamContext { public: typedef AidlMessageQueue CommandMQ; typedef AidlMessageQueue ReplyMQ; typedef AidlMessageQueue DataMQ; explicit StreamContext(const StreamDescriptor& descriptor) : mFrameSizeBytes(descriptor.frameSizeBytes), mCommandMQ(new CommandMQ(descriptor.command)), mReplyMQ(new ReplyMQ(descriptor.reply)), mBufferSizeFrames(descriptor.bufferSizeFrames), mDataMQ(maybeCreateDataMQ(descriptor)) {} void checkIsValid() const { EXPECT_NE(0UL, mFrameSizeBytes); ASSERT_NE(nullptr, mCommandMQ); EXPECT_TRUE(mCommandMQ->isValid()); ASSERT_NE(nullptr, mReplyMQ); EXPECT_TRUE(mReplyMQ->isValid()); if (mDataMQ != nullptr) { EXPECT_TRUE(mDataMQ->isValid()); EXPECT_GE(mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize(), mFrameSizeBytes * mBufferSizeFrames) << "Data MQ actual buffer size is " "less than the buffer size as specified by the descriptor"; } } size_t getBufferSizeBytes() const { return mFrameSizeBytes * mBufferSizeFrames; } size_t getBufferSizeFrames() const { return mBufferSizeFrames; } CommandMQ* getCommandMQ() const { return mCommandMQ.get(); } DataMQ* getDataMQ() const { return mDataMQ.get(); } ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); } private: static std::unique_ptr maybeCreateDataMQ(const StreamDescriptor& descriptor) { using Tag = StreamDescriptor::AudioBuffer::Tag; if (descriptor.audio.getTag() == Tag::fmq) { return std::make_unique(descriptor.audio.get()); } return nullptr; } const size_t mFrameSizeBytes; std::unique_ptr mCommandMQ; std::unique_ptr mReplyMQ; const size_t mBufferSizeFrames; std::unique_ptr mDataMQ; }; struct StreamEventReceiver { virtual ~StreamEventReceiver() = default; enum class Event { None, DrainReady, Error, TransferReady }; virtual std::tuple getLastEvent() const = 0; virtual std::tuple waitForEvent(int clientEventSeq) = 0; static constexpr int kEventSeqInit = -1; }; std::string toString(StreamEventReceiver::Event event) { switch (event) { case StreamEventReceiver::Event::None: return "None"; case StreamEventReceiver::Event::DrainReady: return "DrainReady"; case StreamEventReceiver::Event::Error: return "Error"; case StreamEventReceiver::Event::TransferReady: return "TransferReady"; } return std::to_string(static_cast(event)); } // Transition to the next state happens either due to a command from the client, // or after an event received from the server. using TransitionTrigger = std::variant; using StateTransition = std::pair; struct StateSequence { virtual ~StateSequence() = default; virtual void rewind() = 0; virtual bool done() const = 0; virtual TransitionTrigger getTrigger() = 0; virtual std::set getExpectedStates() = 0; virtual void advance(StreamDescriptor::State state) = 0; }; static const StreamDescriptor::Command kGetStatusCommand = StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kStartCommand = StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kBurstCommand = StreamDescriptor::Command::make(0); static const StreamDescriptor::Command kDrainInCommand = StreamDescriptor::Command::make( StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED); static const StreamDescriptor::Command kDrainOutAllCommand = StreamDescriptor::Command::make( StreamDescriptor::DrainMode::DRAIN_ALL); static const StreamDescriptor::Command kDrainOutEarlyCommand = StreamDescriptor::Command::make( StreamDescriptor::DrainMode::DRAIN_EARLY_NOTIFY); static const StreamDescriptor::Command kStandbyCommand = StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kPauseCommand = StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kFlushCommand = StreamDescriptor::Command::make(Void{}); static const StreamEventReceiver::Event kTransferReadyEvent = StreamEventReceiver::Event::TransferReady; static const StreamEventReceiver::Event kDrainReadyEvent = StreamEventReceiver::Event::DrainReady; // Handle possible bifurcations: // - on burst and on start: 'TRANSFERRING' -> {'ACTIVE', 'TRANSFERRING'} // - on pause: 'TRANSFER_PAUSED' -> {'PAUSED', 'TRANSFER_PAUSED'} // It is assumed that the 'steps' provided on the construction contain the sequence // for the async case, which gets corrected in the case when the HAL decided to do // a synchronous transfer. class SmartStateSequence : public StateSequence { public: explicit SmartStateSequence(const std::vector& steps) : mSteps(steps) {} explicit SmartStateSequence(std::vector&& steps) : mSteps(std::move(steps)) {} void rewind() override { mCurrentStep = 0; } bool done() const override { return mCurrentStep >= mSteps.size(); } TransitionTrigger getTrigger() override { return mSteps[mCurrentStep].first; } std::set getExpectedStates() override { std::set result = {getState()}; if (isBurstBifurcation() || isStartBifurcation()) { result.insert(StreamDescriptor::State::ACTIVE); } else if (isPauseBifurcation()) { result.insert(StreamDescriptor::State::PAUSED); } return result; } void advance(StreamDescriptor::State state) override { if (isBurstBifurcation() && state == StreamDescriptor::State::ACTIVE && mCurrentStep + 1 < mSteps.size() && mSteps[mCurrentStep + 1].first == TransitionTrigger{kTransferReadyEvent}) { mCurrentStep++; } mCurrentStep++; } private: StreamDescriptor::State getState() const { return mSteps[mCurrentStep].second; } bool isBurstBifurcation() { return getTrigger() == TransitionTrigger{kBurstCommand} && getState() == StreamDescriptor::State::TRANSFERRING; } bool isPauseBifurcation() { return getTrigger() == TransitionTrigger{kPauseCommand} && getState() == StreamDescriptor::State::TRANSFER_PAUSED; } bool isStartBifurcation() { return getTrigger() == TransitionTrigger{kStartCommand} && getState() == StreamDescriptor::State::TRANSFERRING; } const std::vector mSteps; size_t mCurrentStep = 0; }; std::string toString(const TransitionTrigger& trigger) { if (std::holds_alternative(trigger)) { return std::string("'") .append(toString(std::get(trigger).getTag())) .append("' command"); } return std::string("'") .append(toString(std::get(trigger))) .append("' event"); } struct StreamLogicDriver { virtual ~StreamLogicDriver() = default; // Return 'true' to stop the worker. virtual bool done() = 0; // For 'Writer' logic, if the 'actualSize' is 0, write is skipped. // The 'fmqByteCount' from the returned command is passed as is to the HAL. virtual TransitionTrigger getNextTrigger(int maxDataSize, int* actualSize = nullptr) = 0; // Return 'true' to indicate that no further processing is needed, // for example, the driver is expecting a bad status to be returned. // The logic cycle will return with 'CONTINUE' status. Otherwise, // the reply will be validated and then passed to 'processValidReply'. virtual bool interceptRawReply(const StreamDescriptor::Reply& reply) = 0; // Return 'false' to indicate that the contents of the reply are unexpected. // Will abort the logic cycle. virtual bool processValidReply(const StreamDescriptor::Reply& reply) = 0; }; class StreamCommonLogic : public StreamLogic { protected: StreamCommonLogic(const StreamContext& context, StreamLogicDriver* driver, StreamEventReceiver* eventReceiver) : mCommandMQ(context.getCommandMQ()), mReplyMQ(context.getReplyMQ()), mDataMQ(context.getDataMQ()), mData(context.getBufferSizeBytes()), mDriver(driver), mEventReceiver(eventReceiver) {} StreamContext::CommandMQ* getCommandMQ() const { return mCommandMQ; } StreamContext::ReplyMQ* getReplyMQ() const { return mReplyMQ; } StreamContext::DataMQ* getDataMQ() const { return mDataMQ; } StreamLogicDriver* getDriver() const { return mDriver; } StreamEventReceiver* getEventReceiver() const { return mEventReceiver; } std::string init() override { LOG(DEBUG) << __func__; return ""; } std::optional maybeGetNextCommand(int* actualSize = nullptr) { TransitionTrigger trigger = mDriver->getNextTrigger(mData.size(), actualSize); if (StreamEventReceiver::Event* expEvent = std::get_if(&trigger); expEvent != nullptr) { auto [eventSeq, event] = mEventReceiver->waitForEvent(mLastEventSeq); mLastEventSeq = eventSeq; if (event != *expEvent) { LOG(ERROR) << __func__ << ": expected event " << toString(*expEvent) << ", got " << toString(event); return {}; } // If we were waiting for an event, the new stream state must be retrieved // via 'getStatus'. return StreamDescriptor::Command::make( Void{}); } return std::get(trigger); } bool readDataFromMQ(size_t readCount) { std::vector data(readCount); if (mDataMQ->read(data.data(), readCount)) { memcpy(mData.data(), data.data(), std::min(mData.size(), data.size())); return true; } LOG(ERROR) << __func__ << ": reading of " << readCount << " bytes from MQ failed"; return false; } bool writeDataToMQ() { if (mDataMQ->write(mData.data(), mData.size())) { return true; } LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to MQ failed"; return false; } private: StreamContext::CommandMQ* mCommandMQ; StreamContext::ReplyMQ* mReplyMQ; StreamContext::DataMQ* mDataMQ; std::vector mData; StreamLogicDriver* const mDriver; StreamEventReceiver* const mEventReceiver; int mLastEventSeq = StreamEventReceiver::kEventSeqInit; }; class StreamReaderLogic : public StreamCommonLogic { public: StreamReaderLogic(const StreamContext& context, StreamLogicDriver* driver, StreamEventReceiver* eventReceiver) : StreamCommonLogic(context, driver, eventReceiver) {} protected: Status cycle() override { if (getDriver()->done()) { LOG(DEBUG) << __func__ << ": clean exit"; return Status::EXIT; } StreamDescriptor::Command command; if (auto maybeCommand = maybeGetNextCommand(); maybeCommand.has_value()) { command = std::move(maybeCommand.value()); } else { LOG(ERROR) << __func__ << ": no next command"; return Status::ABORT; } LOG(DEBUG) << "Writing command: " << command.toString(); if (!getCommandMQ()->writeBlocking(&command, 1)) { LOG(ERROR) << __func__ << ": writing of command into MQ failed"; return Status::ABORT; } StreamDescriptor::Reply reply{}; LOG(DEBUG) << "Reading reply..."; if (!getReplyMQ()->readBlocking(&reply, 1)) { return Status::ABORT; } LOG(DEBUG) << "Reply received: " << reply.toString(); if (getDriver()->interceptRawReply(reply)) { LOG(DEBUG) << __func__ << ": reply has been intercepted by the driver"; return Status::CONTINUE; } if (reply.status != STATUS_OK) { LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status); return Status::ABORT; } if (reply.fmqByteCount < 0 || (command.getTag() == StreamDescriptor::Command::Tag::burst && reply.fmqByteCount > command.get())) { LOG(ERROR) << __func__ << ": received invalid byte count in the reply: " << reply.fmqByteCount; return Status::ABORT; } if (static_cast(reply.fmqByteCount) != getDataMQ()->availableToRead()) { LOG(ERROR) << __func__ << ": the byte count in the reply is not the same as the amount of " << "data available in the MQ: " << reply.fmqByteCount << " != " << getDataMQ()->availableToRead(); } if (reply.latencyMs < 0 && reply.latencyMs != StreamDescriptor::LATENCY_UNKNOWN) { LOG(ERROR) << __func__ << ": received invalid latency value: " << reply.latencyMs; return Status::ABORT; } if (reply.xrunFrames < 0) { LOG(ERROR) << __func__ << ": received invalid xrunFrames value: " << reply.xrunFrames; return Status::ABORT; } if (std::find(enum_range().begin(), enum_range().end(), reply.state) == enum_range().end()) { LOG(ERROR) << __func__ << ": received invalid stream state: " << toString(reply.state); return Status::ABORT; } const bool acceptedReply = getDriver()->processValidReply(reply); if (const size_t readCount = getDataMQ()->availableToRead(); readCount > 0) { if (readDataFromMQ(readCount)) { goto checkAcceptedReply; } LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes from MQ failed"; return Status::ABORT; } // readCount == 0 checkAcceptedReply: if (acceptedReply) { return Status::CONTINUE; } LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString(); return Status::ABORT; } }; using StreamReader = StreamWorker; class StreamWriterLogic : public StreamCommonLogic { public: StreamWriterLogic(const StreamContext& context, StreamLogicDriver* driver, StreamEventReceiver* eventReceiver) : StreamCommonLogic(context, driver, eventReceiver) {} protected: Status cycle() override { if (getDriver()->done()) { LOG(DEBUG) << __func__ << ": clean exit"; return Status::EXIT; } int actualSize = 0; StreamDescriptor::Command command; if (auto maybeCommand = maybeGetNextCommand(&actualSize); maybeCommand.has_value()) { command = std::move(maybeCommand.value()); } else { LOG(ERROR) << __func__ << ": no next command"; return Status::ABORT; } if (actualSize != 0 && !writeDataToMQ()) { return Status::ABORT; } LOG(DEBUG) << "Writing command: " << command.toString(); if (!getCommandMQ()->writeBlocking(&command, 1)) { LOG(ERROR) << __func__ << ": writing of command into MQ failed"; return Status::ABORT; } StreamDescriptor::Reply reply{}; LOG(DEBUG) << "Reading reply..."; if (!getReplyMQ()->readBlocking(&reply, 1)) { LOG(ERROR) << __func__ << ": reading of reply from MQ failed"; return Status::ABORT; } LOG(DEBUG) << "Reply received: " << reply.toString(); if (getDriver()->interceptRawReply(reply)) { return Status::CONTINUE; } if (reply.status != STATUS_OK) { LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status); return Status::ABORT; } if (reply.fmqByteCount < 0 || (command.getTag() == StreamDescriptor::Command::Tag::burst && reply.fmqByteCount > command.get())) { LOG(ERROR) << __func__ << ": received invalid byte count in the reply: " << reply.fmqByteCount; return Status::ABORT; } if (getDataMQ()->availableToWrite() != getDataMQ()->getQuantumCount()) { LOG(ERROR) << __func__ << ": the HAL module did not consume all data from the data MQ: " << "available to write " << getDataMQ()->availableToWrite() << ", total size: " << getDataMQ()->getQuantumCount(); return Status::ABORT; } if (reply.latencyMs < 0 && reply.latencyMs != StreamDescriptor::LATENCY_UNKNOWN) { LOG(ERROR) << __func__ << ": received invalid latency value: " << reply.latencyMs; return Status::ABORT; } if (reply.xrunFrames < 0) { LOG(ERROR) << __func__ << ": received invalid xrunFrames value: " << reply.xrunFrames; return Status::ABORT; } if (std::find(enum_range().begin(), enum_range().end(), reply.state) == enum_range().end()) { LOG(ERROR) << __func__ << ": received invalid stream state: " << toString(reply.state); return Status::ABORT; } if (getDriver()->processValidReply(reply)) { return Status::CONTINUE; } LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString(); return Status::ABORT; } }; using StreamWriter = StreamWorker; class DefaultStreamCallback : public ::aidl::android::hardware::audio::core::BnStreamCallback, public StreamEventReceiver { ndk::ScopedAStatus onTransferReady() override { LOG(DEBUG) << __func__; putLastEvent(Event::TransferReady); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus onError() override { LOG(DEBUG) << __func__; putLastEvent(Event::Error); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus onDrainReady() override { LOG(DEBUG) << __func__; putLastEvent(Event::DrainReady); return ndk::ScopedAStatus::ok(); } public: // To avoid timing out the whole test suite in case no event is received // from the HAL, use a local timeout for event waiting. static constexpr auto kEventTimeoutMs = std::chrono::milliseconds(1000); StreamEventReceiver* getEventReceiver() { return this; } std::tuple getLastEvent() const override { std::lock_guard l(mLock); return getLastEvent_l(); } std::tuple waitForEvent(int clientEventSeq) override { std::unique_lock l(mLock); android::base::ScopedLockAssertion lock_assertion(mLock); LOG(DEBUG) << __func__ << ": client " << clientEventSeq << ", last " << mLastEventSeq; if (mCv.wait_for(l, kEventTimeoutMs, [&]() { android::base::ScopedLockAssertion lock_assertion(mLock); return clientEventSeq < mLastEventSeq; })) { } else { LOG(WARNING) << __func__ << ": timed out waiting for an event"; putLastEvent_l(Event::None); } return getLastEvent_l(); } private: std::tuple getLastEvent_l() const REQUIRES(mLock) { return std::make_tuple(mLastEventSeq, mLastEvent); } void putLastEvent(Event event) { { std::lock_guard l(mLock); putLastEvent_l(event); } mCv.notify_one(); } void putLastEvent_l(Event event) REQUIRES(mLock) { mLastEventSeq++; mLastEvent = event; } mutable std::mutex mLock; std::condition_variable mCv; int mLastEventSeq GUARDED_BY(mLock) = kEventSeqInit; Event mLastEvent GUARDED_BY(mLock) = Event::None; }; template struct IOTraits { static constexpr bool is_input = std::is_same_v; using Worker = std::conditional_t; }; template class WithStream { public: static ndk::ScopedAStatus callClose(std::shared_ptr stream) { std::shared_ptr common; ndk::ScopedAStatus status = stream->getStreamCommon(&common); if (!status.isOk()) return status; return common->close(); } WithStream() {} explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {} WithStream(const WithStream&) = delete; WithStream& operator=(const WithStream&) = delete; ~WithStream() { if (mStream != nullptr) { mContext.reset(); EXPECT_IS_OK(callClose(mStream)) << "port config id " << getPortId(); } } void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); } ScopedAStatus SetUpNoChecks(IModule* module, long bufferSizeFrames) { return SetUpNoChecks(module, mPortConfig.get(), bufferSizeFrames); } ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig, long bufferSizeFrames); void SetUp(IModule* module, long bufferSizeFrames) { ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module)); ASSERT_IS_OK(SetUpNoChecks(module, bufferSizeFrames)) << "port config id " << getPortId(); ASSERT_NE(nullptr, mStream) << "port config id " << getPortId(); EXPECT_GE(mDescriptor.bufferSizeFrames, bufferSizeFrames) << "actual buffer size must be no less than requested"; mContext.emplace(mDescriptor); ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid()); } Stream* get() const { return mStream.get(); } const StreamContext* getContext() const { return mContext ? &(mContext.value()) : nullptr; } StreamEventReceiver* getEventReceiver() { return mStreamCallback->getEventReceiver(); } std::shared_ptr getSharedPointer() const { return mStream; } const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); } int32_t getPortId() const { return mPortConfig.getId(); } private: WithAudioPortConfig mPortConfig; std::shared_ptr mStream; StreamDescriptor mDescriptor; std::optional mContext; std::shared_ptr mStreamCallback; }; SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) { RecordTrackMetadata trackMeta; trackMeta.source = AudioSource::MIC; trackMeta.gain = 1.0; trackMeta.channelMask = portConfig.channelMask.value(); SinkMetadata metadata; metadata.tracks.push_back(trackMeta); return metadata; } template <> ScopedAStatus WithStream::SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig, long bufferSizeFrames) { aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args; args.portConfigId = portConfig.id; args.sinkMetadata = GenerateSinkMetadata(portConfig); args.bufferSizeFrames = bufferSizeFrames; auto callback = ndk::SharedRefBase::make(); // TODO: Uncomment when support for asynchronous input is implemented. // args.callback = callback; aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret; ScopedAStatus status = module->openInputStream(args, &ret); if (status.isOk()) { mStream = std::move(ret.stream); mDescriptor = std::move(ret.desc); mStreamCallback = std::move(callback); } return status; } SourceMetadata GenerateSourceMetadata(const AudioPortConfig& portConfig) { PlaybackTrackMetadata trackMeta; trackMeta.usage = AudioUsage::MEDIA; trackMeta.contentType = AudioContentType::MUSIC; trackMeta.gain = 1.0; trackMeta.channelMask = portConfig.channelMask.value(); SourceMetadata metadata; metadata.tracks.push_back(trackMeta); return metadata; } template <> ScopedAStatus WithStream::SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig, long bufferSizeFrames) { aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args; args.portConfigId = portConfig.id; args.sourceMetadata = GenerateSourceMetadata(portConfig); args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig); args.bufferSizeFrames = bufferSizeFrames; auto callback = ndk::SharedRefBase::make(); args.callback = callback; aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret; ScopedAStatus status = module->openOutputStream(args, &ret); if (status.isOk()) { mStream = std::move(ret.stream); mDescriptor = std::move(ret.desc); mStreamCallback = std::move(callback); } return status; } class WithAudioPatch { public: WithAudioPatch() {} WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig) : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {} WithAudioPatch(bool sinkIsCfg1, const AudioPortConfig& portConfig1, const AudioPortConfig& portConfig2) : mSrcPortConfig(sinkIsCfg1 ? portConfig2 : portConfig1), mSinkPortConfig(sinkIsCfg1 ? portConfig1 : portConfig2) {} WithAudioPatch(const WithAudioPatch&) = delete; WithAudioPatch& operator=(const WithAudioPatch&) = delete; ~WithAudioPatch() { if (mModule != nullptr && mPatch.id != 0) { EXPECT_IS_OK(mModule->resetAudioPatch(mPatch.id)) << "patch id " << getId(); } } void SetUpPortConfigs(IModule* module) { ASSERT_NO_FATAL_FAILURE(mSrcPortConfig.SetUp(module)); ASSERT_NO_FATAL_FAILURE(mSinkPortConfig.SetUp(module)); } ScopedAStatus SetUpNoChecks(IModule* module) { mModule = module; mPatch.sourcePortConfigIds = std::vector{mSrcPortConfig.getId()}; mPatch.sinkPortConfigIds = std::vector{mSinkPortConfig.getId()}; return mModule->setAudioPatch(mPatch, &mPatch); } void SetUp(IModule* module) { ASSERT_NO_FATAL_FAILURE(SetUpPortConfigs(module)); ASSERT_IS_OK(SetUpNoChecks(module)) << "source port config id " << mSrcPortConfig.getId() << "; sink port config id " << mSinkPortConfig.getId(); EXPECT_GT(mPatch.minimumStreamBufferSizeFrames, 0) << "patch id " << getId(); for (auto latencyMs : mPatch.latenciesMs) { EXPECT_GT(latencyMs, 0) << "patch id " << getId(); } } int32_t getId() const { return mPatch.id; } const AudioPatch& get() const { return mPatch; } const AudioPortConfig& getSinkPortConfig() const { return mSinkPortConfig.get(); } const AudioPortConfig& getSrcPortConfig() const { return mSrcPortConfig.get(); } const AudioPortConfig& getPortConfig(bool getSink) const { return getSink ? getSinkPortConfig() : getSrcPortConfig(); } private: WithAudioPortConfig mSrcPortConfig; WithAudioPortConfig mSinkPortConfig; IModule* mModule = nullptr; AudioPatch mPatch; }; TEST_P(AudioCoreModule, Published) { // SetUp must complete with no failures. } TEST_P(AudioCoreModule, CanBeRestarted) { ASSERT_NO_FATAL_FAILURE(RestartService()); } TEST_P(AudioCoreModule, PortIdsAreUnique) { std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); } TEST_P(AudioCoreModule, GetAudioPortsIsStable) { std::vector ports1; ASSERT_IS_OK(module->getAudioPorts(&ports1)); std::vector ports2; ASSERT_IS_OK(module->getAudioPorts(&ports2)); ASSERT_EQ(ports1.size(), ports2.size()) << "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, GetAudioRoutesIsStable) { std::vector routes1; ASSERT_IS_OK(module->getAudioRoutes(&routes1)); std::vector routes2; ASSERT_IS_OK(module->getAudioRoutes(&routes2)); ASSERT_EQ(routes1.size(), routes2.size()) << "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); } TEST_P(AudioCoreModule, GetAudioRoutesAreValid) { std::vector routes; ASSERT_IS_OK(module->getAudioRoutes(&routes)); for (const auto& route : routes) { std::set sources(route.sourcePortIds.begin(), route.sourcePortIds.end()); EXPECT_NE(0UL, sources.size()) << "empty audio port sinks in the audio route: " << route.toString(); EXPECT_EQ(sources.size(), route.sourcePortIds.size()) << "IDs of audio port sinks are not unique in the audio route: " << route.toString(); } } TEST_P(AudioCoreModule, GetAudioRoutesPortIdsAreValid) { std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); std::vector routes; ASSERT_IS_OK(module->getAudioRoutes(&routes)); for (const auto& route : routes) { EXPECT_EQ(1UL, portIds.count(route.sinkPortId)) << route.sinkPortId << " sink port id is unknown"; for (const auto& source : route.sourcePortIds) { EXPECT_EQ(1UL, portIds.count(source)) << source << " source port id is unknown"; } } } TEST_P(AudioCoreModule, GetAudioRoutesForAudioPort) { std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); if (portIds.empty()) { GTEST_SKIP() << "No ports in the module."; } for (const auto portId : portIds) { std::vector routes; EXPECT_IS_OK(module->getAudioRoutesForAudioPort(portId, &routes)); 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 routes; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->getAudioRoutesForAudioPort(portId, &routes)) << "port ID " << portId; } } TEST_P(AudioCoreModule, CheckDevicePorts) { std::vector ports; ASSERT_IS_OK(module->getAudioPorts(&ports)); std::optional defaultOutput, defaultInput; std::set inputs, outputs; const int defaultDeviceFlag = 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE; for (const auto& port : ports) { if (port.ext.getTag() != AudioPortExt::Tag::device) continue; const auto& devicePort = port.ext.get(); EXPECT_NE(AudioDeviceType::NONE, devicePort.device.type.type); EXPECT_NE(AudioDeviceType::IN_DEFAULT, devicePort.device.type.type); EXPECT_NE(AudioDeviceType::OUT_DEFAULT, devicePort.device.type.type); if (devicePort.device.type.type > AudioDeviceType::IN_DEFAULT && devicePort.device.type.type < AudioDeviceType::OUT_DEFAULT) { EXPECT_EQ(AudioIoFlags::Tag::input, port.flags.getTag()); } else if (devicePort.device.type.type > AudioDeviceType::OUT_DEFAULT) { EXPECT_EQ(AudioIoFlags::Tag::output, port.flags.getTag()); } EXPECT_FALSE((devicePort.flags & defaultDeviceFlag) != 0 && !devicePort.device.type.connection.empty()) << "Device port " << port.id << " must be permanently attached to be set as default"; if ((devicePort.flags & defaultDeviceFlag) != 0) { if (port.flags.getTag() == AudioIoFlags::Tag::output) { EXPECT_FALSE(defaultOutput.has_value()) << "At least two output device ports are declared as default: " << defaultOutput.value() << " and " << port.id; defaultOutput = port.id; EXPECT_EQ(0UL, outputs.count(devicePort.device)) << "Non-unique output device: " << devicePort.device.toString(); outputs.insert(devicePort.device); } else if (port.flags.getTag() == AudioIoFlags::Tag::input) { EXPECT_FALSE(defaultInput.has_value()) << "At least two input device ports are declared as default: " << defaultInput.value() << " and " << port.id; defaultInput = port.id; EXPECT_EQ(0UL, inputs.count(devicePort.device)) << "Non-unique input device: " << devicePort.device.toString(); inputs.insert(devicePort.device); } else { FAIL() << "Invalid AudioIoFlags Tag: " << toString(port.flags.getTag()); } } } } TEST_P(AudioCoreModule, CheckMixPorts) { std::vector ports; ASSERT_IS_OK(module->getAudioPorts(&ports)); std::optional primaryMixPort; for (const auto& port : ports) { if (port.ext.getTag() != AudioPortExt::Tag::mix) continue; const auto& mixPort = port.ext.get(); if (port.flags.getTag() == AudioIoFlags::Tag::output && isBitPositionFlagSet(port.flags.get(), AudioOutputFlags::PRIMARY)) { EXPECT_FALSE(primaryMixPort.has_value()) << "At least two mix ports have PRIMARY flag set: " << primaryMixPort.value() << " and " << port.id; primaryMixPort = port.id; EXPECT_EQ(1, mixPort.maxOpenStreamCount) << "Primary mix port " << port.id << " can not have maxOpenStreamCount " << mixPort.maxOpenStreamCount; } } } TEST_P(AudioCoreModule, GetAudioPort) { std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); if (portIds.empty()) { GTEST_SKIP() << "No ports in the module."; } for (const auto portId : portIds) { AudioPort port; EXPECT_IS_OK(module->getAudioPort(portId, &port)); EXPECT_EQ(portId, port.id); } for (const auto portId : GetNonExistentIds(portIds)) { AudioPort port; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->getAudioPort(portId, &port)) << "port ID " << portId; } } TEST_P(AudioCoreModule, SetUpModuleConfig) { ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); // Send the module config to logcat to facilitate failures investigation. LOG(INFO) << "SetUpModuleConfig: " << moduleConfig->toString(); } // 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 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().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().device, portConnected.get().ext.get().device); // Verify that 'getAudioPort' and 'getAudioPorts' return the same connected port. AudioPort connectedPort; EXPECT_IS_OK(module->getAudioPort(connectedPortId, &connectedPort)) << "port ID " << connectedPortId; EXPECT_EQ(portConnected.get(), connectedPort); const auto& portProfiles = connectedPort.profiles; EXPECT_NE(0UL, 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 allPorts; ASSERT_IS_OK(module->getAudioPorts(&allPorts)); 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 portConfigIds; ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); for (const auto portConfigId : GetNonExistentIds(portConfigIds)) { { aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args; args.portConfigId = portConfigId; args.bufferSizeFrames = kDefaultBufferSizeFrames; aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openInputStream(args, &ret)) << "port config ID " << portConfigId; EXPECT_EQ(nullptr, ret.stream); } { aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args; args.portConfigId = portConfigId; args.bufferSizeFrames = kDefaultBufferSizeFrames; aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret)) << "port config ID " << portConfigId; EXPECT_EQ(nullptr, ret.stream); } } } TEST_P(AudioCoreModule, PortConfigIdsAreUnique) { std::set portConfigIds; ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); } TEST_P(AudioCoreModule, PortConfigPortIdsAreValid) { std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); std::vector portConfigs; ASSERT_IS_OK(module->getAudioPortConfigs(&portConfigs)); for (const auto& config : portConfigs) { EXPECT_EQ(1UL, portIds.count(config.portId)) << config.portId << " port id is unknown, config id " << config.id; } } TEST_P(AudioCoreModule, ResetAudioPortConfigInvalidId) { std::set portConfigIds; ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); for (const auto portConfigId : GetNonExistentIds(portConfigIds)) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->resetAudioPortConfig(portConfigId)) << "port config ID " << portConfigId; } } // Verify that for the audio port configs provided by the HAL after init, resetting // the config does not delete it, but brings it back to the initial config. TEST_P(AudioCoreModule, ResetAudioPortConfigToInitialValue) { std::vector portConfigsBefore; ASSERT_IS_OK(module->getAudioPortConfigs(&portConfigsBefore)); // TODO: Change port configs according to port profiles. for (const auto& c : portConfigsBefore) { EXPECT_IS_OK(module->resetAudioPortConfig(c.id)) << "port config ID " << c.id; } std::vector portConfigsAfter; ASSERT_IS_OK(module->getAudioPortConfigs(&portConfigsAfter)); for (const auto& c : portConfigsBefore) { auto afterIt = findById(portConfigsAfter, c.id); EXPECT_NE(portConfigsAfter.end(), afterIt) << " port config ID " << c.id << " was removed by reset"; if (afterIt != portConfigsAfter.end()) { EXPECT_EQ(c, *afterIt); } } } TEST_P(AudioCoreModule, SetAudioPortConfigSuggestedConfig) { ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); auto srcMixPort = moduleConfig->getSourceMixPortForAttachedDevice(); if (!srcMixPort.has_value()) { GTEST_SKIP() << "No mix port for attached output devices"; } AudioPortConfig portConfig; AudioPortConfig suggestedConfig; portConfig.portId = srcMixPort.value().id; { bool applied = true; ASSERT_IS_OK(module->setAudioPortConfig(portConfig, &suggestedConfig, &applied)) << "Config: " << portConfig.toString(); EXPECT_FALSE(applied); } EXPECT_EQ(0, suggestedConfig.id); EXPECT_TRUE(suggestedConfig.sampleRate.has_value()); EXPECT_TRUE(suggestedConfig.channelMask.has_value()); EXPECT_TRUE(suggestedConfig.format.has_value()); EXPECT_TRUE(suggestedConfig.flags.has_value()); WithAudioPortConfig applied(suggestedConfig); ASSERT_NO_FATAL_FAILURE(applied.SetUp(module.get())); const AudioPortConfig& appliedConfig = applied.get(); EXPECT_NE(0, appliedConfig.id); EXPECT_TRUE(appliedConfig.sampleRate.has_value()); EXPECT_EQ(suggestedConfig.sampleRate.value(), appliedConfig.sampleRate.value()); EXPECT_TRUE(appliedConfig.channelMask.has_value()); EXPECT_EQ(suggestedConfig.channelMask.value(), appliedConfig.channelMask.value()); EXPECT_TRUE(appliedConfig.format.has_value()); EXPECT_EQ(suggestedConfig.format.value(), appliedConfig.format.value()); EXPECT_TRUE(appliedConfig.flags.has_value()); EXPECT_EQ(suggestedConfig.flags.value(), appliedConfig.flags.value()); } TEST_P(AudioCoreModule, 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 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()); ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForMixPorts())); } TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) { std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); for (const auto portId : GetNonExistentIds(portIds)) { AudioPortConfig portConfig, suggestedConfig; bool applied; portConfig.portId = portId; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->setAudioPortConfig(portConfig, &suggestedConfig, &applied)) << "port ID " << portId; EXPECT_FALSE(suggestedConfig.format.has_value()); EXPECT_FALSE(suggestedConfig.channelMask.has_value()); EXPECT_FALSE(suggestedConfig.sampleRate.has_value()); } } TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) { std::set portConfigIds; ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); for (const auto portConfigId : GetNonExistentIds(portConfigIds)) { AudioPortConfig portConfig, suggestedConfig; bool applied; portConfig.id = portConfigId; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->setAudioPortConfig(portConfig, &suggestedConfig, &applied)) << "port config ID " << portConfigId; EXPECT_FALSE(suggestedConfig.format.has_value()); EXPECT_FALSE(suggestedConfig.channelMask.has_value()); EXPECT_FALSE(suggestedConfig.sampleRate.has_value()); } } TEST_P(AudioCoreModule, TryConnectMissingDevice) { ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); std::vector ports = moduleConfig->getExternalDevicePorts(); if (ports.empty()) { GTEST_SKIP() << "No external devices in the module."; } AudioPort ignored; WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(debug); doNotSimulateConnections.flags().simulateDeviceConnections = false; ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get())); for (const auto& port : ports) { AudioPort portWithData = port; portWithData.ext.get().device.address = GenerateUniqueDeviceAddress(); EXPECT_STATUS(EX_ILLEGAL_STATE, module->connectExternalDevice(portWithData, &ignored)) << "static port " << portWithData.toString(); } } TEST_P(AudioCoreModule, TryChangingConnectionSimulationMidway) { ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); std::vector 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; EXPECT_STATUS(EX_ILLEGAL_STATE, module->setModuleDebug(midwayDebugChange)) << "when trying to disable connections simulation while having a connected device"; } TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceInvalidPorts) { AudioPort ignored; std::set portIds; ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); for (const auto portId : GetNonExistentIds(portIds)) { AudioPort invalidPort; invalidPort.id = portId; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(invalidPort, &ignored)) << "port ID " << portId << ", when setting CONNECTED state"; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(portId)) << "port ID " << portId << ", when setting DISCONNECTED state"; } std::vector ports; ASSERT_IS_OK(module->getAudioPorts(&ports)); for (const auto& port : ports) { if (port.ext.getTag() != AudioPortExt::Tag::device) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(port, &ignored)) << "non-device port ID " << port.id << " when setting CONNECTED state"; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(port.id)) << "non-device port ID " << port.id << " when setting DISCONNECTED state"; } else { const auto& devicePort = port.ext.get(); if (devicePort.device.type.connection.empty()) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(port, &ignored)) << "for a permanently attached device port ID " << port.id << " when setting CONNECTED state"; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(port.id)) << "for a 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 ports = moduleConfig->getExternalDevicePorts(); if (ports.empty()) { GTEST_SKIP() << "No external devices in the module."; } for (const auto& port : ports) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->disconnectExternalDevice(port.id)) << "when disconnecting already disconnected device port ID " << port.id; AudioPort portWithData = port; portWithData.ext.get().device.address = GenerateUniqueDeviceAddress(); WithDevicePortConnectedState portConnected(portWithData); ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get())); EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->connectExternalDevice(portConnected.get(), &ignored)) << "when trying to connect a connected device port " << portConnected.get().toString(); EXPECT_STATUS(EX_ILLEGAL_STATE, module->connectExternalDevice(portWithData, &ignored)) << "when connecting again the external device " << portWithData.ext.get().device.toString() << "; 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 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())); EXPECT_STATUS(EX_ILLEGAL_STATE, module->disconnectExternalDevice(portConnected.getId())) << "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 ports = moduleConfig->getExternalDevicePorts(); if (ports.empty()) { GTEST_SKIP() << "No external devices in the module."; } for (const auto& port : ports) { std::vector routesBefore; ASSERT_IS_OK(module->getAudioRoutes(&routesBefore)); int32_t connectedPortId; { WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress()); ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get())); connectedPortId = portConnected.getId(); std::vector connectedPortRoutes; ASSERT_IS_OK(module->getAudioRoutesForAudioPort(connectedPortId, &connectedPortRoutes)) << "when retrieving routes for connected port id " << connectedPortId; // There must be routes for the port to be useful. if (connectedPortRoutes.empty()) { std::vector allRoutes; ASSERT_IS_OK(module->getAudioRoutes(&allRoutes)); ADD_FAILURE() << " no routes returned for the connected port " << portConnected.get().toString() << "; all routes: " << android::internal::ToString(allRoutes); } } std::vector ignored; ASSERT_STATUS(EX_ILLEGAL_ARGUMENT, module->getAudioRoutesForAudioPort(connectedPortId, &ignored)) << "when retrieving routes for released connected port id " << connectedPortId; std::vector routesAfter; ASSERT_IS_OK(module->getAudioRoutes(&routesAfter)); 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); } } TEST_P(AudioCoreModule, MasterMute) { bool isSupported = false; EXPECT_NO_FATAL_FAILURE(TestAccessors(module.get(), &IModule::getMasterMute, &IModule::setMasterMute, {false, true}, {}, &isSupported)); if (!isSupported) { GTEST_SKIP() << "Master mute is not supported"; } // TODO: Test that master mute actually mutes output. } TEST_P(AudioCoreModule, MasterVolume) { bool isSupported = false; EXPECT_NO_FATAL_FAILURE(TestAccessors( module.get(), &IModule::getMasterVolume, &IModule::setMasterVolume, {0.0f, 0.5f, 1.0f}, {-0.1, 1.1, NAN, INFINITY, -INFINITY, 1 + std::numeric_limits::epsilon()}, &isSupported)); if (!isSupported) { GTEST_SKIP() << "Master volume is not supported"; } // TODO: Test that master volume actually attenuates output. } TEST_P(AudioCoreModule, MicMute) { bool isSupported = false; EXPECT_NO_FATAL_FAILURE(TestAccessors(module.get(), &IModule::getMicMute, &IModule::setMicMute, {false, true}, {}, &isSupported)); if (!isSupported) { GTEST_SKIP() << "Mic mute is not supported"; } // TODO: Test that mic mute actually mutes input. } TEST_P(AudioCoreModule, GetMicrophones) { ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); const std::vector builtInMicPorts = moduleConfig->getAttachedMicrophonePorts(); std::vector micInfos; ScopedAStatus status = module->getMicrophones(&micInfos); if (!status.isOk()) { EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode()); ASSERT_FALSE(builtInMicPorts.empty()) << "When the HAL module does not have built-in microphones, IModule.getMicrophones" << " must complete with no error and return an empty list"; GTEST_SKIP() << "Microphone info is not supported"; } std::set micPortIdsWithInfo; for (const auto& micInfo : micInfos) { const auto& micDevice = micInfo.device; const auto it = std::find_if(builtInMicPorts.begin(), builtInMicPorts.end(), [&](const auto& port) { return port.ext.template get().device == micDevice; }); if (it != builtInMicPorts.end()) { micPortIdsWithInfo.insert(it->id); } else { ADD_FAILURE() << "No device port found with a device specified for the microphone \"" << micInfo.id << "\": " << micDevice.toString(); } } if (micPortIdsWithInfo.size() != builtInMicPorts.size()) { std::vector micPortsNoInfo; std::copy_if(builtInMicPorts.begin(), builtInMicPorts.end(), std::back_inserter(micPortsNoInfo), [&](const auto& port) { return micPortIdsWithInfo.count(port.id) == 0; }); ADD_FAILURE() << "No MicrophoneInfo is provided for the following microphone device ports: " << ::android::internal::ToString(micPortsNoInfo); } } TEST_P(AudioCoreModule, UpdateAudioMode) { for (const auto mode : ::ndk::enum_range()) { EXPECT_IS_OK(module->updateAudioMode(mode)) << toString(mode); } EXPECT_IS_OK(module->updateAudioMode(AudioMode::NORMAL)); } TEST_P(AudioCoreModule, UpdateScreenRotation) { for (const auto rotation : ::ndk::enum_range()) { EXPECT_IS_OK(module->updateScreenRotation(rotation)) << toString(rotation); } EXPECT_IS_OK(module->updateScreenRotation(IModule::ScreenRotation::DEG_0)); } TEST_P(AudioCoreModule, UpdateScreenState) { EXPECT_IS_OK(module->updateScreenState(false)); EXPECT_IS_OK(module->updateScreenState(true)); } TEST_P(AudioCoreModule, GenerateHwAvSyncId) { const auto kStatuses = {EX_NONE, EX_ILLEGAL_STATE}; int32_t id1; ndk::ScopedAStatus status = module->generateHwAvSyncId(&id1); if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { GTEST_SKIP() << "HW AV Sync is not supported"; } EXPECT_STATUS(kStatuses, status); if (status.isOk()) { int32_t id2; ASSERT_IS_OK(module->generateHwAvSyncId(&id2)); EXPECT_NE(id1, id2) << "HW AV Sync IDs must be unique"; } } TEST_P(AudioCoreModule, GetVendorParameters) { bool isGetterSupported = false; EXPECT_NO_FATAL_FAILURE(TestGetVendorParameters(module.get(), &isGetterSupported)); ndk::ScopedAStatus status = module->setVendorParameters({}, false); EXPECT_EQ(isGetterSupported, status.getExceptionCode() != EX_UNSUPPORTED_OPERATION) << "Support for getting and setting of vendor parameters must be consistent"; if (!isGetterSupported) { GTEST_SKIP() << "Vendor parameters are not supported"; } } TEST_P(AudioCoreModule, SetVendorParameters) { bool isSupported = false; EXPECT_NO_FATAL_FAILURE(TestSetVendorParameters(module.get(), &isSupported)); if (!isSupported) { GTEST_SKIP() << "Vendor parameters are not supported"; } } class AudioCoreTelephony : public AudioCoreModuleBase, public testing::TestWithParam { public: void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam())); ASSERT_IS_OK(module->getTelephony(&telephony)); } void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); } std::shared_ptr telephony; }; TEST_P(AudioCoreTelephony, GetSupportedAudioModes) { if (telephony == nullptr) { GTEST_SKIP() << "Telephony is not supported"; } std::vector modes1; ASSERT_IS_OK(telephony->getSupportedAudioModes(&modes1)); const std::vector kMandatoryModes = {AudioMode::NORMAL, AudioMode::RINGTONE, AudioMode::IN_CALL, AudioMode::IN_COMMUNICATION}; for (const auto mode : kMandatoryModes) { EXPECT_NE(modes1.end(), std::find(modes1.begin(), modes1.end(), mode)) << "Mandatory mode not supported: " << toString(mode); } std::vector modes2; ASSERT_IS_OK(telephony->getSupportedAudioModes(&modes2)); ASSERT_EQ(modes1.size(), modes2.size()) << "Sizes of audio mode arrays do not match across consequent calls to " << "getSupportedAudioModes"; std::sort(modes1.begin(), modes1.end()); std::sort(modes2.begin(), modes2.end()); EXPECT_EQ(modes1, modes2); }; TEST_P(AudioCoreTelephony, SwitchAudioMode) { if (telephony == nullptr) { GTEST_SKIP() << "Telephony is not supported"; } std::vector supportedModes; ASSERT_IS_OK(telephony->getSupportedAudioModes(&supportedModes)); std::set unsupportedModes = { // Start with all, remove supported ones ::ndk::enum_range().begin(), ::ndk::enum_range().end()}; for (const auto mode : supportedModes) { EXPECT_IS_OK(telephony->switchAudioMode(mode)) << toString(mode); unsupportedModes.erase(mode); } for (const auto mode : unsupportedModes) { EXPECT_STATUS(EX_UNSUPPORTED_OPERATION, telephony->switchAudioMode(mode)) << toString(mode); } } using CommandSequence = std::vector; class StreamLogicDriverInvalidCommand : public StreamLogicDriver { public: StreamLogicDriverInvalidCommand(const CommandSequence& commands) : mCommands(commands) {} std::string getUnexpectedStatuses() { // This method is intended to be called after the worker thread has joined, // thus no extra synchronization is needed. std::string s; if (!mStatuses.empty()) { s = std::string("Pairs of (command, actual status): ") .append((android::internal::ToString(mStatuses))); } return s; } bool done() override { return mNextCommand >= mCommands.size(); } TransitionTrigger getNextTrigger(int, int* actualSize) override { if (actualSize != nullptr) *actualSize = 0; return mCommands[mNextCommand++]; } bool interceptRawReply(const StreamDescriptor::Reply& reply) override { const size_t currentCommand = mNextCommand - 1; // increased by getNextTrigger const bool isLastCommand = currentCommand == mCommands.size() - 1; // All but the last command should run correctly. The last command must return 'BAD_VALUE' // status. if ((!isLastCommand && reply.status != STATUS_OK) || (isLastCommand && reply.status != STATUS_BAD_VALUE)) { std::string s = mCommands[currentCommand].toString(); s.append(", ").append(statusToString(reply.status)); mStatuses.push_back(std::move(s)); // Process the reply, since the worker exits in case of an error. return false; } return isLastCommand; } bool processValidReply(const StreamDescriptor::Reply&) override { return true; } private: const CommandSequence mCommands; size_t mNextCommand = 0; std::vector mStatuses; }; template class AudioStream : public AudioCoreModule { public: void SetUp() override { ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp()); ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); } void GetStreamCommon() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr streamCommon1; EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon1)); std::shared_ptr streamCommon2; EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon2)); ASSERT_NE(nullptr, streamCommon1); ASSERT_NE(nullptr, streamCommon2); EXPECT_EQ(streamCommon1->asBinder(), streamCommon2->asBinder()) << "getStreamCommon must return the same interface instance across invocations"; } void CloseTwice() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } std::shared_ptr heldStream; { WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); heldStream = stream.getSharedPointer(); } EXPECT_STATUS(EX_ILLEGAL_STATE, WithStream::callClose(heldStream)) << "when closing the stream twice"; } void OpenAllConfigs() { const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IOTraits::is_input); for (const auto& portConfig : allPortConfigs) { WithStream stream(portConfig); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); } } void OpenInvalidBufferSize() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get())); // The buffer size of 1 frame should be impractically small, and thus // less than any minimum buffer size suggested by any HAL. for (long bufferSize : std::array{-1, 0, 1, std::numeric_limits::max()}) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.SetUpNoChecks(module.get(), bufferSize)) << "for the buffer size " << bufferSize; EXPECT_EQ(nullptr, stream.get()); } } void OpenInvalidDirection() { // Important! The direction of the port config must be reversed. const auto portConfig = moduleConfig->getSingleConfigForMixPort(!IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get())); EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames)) << "port config ID " << stream.getPortId(); EXPECT_EQ(nullptr, stream.get()); } void OpenOverMaxCount() { constexpr bool isInput = IOTraits::is_input; auto ports = moduleConfig->getMixPorts(isInput, true /*attachedOnly*/); bool hasSingleRun = false; for (const auto& port : ports) { const size_t maxStreamCount = port.ext.get().maxOpenStreamCount; if (maxStreamCount == 0) { continue; } auto portConfigs = moduleConfig->getPortConfigsForMixPorts(isInput, port); if (portConfigs.size() < maxStreamCount + 1) { // Not able to open a sufficient number of streams for this port. continue; } hasSingleRun = true; std::optional> streamWraps[maxStreamCount + 1]; for (size_t i = 0; i <= maxStreamCount; ++i) { streamWraps[i].emplace(portConfigs[i]); WithStream& stream = streamWraps[i].value(); if (i < maxStreamCount) { ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); } else { ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get())); EXPECT_STATUS(EX_ILLEGAL_STATE, stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames)) << "port config ID " << stream.getPortId() << ", maxOpenStreamCount is " << maxStreamCount; } } } if (!hasSingleRun) { GTEST_SKIP() << "Not enough ports to test max open stream count"; } } void OpenTwiceSamePortConfig() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value())); } void ResetPortConfigWithOpenStream() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(stream.getPortId())) << "port config ID " << stream.getPortId(); } void SendInvalidCommand() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } EXPECT_NO_FATAL_FAILURE(SendInvalidCommandImpl(portConfig.value())); } void UpdateHwAvSyncId() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr streamCommon; ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon)); ASSERT_NE(nullptr, streamCommon); const auto kStatuses = {EX_NONE, EX_ILLEGAL_ARGUMENT, EX_ILLEGAL_STATE}; for (const auto id : {-100, -1, 0, 1, 100}) { ndk::ScopedAStatus status = streamCommon->updateHwAvSyncId(id); if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { GTEST_SKIP() << "HW AV Sync is not supported"; } EXPECT_STATUS(kStatuses, status) << "id: " << id; } } void GetVendorParameters() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr streamCommon; ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon)); ASSERT_NE(nullptr, streamCommon); bool isGetterSupported = false; EXPECT_NO_FATAL_FAILURE(TestGetVendorParameters(module.get(), &isGetterSupported)); ndk::ScopedAStatus status = module->setVendorParameters({}, false); EXPECT_EQ(isGetterSupported, status.getExceptionCode() != EX_UNSUPPORTED_OPERATION) << "Support for getting and setting of vendor parameters must be consistent"; if (!isGetterSupported) { GTEST_SKIP() << "Vendor parameters are not supported"; } } void SetVendorParameters() { const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits::is_input); if (!portConfig.has_value()) { GTEST_SKIP() << "No mix port for attached devices"; } WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); std::shared_ptr streamCommon; ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon)); ASSERT_NE(nullptr, streamCommon); bool isSupported = false; EXPECT_NO_FATAL_FAILURE(TestSetVendorParameters(module.get(), &isSupported)); if (!isSupported) { GTEST_SKIP() << "Vendor parameters are not supported"; } } void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) { WithStream stream1(portConfig); ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSizeFrames)); WithStream stream2; EXPECT_STATUS(EX_ILLEGAL_STATE, stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(), kDefaultBufferSizeFrames)) << "when opening a stream twice for the same port config ID " << stream1.getPortId(); } void SendInvalidCommandImpl(const AudioPortConfig& portConfig) { using TestSequence = std::pair; // The last command in 'CommandSequence' is the one that must trigger // an error status. All preceding commands are to put the state machine // into a state which accepts the last command. std::vector sequences{ std::make_pair(std::string("HalReservedExit"), std::vector{StreamDescriptor::Command::make< StreamDescriptor::Command::Tag::halReservedExit>(0)}), std::make_pair(std::string("BurstNeg"), std::vector{kStartCommand, StreamDescriptor::Command::make< StreamDescriptor::Command::Tag::burst>(-1)}), std::make_pair( std::string("BurstMinInt"), std::vector{kStartCommand, StreamDescriptor::Command::make< StreamDescriptor::Command::Tag::burst>( std::numeric_limits::min())})}; if (IOTraits::is_input) { sequences.emplace_back("DrainAll", std::vector{kStartCommand, kBurstCommand, kDrainOutAllCommand}); sequences.emplace_back( "DrainEarly", std::vector{kStartCommand, kBurstCommand, kDrainOutEarlyCommand}); } else { sequences.emplace_back("DrainUnspecified", std::vector{kStartCommand, kBurstCommand, kDrainInCommand}); } for (const auto& seq : sequences) { SCOPED_TRACE(std::string("Sequence ").append(seq.first)); LOG(DEBUG) << __func__ << ": Sequence " << seq.first; WithStream stream(portConfig); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); StreamLogicDriverInvalidCommand driver(seq.second); typename IOTraits::Worker worker(*stream.getContext(), &driver, stream.getEventReceiver()); LOG(DEBUG) << __func__ << ": starting worker..."; ASSERT_TRUE(worker.start()); LOG(DEBUG) << __func__ << ": joining worker..."; worker.join(); EXPECT_EQ("", driver.getUnexpectedStatuses()); } } }; using AudioStreamIn = AudioStream; using AudioStreamOut = AudioStream; #define TEST_IN_AND_OUT_STREAM(method_name) \ TEST_P(AudioStreamIn, method_name) { \ ASSERT_NO_FATAL_FAILURE(method_name()); \ } \ TEST_P(AudioStreamOut, method_name) { \ ASSERT_NO_FATAL_FAILURE(method_name()); \ } TEST_IN_AND_OUT_STREAM(CloseTwice); TEST_IN_AND_OUT_STREAM(GetStreamCommon); TEST_IN_AND_OUT_STREAM(OpenAllConfigs); TEST_IN_AND_OUT_STREAM(OpenInvalidBufferSize); TEST_IN_AND_OUT_STREAM(OpenInvalidDirection); TEST_IN_AND_OUT_STREAM(OpenOverMaxCount); TEST_IN_AND_OUT_STREAM(OpenTwiceSamePortConfig); TEST_IN_AND_OUT_STREAM(ResetPortConfigWithOpenStream); TEST_IN_AND_OUT_STREAM(SendInvalidCommand); TEST_IN_AND_OUT_STREAM(UpdateHwAvSyncId); TEST_IN_AND_OUT_STREAM(GetVendorParameters); TEST_IN_AND_OUT_STREAM(SetVendorParameters); namespace aidl::android::hardware::audio::core { std::ostream& operator<<(std::ostream& os, const IStreamIn::MicrophoneDirection& md) { os << toString(md); return os; } } // namespace aidl::android::hardware::audio::core TEST_P(AudioStreamIn, ActiveMicrophones) { std::vector micInfos; ScopedAStatus status = module->getMicrophones(&micInfos); if (!status.isOk()) { GTEST_SKIP() << "Microphone info is not supported"; } const auto ports = moduleConfig->getInputMixPorts(true /*attachedOnly*/); if (ports.empty()) { GTEST_SKIP() << "No input mix ports for attached devices"; } for (const auto& port : ports) { const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port); ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port"; WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); { // The port of the stream is not connected, thus the list of active mics must be empty. std::vector activeMics; EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics)); EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a " "non-empty list of active microphones"; } if (auto micDevicePorts = ModuleConfig::getBuiltInMicPorts( moduleConfig->getAttachedSourceDevicesPortsForMixPort(port)); !micDevicePorts.empty()) { auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(micDevicePorts[0]); WithAudioPatch patch(true /*isInput*/, stream.getPortConfig(), devicePortConfig); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); std::vector activeMics; EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics)); for (const auto& mic : activeMics) { EXPECT_NE(micInfos.end(), std::find_if(micInfos.begin(), micInfos.end(), [&](const auto& micInfo) { return micInfo.id == mic.id; })) << "active microphone \"" << mic.id << "\" is not listed in " << "microphone infos returned by the module: " << ::android::internal::ToString(micInfos); EXPECT_NE(0UL, mic.channelMapping.size()) << "No channels specified for the microphone \"" << mic.id << "\""; } } { // Now the port of the stream is not connected again, re-check that there are no // active microphones. std::vector activeMics; EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics)); EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a " "non-empty list of active microphones"; } } } TEST_P(AudioStreamIn, MicrophoneDirection) { using MD = IStreamIn::MicrophoneDirection; const auto ports = moduleConfig->getInputMixPorts(true /*attachedOnly*/); if (ports.empty()) { GTEST_SKIP() << "No input mix ports for attached devices"; } bool isSupported = false; for (const auto& port : ports) { const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port); ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port"; WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); EXPECT_NO_FATAL_FAILURE( TestAccessors(stream.get(), &IStreamIn::getMicrophoneDirection, &IStreamIn::setMicrophoneDirection, std::vector(enum_range().begin(), enum_range().end()), {}, &isSupported)); if (!isSupported) break; } if (!isSupported) { GTEST_SKIP() << "Microphone direction is not supported"; } } TEST_P(AudioStreamIn, MicrophoneFieldDimension) { const auto ports = moduleConfig->getInputMixPorts(true /*attachedOnly*/); if (ports.empty()) { GTEST_SKIP() << "No input mix ports for attached devices"; } bool isSupported = false; for (const auto& port : ports) { const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port); ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port"; WithStream stream(portConfig.value()); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); EXPECT_NO_FATAL_FAILURE(TestAccessors( stream.get(), &IStreamIn::getMicrophoneFieldDimension, &IStreamIn::setMicrophoneFieldDimension, {IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE, IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE / 2.0f, IStreamIn::MIC_FIELD_DIMENSION_NO_ZOOM, IStreamIn::MIC_FIELD_DIMENSION_MAX_ZOOM / 2.0f, IStreamIn::MIC_FIELD_DIMENSION_MAX_ZOOM}, {IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE * 2, IStreamIn::MIC_FIELD_DIMENSION_MAX_ZOOM * 2, IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE * 1.1f, IStreamIn::MIC_FIELD_DIMENSION_MAX_ZOOM * 1.1f, -INFINITY, INFINITY, -NAN, NAN}, &isSupported)); if (!isSupported) break; } if (!isSupported) { GTEST_SKIP() << "Microphone direction is not supported"; } } TEST_P(AudioStreamOut, OpenTwicePrimary) { const auto mixPorts = moduleConfig->getPrimaryMixPorts(true /*attachedOnly*/, true /*singlePort*/); if (mixPorts.empty()) { GTEST_SKIP() << "No primary mix port which could be routed to attached devices"; } const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *mixPorts.begin()); ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for the primary mix port"; EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value())); } TEST_P(AudioStreamOut, RequireOffloadInfo) { const auto offloadMixPorts = moduleConfig->getOffloadMixPorts(true /*attachedOnly*/, true /*singlePort*/); if (offloadMixPorts.empty()) { GTEST_SKIP() << "No mix port for compressed offload that could be routed to attached devices"; } const auto config = moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin()); ASSERT_TRUE(config.has_value()) << "No profiles specified for the compressed offload mix port"; WithAudioPortConfig portConfig(config.value()); ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get())); StreamDescriptor descriptor; std::shared_ptr ignored; aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args; args.portConfigId = portConfig.getId(); args.sourceMetadata = GenerateSourceMetadata(portConfig.get()); args.bufferSizeFrames = kDefaultBufferSizeFrames; aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret)) << "when no offload info is provided for a compressed offload mix port"; } TEST_P(AudioStreamOut, RequireAsyncCallback) { const auto nonBlockingMixPorts = moduleConfig->getNonBlockingMixPorts(true /*attachedOnly*/, true /*singlePort*/); if (nonBlockingMixPorts.empty()) { GTEST_SKIP() << "No mix port for non-blocking output that could be routed to attached devices"; } const auto config = moduleConfig->getSingleConfigForMixPort(false, *nonBlockingMixPorts.begin()); ASSERT_TRUE(config.has_value()) << "No profiles specified for the non-blocking mix port"; WithAudioPortConfig portConfig(config.value()); ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get())); StreamDescriptor descriptor; std::shared_ptr ignored; aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args; args.portConfigId = portConfig.getId(); args.sourceMetadata = GenerateSourceMetadata(portConfig.get()); args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig.get()); args.bufferSizeFrames = kDefaultBufferSizeFrames; aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret)) << "when no async callback is provided for a non-blocking mix port"; } class StreamLogicDefaultDriver : public StreamLogicDriver { public: explicit StreamLogicDefaultDriver(std::shared_ptr commands) : mCommands(commands) { mCommands->rewind(); } // The three methods below is intended to be called after the worker // thread has joined, thus no extra synchronization is needed. bool hasObservablePositionIncrease() const { return mObservablePositionIncrease; } bool hasRetrogradeObservablePosition() const { return mRetrogradeObservablePosition; } std::string getUnexpectedStateTransition() const { return mUnexpectedTransition; } bool done() override { return mCommands->done(); } TransitionTrigger getNextTrigger(int maxDataSize, int* actualSize) override { auto trigger = mCommands->getTrigger(); if (StreamDescriptor::Command* command = std::get_if(&trigger); command != nullptr) { if (command->getTag() == StreamDescriptor::Command::Tag::burst) { if (actualSize != nullptr) { // In the output scenario, reduce slightly the fmqByteCount to verify // that the HAL module always consumes all data from the MQ. if (maxDataSize > 1) maxDataSize--; *actualSize = maxDataSize; } command->set(maxDataSize); } else { if (actualSize != nullptr) *actualSize = 0; } } return trigger; } bool interceptRawReply(const StreamDescriptor::Reply&) override { return false; } bool processValidReply(const StreamDescriptor::Reply& reply) override { if (reply.observable.frames != StreamDescriptor::Position::UNKNOWN) { if (mPreviousFrames.has_value()) { if (reply.observable.frames > mPreviousFrames.value()) { mObservablePositionIncrease = true; } else if (reply.observable.frames < mPreviousFrames.value()) { mRetrogradeObservablePosition = true; } } mPreviousFrames = reply.observable.frames; } auto expected = mCommands->getExpectedStates(); if (expected.count(reply.state) == 0) { std::string s = std::string("Unexpected transition from the state ") .append(mPreviousState.has_value() ? toString(mPreviousState.value()) : "") .append(" to ") .append(toString(reply.state)) .append(" (expected one of ") .append(::android::internal::ToString(expected)) .append(") caused by the ") .append(toString(mCommands->getTrigger())); LOG(ERROR) << __func__ << ": " << s; mUnexpectedTransition = std::move(s); return false; } mCommands->advance(reply.state); mPreviousState = reply.state; return true; } protected: std::shared_ptr mCommands; std::optional mPreviousState; std::optional mPreviousFrames; bool mObservablePositionIncrease = false; bool mRetrogradeObservablePosition = false; std::string mUnexpectedTransition; }; enum { NAMED_CMD_NAME, NAMED_CMD_DELAY_MS, NAMED_CMD_STREAM_TYPE, NAMED_CMD_CMDS }; enum class StreamTypeFilter { ANY, SYNC, ASYNC }; using NamedCommandSequence = std::tuple>; enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ }; using StreamIoTestParameters = std::tuple; template class AudioStreamIo : public AudioCoreModuleBase, public testing::TestWithParam { public: void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpImpl(std::get(GetParam()))); ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); } void Run() { const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IOTraits::is_input); if (allPortConfigs.empty()) { GTEST_SKIP() << "No mix ports have attached devices"; } for (const auto& portConfig : allPortConfigs) { SCOPED_TRACE(portConfig.toString()); const bool isNonBlocking = IOTraits::is_input ? false : // TODO: Uncomment when support for asynchronous input is implemented. /*isBitPositionFlagSet( portConfig.flags.value().template get(), AudioInputFlags::NON_BLOCKING) :*/ isBitPositionFlagSet(portConfig.flags.value() .template get(), AudioOutputFlags::NON_BLOCKING); if (auto streamType = std::get(std::get(GetParam())); (isNonBlocking && streamType == StreamTypeFilter::SYNC) || (!isNonBlocking && streamType == StreamTypeFilter::ASYNC)) { continue; } WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug); delayTransientStates.flags().streamTransientStateDelayMs = std::get(std::get(GetParam())); ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get())); const auto& commandsAndStates = std::get(std::get(GetParam())); if (!std::get(GetParam())) { ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates)); } else { ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates)); } } } bool ValidateObservablePosition(const AudioPortConfig& devicePortConfig) { return !isTelephonyDeviceType( devicePortConfig.ext.get().device.type.type); } // Set up a patch first, then open a stream. void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig, std::shared_ptr commandsAndStates) { auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort( IOTraits::is_input, portConfig); ASSERT_FALSE(devicePorts.empty()); auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]); WithAudioPatch patch(IOTraits::is_input, portConfig, devicePortConfig); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); WithStream stream(patch.getPortConfig(IOTraits::is_input)); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); StreamLogicDefaultDriver driver(commandsAndStates); typename IOTraits::Worker worker(*stream.getContext(), &driver, stream.getEventReceiver()); LOG(DEBUG) << __func__ << ": starting worker..."; ASSERT_TRUE(worker.start()); LOG(DEBUG) << __func__ << ": joining worker..."; worker.join(); EXPECT_FALSE(worker.hasError()) << worker.getError(); EXPECT_EQ("", driver.getUnexpectedStateTransition()); if (ValidateObservablePosition(devicePortConfig)) { EXPECT_TRUE(driver.hasObservablePositionIncrease()); EXPECT_FALSE(driver.hasRetrogradeObservablePosition()); } } // Open a stream, then set up a patch for it. void RunStreamIoCommandsImplSeq2(const AudioPortConfig& portConfig, std::shared_ptr commandsAndStates) { WithStream stream(portConfig); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); StreamLogicDefaultDriver driver(commandsAndStates); typename IOTraits::Worker worker(*stream.getContext(), &driver, stream.getEventReceiver()); auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort( IOTraits::is_input, portConfig); ASSERT_FALSE(devicePorts.empty()); auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]); WithAudioPatch patch(IOTraits::is_input, stream.getPortConfig(), devicePortConfig); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); LOG(DEBUG) << __func__ << ": starting worker..."; ASSERT_TRUE(worker.start()); LOG(DEBUG) << __func__ << ": joining worker..."; worker.join(); EXPECT_FALSE(worker.hasError()) << worker.getError(); EXPECT_EQ("", driver.getUnexpectedStateTransition()); if (ValidateObservablePosition(devicePortConfig)) { EXPECT_TRUE(driver.hasObservablePositionIncrease()); EXPECT_FALSE(driver.hasRetrogradeObservablePosition()); } } }; using AudioStreamIoIn = AudioStreamIo; using AudioStreamIoOut = AudioStreamIo; #define TEST_IN_AND_OUT_STREAM_IO(method_name) \ TEST_P(AudioStreamIoIn, method_name) { \ ASSERT_NO_FATAL_FAILURE(method_name()); \ } \ TEST_P(AudioStreamIoOut, method_name) { \ ASSERT_NO_FATAL_FAILURE(method_name()); \ } TEST_IN_AND_OUT_STREAM_IO(Run); // Tests specific to audio patches. The fixure class is named 'AudioModulePatch' // to avoid clashing with 'AudioPatch' class. class AudioModulePatch : public AudioCoreModule { public: static std::string direction(bool isInput, bool capitalize) { return isInput ? (capitalize ? "Input" : "input") : (capitalize ? "Output" : "output"); } void SetUp() override { ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp()); ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); } void SetInvalidPatchHelper(int32_t expectedException, const std::vector& sources, const std::vector& sinks) { AudioPatch patch; patch.sourcePortConfigIds = sources; patch.sinkPortConfigIds = sinks; ASSERT_STATUS(expectedException, module->setAudioPatch(patch, &patch)) << "patch source ids: " << android::internal::ToString(sources) << "; sink ids: " << android::internal::ToString(sinks); } void ResetPortConfigUsedByPatch(bool isInput) { auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); if (srcSinkGroups.empty()) { GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; } auto srcSinkGroup = *srcSinkGroups.begin(); auto srcSink = *srcSinkGroup.second.begin(); WithAudioPatch patch(srcSink.first, srcSink.second); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); std::vector sourceAndSinkPortConfigIds(patch.get().sourcePortConfigIds); sourceAndSinkPortConfigIds.insert(sourceAndSinkPortConfigIds.end(), patch.get().sinkPortConfigIds.begin(), patch.get().sinkPortConfigIds.end()); for (const auto portConfigId : sourceAndSinkPortConfigIds) { EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(portConfigId)) << "port config ID " << portConfigId; } } void SetInvalidPatch(bool isInput) { auto srcSinkPair = moduleConfig->getRoutableSrcSinkPair(isInput); if (!srcSinkPair.has_value()) { GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; } WithAudioPortConfig srcPortConfig(srcSinkPair.value().first); ASSERT_NO_FATAL_FAILURE(srcPortConfig.SetUp(module.get())); WithAudioPortConfig sinkPortConfig(srcSinkPair.value().second); ASSERT_NO_FATAL_FAILURE(sinkPortConfig.SetUp(module.get())); { // Check that the pair can actually be used for setting up a patch. WithAudioPatch patch(srcPortConfig.get(), sinkPortConfig.get()); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); } EXPECT_NO_FATAL_FAILURE( SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {}, {sinkPortConfig.getId()})); EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper( EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId(), srcPortConfig.getId()}, {sinkPortConfig.getId()})); EXPECT_NO_FATAL_FAILURE( SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {})); EXPECT_NO_FATAL_FAILURE( SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {sinkPortConfig.getId(), sinkPortConfig.getId()})); std::set portConfigIds; ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); for (const auto portConfigId : GetNonExistentIds(portConfigIds)) { EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {portConfigId}, {sinkPortConfig.getId()})); EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {portConfigId})); } } void SetNonRoutablePatch(bool isInput) { auto srcSinkPair = moduleConfig->getNonRoutableSrcSinkPair(isInput); if (!srcSinkPair.has_value()) { GTEST_SKIP() << "All possible source/sink pairs are routable"; } WithAudioPatch patch(srcSinkPair.value().first, srcSinkPair.value().second); ASSERT_NO_FATAL_FAILURE(patch.SetUpPortConfigs(module.get())); EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, patch.SetUpNoChecks(module.get())) << "when setting up a patch from " << srcSinkPair.value().first.toString() << " to " << srcSinkPair.value().second.toString() << " that does not have a route"; } void SetPatch(bool isInput) { auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); if (srcSinkGroups.empty()) { GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; } for (const auto& srcSinkGroup : srcSinkGroups) { const auto& route = srcSinkGroup.first; std::vector> patches; for (const auto& srcSink : srcSinkGroup.second) { if (!route.isExclusive) { patches.push_back( std::make_unique(srcSink.first, srcSink.second)); EXPECT_NO_FATAL_FAILURE(patches[patches.size() - 1]->SetUp(module.get())); } else { WithAudioPatch patch(srcSink.first, srcSink.second); EXPECT_NO_FATAL_FAILURE(patch.SetUp(module.get())); } } } } void UpdatePatch(bool isInput) { auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); if (srcSinkGroups.empty()) { GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; } for (const auto& srcSinkGroup : srcSinkGroups) { for (const auto& srcSink : srcSinkGroup.second) { WithAudioPatch patch(srcSink.first, srcSink.second); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); AudioPatch ignored; EXPECT_NO_FATAL_FAILURE(module->setAudioPatch(patch.get(), &ignored)); } } } void UpdateInvalidPatchId(bool isInput) { auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); if (srcSinkGroups.empty()) { GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; } // First, set up a patch to ensure that its settings are accepted. auto srcSinkGroup = *srcSinkGroups.begin(); auto srcSink = *srcSinkGroup.second.begin(); WithAudioPatch patch(srcSink.first, srcSink.second); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); // Then use the same patch setting, except for having an invalid ID. std::set patchIds; ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds)); for (const auto patchId : GetNonExistentIds(patchIds)) { AudioPatch patchWithNonExistendId = patch.get(); patchWithNonExistendId.id = patchId; EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId)) << "patch ID " << patchId; } } }; // Not all tests require both directions, so parametrization would require // more abstractions. #define TEST_PATCH_BOTH_DIRECTIONS(method_name) \ TEST_P(AudioModulePatch, method_name##Input) { \ ASSERT_NO_FATAL_FAILURE(method_name(true)); \ } \ TEST_P(AudioModulePatch, method_name##Output) { \ ASSERT_NO_FATAL_FAILURE(method_name(false)); \ } TEST_PATCH_BOTH_DIRECTIONS(ResetPortConfigUsedByPatch); TEST_PATCH_BOTH_DIRECTIONS(SetInvalidPatch); TEST_PATCH_BOTH_DIRECTIONS(SetNonRoutablePatch); TEST_PATCH_BOTH_DIRECTIONS(SetPatch); TEST_PATCH_BOTH_DIRECTIONS(UpdateInvalidPatchId); TEST_PATCH_BOTH_DIRECTIONS(UpdatePatch); TEST_P(AudioModulePatch, ResetInvalidPatchId) { std::set patchIds; ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds)); for (const auto patchId : GetNonExistentIds(patchIds)) { EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->resetAudioPatch(patchId)) << "patch ID " << patchId; } } class AudioCoreSoundDose : public AudioCoreModuleBase, public testing::TestWithParam { public: class NoOpHalSoundDoseCallback : public ISoundDose::BnHalSoundDoseCallback { public: ndk::ScopedAStatus onMomentaryExposureWarning(float in_currentDbA, const AudioDevice& in_audioDevice) override; ndk::ScopedAStatus onNewMelValues( const ISoundDose::IHalSoundDoseCallback::MelRecord& in_melRecord, const AudioDevice& in_audioDevice) override; }; void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam())); ASSERT_IS_OK(module->getSoundDose(&soundDose)); callback = ndk::SharedRefBase::make(); } void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); } std::shared_ptr soundDose; std::shared_ptr callback; }; ndk::ScopedAStatus AudioCoreSoundDose::NoOpHalSoundDoseCallback::onMomentaryExposureWarning( float in_currentDbA, const AudioDevice& in_audioDevice) { // Do nothing (void)in_currentDbA; (void)in_audioDevice; LOG(INFO) << "NoOpHalSoundDoseCallback::onMomentaryExposureWarning called"; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus AudioCoreSoundDose::NoOpHalSoundDoseCallback::onNewMelValues( const ISoundDose::IHalSoundDoseCallback::MelRecord& in_melRecord, const AudioDevice& in_audioDevice) { // Do nothing (void)in_melRecord; (void)in_audioDevice; LOG(INFO) << "NoOpHalSoundDoseCallback::onNewMelValues called"; return ndk::ScopedAStatus::ok(); } TEST_P(AudioCoreSoundDose, GetSetOutputRs2) { if (soundDose == nullptr) { GTEST_SKIP() << "SoundDose is not supported"; } bool isSupported = false; EXPECT_NO_FATAL_FAILURE(TestAccessors(soundDose.get(), &ISoundDose::getOutputRs2, &ISoundDose::setOutputRs2, /*validValues=*/{80.f, 90.f, 100.f}, /*invalidValues=*/{79.f, 101.f}, &isSupported)); EXPECT_TRUE(isSupported) << "Getting/Setting RS2 must be supported"; } TEST_P(AudioCoreSoundDose, CheckDefaultRs2Value) { if (soundDose == nullptr) { GTEST_SKIP() << "SoundDose is not supported"; } float rs2Value; ASSERT_IS_OK(soundDose->getOutputRs2(&rs2Value)); EXPECT_EQ(rs2Value, ISoundDose::DEFAULT_MAX_RS2); } TEST_P(AudioCoreSoundDose, RegisterSoundDoseCallbackTwiceThrowsException) { if (soundDose == nullptr) { GTEST_SKIP() << "SoundDose is not supported"; } ASSERT_IS_OK(soundDose->registerSoundDoseCallback(callback)); EXPECT_STATUS(EX_ILLEGAL_STATE, soundDose->registerSoundDoseCallback(callback)) << "Registering sound dose callback twice should throw EX_ILLEGAL_STATE"; } TEST_P(AudioCoreSoundDose, RegisterSoundDoseNullCallbackThrowsException) { if (soundDose == nullptr) { GTEST_SKIP() << "SoundDose is not supported"; } EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, soundDose->registerSoundDoseCallback(nullptr)) << "Registering nullptr sound dose callback should throw EX_ILLEGAL_ARGUMENT"; } INSTANTIATE_TEST_SUITE_P(AudioCoreModuleTest, AudioCoreModule, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreModule); INSTANTIATE_TEST_SUITE_P(AudioCoreTelephonyTest, AudioCoreTelephony, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreTelephony); INSTANTIATE_TEST_SUITE_P(AudioStreamInTest, AudioStreamIn, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIn); INSTANTIATE_TEST_SUITE_P(AudioStreamOutTest, AudioStreamOut, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut); INSTANTIATE_TEST_SUITE_P(AudioCoreSoundDoseTest, AudioCoreSoundDose, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreSoundDose); // This is the value used in test sequences for which the test needs to ensure // that the HAL stays in a transient state long enough to receive the next command. static const int kStreamTransientStateTransitionDelayMs = 3000; // TODO: Add async test cases for input once it is implemented. std::shared_ptr makeBurstCommands(bool isSync, size_t burstCount) { const auto burst = isSync ? std::vector{std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)} : std::vector{ std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING), std::make_pair(kTransferReadyEvent, StreamDescriptor::State::ACTIVE)}; std::vector result{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE)}; for (size_t i = 0; i < burstCount; ++i) { result.insert(result.end(), burst.begin(), burst.end()); } return std::make_shared(result); } static const NamedCommandSequence kReadSeq = std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true, 3)); static const NamedCommandSequence kWriteSyncSeq = std::make_tuple( std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true, 3)); static const NamedCommandSequence kWriteAsyncSeq = std::make_tuple( std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false, 3)); std::shared_ptr makeAsyncDrainCommands(bool isInput) { return std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, isInput ? StreamDescriptor::State::ACTIVE : StreamDescriptor::State::TRANSFERRING), std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand, StreamDescriptor::State::DRAINING), isInput ? std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE) : std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING), std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand, StreamDescriptor::State::DRAINING)}); } static const NamedCommandSequence kWriteDrainAsyncSeq = std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, makeAsyncDrainCommands(false)); static const NamedCommandSequence kDrainInSeq = std::make_tuple( std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true)); std::shared_ptr makeDrainOutCommands(bool isSync) { return std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING), std::make_pair(isSync ? TransitionTrigger(kGetStatusCommand) : TransitionTrigger(kDrainReadyEvent), StreamDescriptor::State::IDLE)}); } static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple( std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true)); static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple( std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false)); std::shared_ptr makeDrainOutPauseCommands(bool isSync) { return std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE : StreamDescriptor::State::TRANSFERRING), std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING), std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING), std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::PAUSED : StreamDescriptor::State::TRANSFER_PAUSED)}); } static const NamedCommandSequence kDrainPauseOutSyncSeq = std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC, makeDrainOutPauseCommands(true)); static const NamedCommandSequence kDrainPauseOutAsyncSeq = std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, makeDrainOutPauseCommands(false)); // This sequence also verifies that the capture / presentation position is not reset on standby. std::shared_ptr makeStandbyCommands(bool isInput, bool isSync) { return std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY), std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, isInput || isSync ? StreamDescriptor::State::ACTIVE : StreamDescriptor::State::TRANSFERRING), std::make_pair(kPauseCommand, isInput || isSync ? StreamDescriptor::State::PAUSED : StreamDescriptor::State::TRANSFER_PAUSED), std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY : StreamDescriptor::State::IDLE), std::make_pair(isInput ? kGetStatusCommand : kStandbyCommand, // no-op for input StreamDescriptor::State::STANDBY), std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, isInput || isSync ? StreamDescriptor::State::ACTIVE : StreamDescriptor::State::TRANSFERRING)}); } static const NamedCommandSequence kStandbyInSeq = std::make_tuple( std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false)); static const NamedCommandSequence kStandbyOutSyncSeq = std::make_tuple( std::string("Standby"), 0, StreamTypeFilter::SYNC, makeStandbyCommands(false, true)); static const NamedCommandSequence kStandbyOutAsyncSeq = std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, makeStandbyCommands(false, false)); static const NamedCommandSequence kPauseInSeq = std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY, std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)})); static const NamedCommandSequence kPauseOutSyncSeq = std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC, std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED), std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)})); /* TODO: Figure out a better way for testing sync/async bursts static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple( std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING), std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED), std::make_pair(kStartCommand, StreamDescriptor::State::TRANSFERRING), std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED), std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAIN_PAUSED), std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFER_PAUSED)})); */ std::shared_ptr makeFlushCommands(bool isInput, bool isSync) { return std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, isInput || isSync ? StreamDescriptor::State::ACTIVE : StreamDescriptor::State::TRANSFERRING), std::make_pair(kPauseCommand, isInput || isSync ? StreamDescriptor::State::PAUSED : StreamDescriptor::State::TRANSFER_PAUSED), std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY : StreamDescriptor::State::IDLE)}); } static const NamedCommandSequence kFlushInSeq = std::make_tuple( std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false)); static const NamedCommandSequence kFlushOutSyncSeq = std::make_tuple( std::string("Flush"), 0, StreamTypeFilter::SYNC, makeFlushCommands(false, true)); static const NamedCommandSequence kFlushOutAsyncSeq = std::make_tuple(std::string("Flush"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, makeFlushCommands(false, false)); std::shared_ptr makeDrainPauseFlushOutCommands(bool isSync) { return std::make_shared(std::vector{ std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE : StreamDescriptor::State::TRANSFERRING), std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING), std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)}); } static const NamedCommandSequence kDrainPauseFlushOutSyncSeq = std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC, makeDrainPauseFlushOutCommands(true)); static const NamedCommandSequence kDrainPauseFlushOutAsyncSeq = std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, makeDrainPauseFlushOutCommands(false)); // Note, this isn't the "official" enum printer, it is only used to make the test name suffix. std::string PrintStreamFilterToString(StreamTypeFilter filter) { switch (filter) { case StreamTypeFilter::ANY: return ""; case StreamTypeFilter::SYNC: return "Sync"; case StreamTypeFilter::ASYNC: return "Async"; } return std::string("Unknown").append(std::to_string(static_cast(filter))); } std::string GetStreamIoTestName(const testing::TestParamInfo& info) { return android::PrintInstanceNameToString( testing::TestParamInfo{std::get(info.param), info.index}) .append("_") .append(std::get(std::get(info.param))) .append(PrintStreamFilterToString( std::get(std::get(info.param)))) .append("_SetupSeq") .append(std::get(info.param) ? "2" : "1"); } INSTANTIATE_TEST_SUITE_P( AudioStreamIoInTest, AudioStreamIoIn, testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), testing::Values(kReadSeq, kDrainInSeq, kStandbyInSeq, kPauseInSeq, kFlushInSeq), testing::Values(false, true)), GetStreamIoTestName); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIoIn); INSTANTIATE_TEST_SUITE_P( AudioStreamIoOutTest, AudioStreamIoOut, testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), testing::Values(kWriteSyncSeq, kWriteAsyncSeq, kWriteDrainAsyncSeq, kDrainOutSyncSeq, kDrainPauseOutSyncSeq, kDrainPauseOutAsyncSeq, kStandbyOutSyncSeq, kStandbyOutAsyncSeq, kPauseOutSyncSeq, // kPauseOutAsyncSeq, kFlushOutSyncSeq, kFlushOutAsyncSeq, kDrainPauseFlushOutSyncSeq, kDrainPauseFlushOutAsyncSeq), testing::Values(false, true)), GetStreamIoTestName); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIoOut); INSTANTIATE_TEST_SUITE_P(AudioPatchTest, AudioModulePatch, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModulePatch); class TestExecutionTracer : public ::testing::EmptyTestEventListener { public: void OnTestStart(const ::testing::TestInfo& test_info) override { TraceTestState("Started", test_info); } void OnTestEnd(const ::testing::TestInfo& test_info) override { TraceTestState("Completed", test_info); } private: static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info) { LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name(); } }; int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer()); android::base::SetMinimumLogSeverity(::android::base::DEBUG); ABinderProcess_setThreadPoolMaxThreadCount(1); ABinderProcess_startThreadPool(); return RUN_ALL_TESTS(); }