/* * Copyright (C) 2017 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. */ #define EGMOCK_VERBOSE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace hardware { namespace broadcastradio { namespace V2_0 { namespace vts { using namespace std::chrono_literals; using std::unordered_set; using std::vector; using testing::_; using testing::AnyNumber; using testing::ByMove; using testing::DoAll; using testing::Invoke; using testing::SaveArg; using broadcastradio::vts::CallBarrier; using broadcastradio::vts::clearAndWait; using utils::make_identifier; using utils::make_selector_amfm; namespace timeout { static constexpr auto tune = 30s; static constexpr auto programListScan = 5min; } // namespace timeout static constexpr auto gTuneWorkaround = 200ms; static const ConfigFlag gConfigFlagValues[] = { ConfigFlag::FORCE_MONO, ConfigFlag::FORCE_ANALOG, ConfigFlag::FORCE_DIGITAL, ConfigFlag::RDS_AF, ConfigFlag::RDS_REG, ConfigFlag::DAB_DAB_LINKING, ConfigFlag::DAB_FM_LINKING, ConfigFlag::DAB_DAB_SOFT_LINKING, ConfigFlag::DAB_FM_SOFT_LINKING, }; class TunerCallbackMock : public ITunerCallback { public: TunerCallbackMock(); MOCK_METHOD2(onTuneFailed, Return(Result, const ProgramSelector&)); MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChanged_, Return(const ProgramInfo&)); virtual Return onCurrentProgramInfoChanged(const ProgramInfo& info); Return onProgramListUpdated(const ProgramListChunk& chunk); MOCK_METHOD1(onAntennaStateChange, Return(bool connected)); MOCK_METHOD1(onParametersUpdated, Return(const hidl_vec& parameters)); MOCK_TIMEOUT_METHOD0(onProgramListReady, void()); std::mutex mLock; utils::ProgramInfoSet mProgramList; }; struct AnnouncementListenerMock : public IAnnouncementListener { MOCK_METHOD1(onListUpdated, Return(const hidl_vec&)); }; class BroadcastRadioHalTest : public ::testing::TestWithParam { protected: virtual void SetUp() override; virtual void TearDown() override; bool openSession(); bool getAmFmRegionConfig(bool full, AmFmRegionConfig* config); std::optional getProgramList(); sp mModule; Properties mProperties; sp mSession; sp mCallback = new TunerCallbackMock(); }; static void printSkipped(std::string msg) { const auto testInfo = testing::UnitTest::GetInstance()->current_test_info(); std::cout << "[ SKIPPED ] " << testInfo->test_case_name() << "." << testInfo->name() << std::endl; std::cout << msg << std::endl; } MATCHER_P(InfoHasId, id, std::string(negation ? "does not contain" : "contains") + " " + toString(id)) { auto ids = utils::getAllIds(arg.selector, utils::getType(id)); return ids.end() != find(ids.begin(), ids.end(), id.value); } TunerCallbackMock::TunerCallbackMock() { EXPECT_TIMEOUT_CALL(*this, onCurrentProgramInfoChanged_, _).Times(AnyNumber()); // we expect the antenna is connected through the whole test EXPECT_CALL(*this, onAntennaStateChange(false)).Times(0); } Return TunerCallbackMock::onCurrentProgramInfoChanged(const ProgramInfo& info) { for (auto&& id : info.selector) { EXPECT_NE(IdentifierType::INVALID, utils::getType(id)); } auto logically = utils::getType(info.logicallyTunedTo); /* This field is required for currently tuned program and should be INVALID * for entries from the program list. */ EXPECT_TRUE( logically == IdentifierType::AMFM_FREQUENCY || logically == IdentifierType::RDS_PI || logically == IdentifierType::HD_STATION_ID_EXT || logically == IdentifierType::DAB_SID_EXT || logically == IdentifierType::DRMO_SERVICE_ID || logically == IdentifierType::SXM_SERVICE_ID || (logically >= IdentifierType::VENDOR_START && logically <= IdentifierType::VENDOR_END) || logically > IdentifierType::SXM_CHANNEL); auto physically = utils::getType(info.physicallyTunedTo); // ditto (see "logically" above) EXPECT_TRUE( physically == IdentifierType::AMFM_FREQUENCY || physically == IdentifierType::DAB_ENSEMBLE || physically == IdentifierType::DRMO_FREQUENCY || physically == IdentifierType::SXM_CHANNEL || (physically >= IdentifierType::VENDOR_START && physically <= IdentifierType::VENDOR_END) || physically > IdentifierType::SXM_CHANNEL); if (logically == IdentifierType::AMFM_FREQUENCY) { auto ps = utils::getMetadataString(info, MetadataKey::RDS_PS); if (ps.has_value()) { EXPECT_NE("", android::base::Trim(*ps)) << "Don't use empty RDS_PS as an indicator of missing RSD PS data."; } } return onCurrentProgramInfoChanged_(info); } Return TunerCallbackMock::onProgramListUpdated(const ProgramListChunk& chunk) { std::lock_guard lk(mLock); updateProgramList(mProgramList, chunk); if (chunk.complete) onProgramListReady(); return {}; } void BroadcastRadioHalTest::SetUp() { EXPECT_EQ(nullptr, mModule.get()) << "Module is already open"; // lookup HIDL service (radio module) mModule = IBroadcastRadio::getService(GetParam()); ASSERT_NE(nullptr, mModule.get()) << "Couldn't find broadcast radio HAL implementation"; // get module properties auto propResult = mModule->getProperties([&](const Properties& p) { mProperties = p; }); ASSERT_TRUE(propResult.isOk()); EXPECT_FALSE(mProperties.maker.empty()); EXPECT_FALSE(mProperties.product.empty()); EXPECT_GT(mProperties.supportedIdentifierTypes.size(), 0u); } void BroadcastRadioHalTest::TearDown() { mSession.clear(); mModule.clear(); clearAndWait(mCallback, 1s); } bool BroadcastRadioHalTest::openSession() { EXPECT_EQ(nullptr, mSession.get()) << "Session is already open"; Result halResult = Result::UNKNOWN_ERROR; auto openCb = [&](Result result, const sp& session) { halResult = result; if (result != Result::OK) return; mSession = session; }; auto hidlResult = mModule->openSession(mCallback, openCb); EXPECT_TRUE(hidlResult.isOk()); EXPECT_EQ(Result::OK, halResult); EXPECT_NE(nullptr, mSession.get()); return nullptr != mSession.get(); } bool BroadcastRadioHalTest::getAmFmRegionConfig(bool full, AmFmRegionConfig* config) { auto halResult = Result::UNKNOWN_ERROR; auto cb = [&](Result result, AmFmRegionConfig configCb) { halResult = result; if (config) *config = configCb; }; auto hidlResult = mModule->getAmFmRegionConfig(full, cb); EXPECT_TRUE(hidlResult.isOk()); if (halResult == Result::NOT_SUPPORTED) return false; EXPECT_EQ(Result::OK, halResult); return halResult == Result::OK; } std::optional BroadcastRadioHalTest::getProgramList() { EXPECT_TIMEOUT_CALL(*mCallback, onProgramListReady).Times(AnyNumber()); auto startResult = mSession->startProgramListUpdates({}); if (startResult == Result::NOT_SUPPORTED) { printSkipped("Program list not supported"); return std::nullopt; } EXPECT_EQ(Result::OK, startResult); if (startResult != Result::OK) return std::nullopt; EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onProgramListReady, timeout::programListScan); auto stopResult = mSession->stopProgramListUpdates(); EXPECT_TRUE(stopResult.isOk()); return mCallback->mProgramList; } /** * Test session opening. * * Verifies that: * - the method succeeds on a first and subsequent calls; * - the method succeeds when called for the second time without * closing previous session. */ TEST_P(BroadcastRadioHalTest, OpenSession) { // simply open session for the first time ASSERT_TRUE(openSession()); // drop (without explicit close) and re-open the session mSession.clear(); ASSERT_TRUE(openSession()); // open the second session (the first one should be forcibly closed) auto secondSession = mSession; mSession.clear(); ASSERT_TRUE(openSession()); } static bool isValidAmFmFreq(uint64_t freq) { auto id = utils::make_identifier(IdentifierType::AMFM_FREQUENCY, freq); return utils::isValid(id); } static void validateRange(const AmFmBandRange& range) { EXPECT_TRUE(isValidAmFmFreq(range.lowerBound)); EXPECT_TRUE(isValidAmFmFreq(range.upperBound)); EXPECT_LT(range.lowerBound, range.upperBound); EXPECT_GT(range.spacing, 0u); EXPECT_EQ(0u, (range.upperBound - range.lowerBound) % range.spacing); } static bool supportsFM(const AmFmRegionConfig& config) { for (auto&& range : config.ranges) { if (utils::getBand(range.lowerBound) == utils::FrequencyBand::FM) return true; } return false; } /** * Test fetching AM/FM regional configuration. * * Verifies that: * - AM/FM regional configuration is either set at startup or not supported at all by the hardware; * - there is at least one AM/FM band configured; * - FM Deemphasis and RDS are correctly configured for FM-capable radio; * - all channel grids (frequency ranges and spacings) are valid; * - seek spacing is a multiple of the manual spacing value. */ TEST_P(BroadcastRadioHalTest, GetAmFmRegionConfig) { AmFmRegionConfig config; bool supported = getAmFmRegionConfig(false, &config); if (!supported) { printSkipped("AM/FM not supported"); return; } EXPECT_GT(config.ranges.size(), 0u); EXPECT_LE(popcountll(config.fmDeemphasis), 1); EXPECT_LE(popcountll(config.fmRds), 1); for (auto&& range : config.ranges) { validateRange(range); EXPECT_EQ(0u, range.scanSpacing % range.spacing); EXPECT_GE(range.scanSpacing, range.spacing); } if (supportsFM(config)) { EXPECT_EQ(popcountll(config.fmDeemphasis), 1); } } /** * Test fetching AM/FM regional capabilities. * * Verifies that: * - AM/FM regional capabilities are either available or not supported at all by the hardware; * - there is at least one AM/FM range supported; * - there is at least one de-emphasis filter mode supported for FM-capable radio; * - all channel grids (frequency ranges and spacings) are valid; * - seek spacing is not set. */ TEST_P(BroadcastRadioHalTest, GetAmFmRegionConfigCapabilities) { AmFmRegionConfig config; bool supported = getAmFmRegionConfig(true, &config); if (!supported) { printSkipped("AM/FM not supported"); return; } EXPECT_GT(config.ranges.size(), 0u); for (auto&& range : config.ranges) { validateRange(range); EXPECT_EQ(0u, range.scanSpacing); } if (supportsFM(config)) { EXPECT_GE(popcountll(config.fmDeemphasis), 1); } } /** * Test fetching DAB regional configuration. * * Verifies that: * - DAB regional configuration is either set at startup or not supported at all by the hardware; * - all channel labels match correct format; * - all channel frequencies are in correct range. */ TEST_P(BroadcastRadioHalTest, GetDabRegionConfig) { Result halResult; hidl_vec config; auto cb = [&](Result result, hidl_vec configCb) { halResult = result; config = configCb; }; auto hidlResult = mModule->getDabRegionConfig(cb); ASSERT_TRUE(hidlResult.isOk()); if (halResult == Result::NOT_SUPPORTED) { printSkipped("DAB not supported"); return; } ASSERT_EQ(Result::OK, halResult); std::regex re("^[A-Z0-9][A-Z0-9 ]{0,5}[A-Z0-9]$"); // double-check correctness of the test ASSERT_TRUE(std::regex_match("5A", re)); ASSERT_FALSE(std::regex_match("5a", re)); ASSERT_FALSE(std::regex_match("1234ABCD", re)); ASSERT_TRUE(std::regex_match("CN 12D", re)); ASSERT_FALSE(std::regex_match(" 5A", re)); for (auto&& entry : config) { EXPECT_TRUE(std::regex_match(std::string(entry.label), re)); auto id = utils::make_identifier(IdentifierType::DAB_FREQUENCY, entry.frequency); EXPECT_TRUE(utils::isValid(id)); } } /** * Test tuning with FM selector. * * Verifies that: * - if AM/FM selector is not supported, the method returns NOT_SUPPORTED; * - if it is supported, the method succeeds; * - after a successful tune call, onCurrentProgramInfoChanged callback is * invoked carrying a proper selector; * - program changes exactly to what was requested. */ TEST_P(BroadcastRadioHalTest, FmTune) { ASSERT_TRUE(openSession()); uint64_t freq = 90900; // 90.9 FM auto sel = make_selector_amfm(freq); /* TODO(b/69958777): there is a race condition between tune() and onCurrentProgramInfoChanged * callback setting infoCb, because egmock cannot distinguish calls with different matchers * (there is one here and one in callback constructor). * * This sleep workaround will fix default implementation, but the real HW tests will still be * flaky. We probably need to implement egmock alternative based on actions. */ std::this_thread::sleep_for(gTuneWorkaround); // try tuning ProgramInfo infoCb = {}; EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged_, InfoHasId(utils::make_identifier(IdentifierType::AMFM_FREQUENCY, freq))) .Times(AnyNumber()) .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(Void())))) .WillRepeatedly(testing::InvokeWithoutArgs([] { return Void(); })); auto result = mSession->tune(sel); // expect a failure if it's not supported if (!utils::isSupported(mProperties, sel)) { EXPECT_EQ(Result::NOT_SUPPORTED, result); return; } // expect a callback if it succeeds EXPECT_EQ(Result::OK, result); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged_, timeout::tune); LOG(DEBUG) << "current program info: " << toString(infoCb); // it should tune exactly to what was requested auto freqs = utils::getAllIds(infoCb.selector, IdentifierType::AMFM_FREQUENCY); EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq)); } /** * Test tuning with invalid selectors. * * Verifies that: * - if the selector is not supported, it's ignored; * - if it is supported, an invalid value results with INVALID_ARGUMENTS; */ TEST_P(BroadcastRadioHalTest, TuneFailsWithInvalid) { ASSERT_TRUE(openSession()); vector invalid = { make_identifier(IdentifierType::AMFM_FREQUENCY, 0), make_identifier(IdentifierType::RDS_PI, 0x10000), make_identifier(IdentifierType::HD_STATION_ID_EXT, 0x100000000), make_identifier(IdentifierType::DAB_SID_EXT, 0), make_identifier(IdentifierType::DRMO_SERVICE_ID, 0x100000000), make_identifier(IdentifierType::SXM_SERVICE_ID, 0x100000000), }; for (auto&& id : invalid) { ProgramSelector sel{id, {}}; auto result = mSession->tune(sel); if (utils::isSupported(mProperties, sel)) { EXPECT_EQ(Result::INVALID_ARGUMENTS, result); } else { EXPECT_EQ(Result::NOT_SUPPORTED, result); } } } /** * Test tuning with DAB selector. * * Verifies that: * - if DAB selector is not supported, the method returns NOT_SUPPORTED; * - if it is supported, the method succeeds; * - after a successful tune call, onCurrentProgramInfoChanged callback is * invoked carrying a proper selector; * - program changes exactly to what was requested. */ TEST_P(BroadcastRadioHalTest, DabTune) { ASSERT_TRUE(openSession()); ProgramSelector sel = {}; uint64_t freq = 178352; sel.primaryId = make_identifier(IdentifierType::DAB_FREQUENCY,freq); std::this_thread::sleep_for(gTuneWorkaround); // try tuning ProgramInfo infoCb = {}; EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged_, InfoHasId(utils::make_identifier(IdentifierType::DAB_FREQUENCY, freq))) .Times(AnyNumber()) .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(Void())))); auto result = mSession->tune(sel); // expect a failure if it's not supported if (!utils::isSupported(mProperties, sel)) { EXPECT_EQ(Result::NOT_SUPPORTED, result); return; } // expect a callback if it succeeds EXPECT_EQ(Result::OK, result); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged_, timeout::tune); LOG(DEBUG) << "current program info: " << toString(infoCb); // it should tune exactly to what was requested auto freqs = utils::getAllIds(infoCb.selector, IdentifierType::DAB_FREQUENCY); EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq)); } /** * Test tuning with empty program selector. * * Verifies that: * - tune fails with NOT_SUPPORTED when program selector is not initialized. */ TEST_P(BroadcastRadioHalTest, TuneFailsWithEmpty) { ASSERT_TRUE(openSession()); // Program type is 1-based, so 0 will always be invalid. ProgramSelector sel = {}; auto result = mSession->tune(sel); ASSERT_EQ(Result::NOT_SUPPORTED, result); } /** * Test seeking to next/prev station via ITunerSession::scan(). * * Verifies that: * - the method succeeds; * - the program info is changed within timeout::tune; * - works both directions and with or without skipping sub-channel. */ TEST_P(BroadcastRadioHalTest, Seek) { ASSERT_TRUE(openSession()); // TODO(b/69958777): see FmTune workaround std::this_thread::sleep_for(gTuneWorkaround); EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged_, _).Times(AnyNumber()); auto result = mSession->scan(true /* up */, true /* skip subchannel */); if (result == Result::NOT_SUPPORTED) { printSkipped("seek not supported"); return; } EXPECT_EQ(Result::OK, result); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged_, timeout::tune); EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged_, _).Times(AnyNumber()); result = mSession->scan(false /* down */, false /* don't skip subchannel */); EXPECT_EQ(Result::OK, result); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged_, timeout::tune); } /** * Test step operation. * * Verifies that: * - the method succeeds or returns NOT_SUPPORTED; * - the program info is changed within timeout::tune if the method succeeded; * - works both directions. */ TEST_P(BroadcastRadioHalTest, Step) { ASSERT_TRUE(openSession()); // TODO(b/69958777): see FmTune workaround std::this_thread::sleep_for(gTuneWorkaround); EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged_, _).Times(AnyNumber()); auto result = mSession->step(true /* up */); if (result == Result::NOT_SUPPORTED) { printSkipped("step not supported"); return; } EXPECT_EQ(Result::OK, result); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged_, timeout::tune); EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged_, _).Times(AnyNumber()); result = mSession->step(false /* down */); EXPECT_EQ(Result::OK, result); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged_, timeout::tune); } /** * Test tune cancellation. * * Verifies that: * - the method does not crash after being invoked multiple times. */ TEST_P(BroadcastRadioHalTest, Cancel) { ASSERT_TRUE(openSession()); for (int i = 0; i < 10; i++) { auto result = mSession->scan(true /* up */, true /* skip subchannel */); if (result == Result::NOT_SUPPORTED) { printSkipped("cancel is skipped because of seek not supported"); return; } ASSERT_EQ(Result::OK, result); auto cancelResult = mSession->cancel(); ASSERT_TRUE(cancelResult.isOk()); } } /** * Test IBroadcastRadio::get|setParameters() methods called with no parameters. * * Verifies that: * - callback is called for empty parameters set. */ TEST_P(BroadcastRadioHalTest, NoParameters) { ASSERT_TRUE(openSession()); hidl_vec halResults = {}; bool wasCalled = false; auto cb = [&](hidl_vec results) { wasCalled = true; halResults = results; }; auto hidlResult = mSession->setParameters({}, cb); ASSERT_TRUE(hidlResult.isOk()); ASSERT_TRUE(wasCalled); ASSERT_EQ(0u, halResults.size()); wasCalled = false; hidlResult = mSession->getParameters({}, cb); ASSERT_TRUE(hidlResult.isOk()); ASSERT_TRUE(wasCalled); ASSERT_EQ(0u, halResults.size()); } /** * Test IBroadcastRadio::get|setParameters() methods called with unknown parameters. * * Verifies that: * - unknown parameters are ignored; * - callback is called also for empty results set. */ TEST_P(BroadcastRadioHalTest, UnknownParameters) { ASSERT_TRUE(openSession()); hidl_vec halResults = {}; bool wasCalled = false; auto cb = [&](hidl_vec results) { wasCalled = true; halResults = results; }; auto hidlResult = mSession->setParameters({{"com.google.unknown", "dummy"}}, cb); ASSERT_TRUE(hidlResult.isOk()); ASSERT_TRUE(wasCalled); ASSERT_EQ(0u, halResults.size()); wasCalled = false; hidlResult = mSession->getParameters({{"com.google.unknown*", "dummy"}}, cb); ASSERT_TRUE(hidlResult.isOk()); ASSERT_TRUE(wasCalled); ASSERT_EQ(0u, halResults.size()); } /** * Test session closing. * * Verifies that: * - the method does not crash after being invoked multiple times. */ TEST_P(BroadcastRadioHalTest, Close) { ASSERT_TRUE(openSession()); for (int i = 0; i < 10; i++) { auto cancelResult = mSession->close(); ASSERT_TRUE(cancelResult.isOk()); } } /** * Test geting image of invalid ID. * * Verifies that: * - getImage call handles argument 0 gracefully. */ TEST_P(BroadcastRadioHalTest, GetNoImage) { size_t len = 0; auto result = mModule->getImage(0, [&](hidl_vec rawImage) { len = rawImage.size(); }); ASSERT_TRUE(result.isOk()); ASSERT_EQ(0u, len); } /** * Test getting config flags. * * Verifies that: * - isConfigFlagSet either succeeds or ends with NOT_SUPPORTED or INVALID_STATE; * - call success or failure is consistent with setConfigFlag. */ TEST_P(BroadcastRadioHalTest, FetchConfigFlags) { ASSERT_TRUE(openSession()); for (auto flag : gConfigFlagValues) { auto halResult = Result::UNKNOWN_ERROR; auto cb = [&](Result result, bool) { halResult = result; }; auto hidlResult = mSession->isConfigFlagSet(flag, cb); EXPECT_TRUE(hidlResult.isOk()); if (halResult != Result::NOT_SUPPORTED && halResult != Result::INVALID_STATE) { ASSERT_EQ(Result::OK, halResult); } // set must fail or succeed the same way as get auto setResult = mSession->setConfigFlag(flag, false); EXPECT_EQ(halResult, setResult); setResult = mSession->setConfigFlag(flag, true); EXPECT_EQ(halResult, setResult); } } /** * Test setting config flags. * * Verifies that: * - setConfigFlag either succeeds or ends with NOT_SUPPORTED or INVALID_STATE; * - isConfigFlagSet reflects the state requested immediately after the set call. */ TEST_P(BroadcastRadioHalTest, SetConfigFlags) { ASSERT_TRUE(openSession()); auto get = [&](ConfigFlag flag) { auto halResult = Result::UNKNOWN_ERROR; bool gotValue = false; auto cb = [&](Result result, bool value) { halResult = result; gotValue = value; }; auto hidlResult = mSession->isConfigFlagSet(flag, cb); EXPECT_TRUE(hidlResult.isOk()); EXPECT_EQ(Result::OK, halResult); return gotValue; }; for (auto flag : gConfigFlagValues) { auto result = mSession->setConfigFlag(flag, false); if (result == Result::NOT_SUPPORTED || result == Result::INVALID_STATE) { // setting to true must result in the same error as false auto secondResult = mSession->setConfigFlag(flag, true); EXPECT_EQ(result, secondResult); continue; } ASSERT_EQ(Result::OK, result); // verify false is set auto value = get(flag); EXPECT_FALSE(value); // try setting true this time result = mSession->setConfigFlag(flag, true); ASSERT_EQ(Result::OK, result); value = get(flag); EXPECT_TRUE(value); // false again result = mSession->setConfigFlag(flag, false); ASSERT_EQ(Result::OK, result); value = get(flag); EXPECT_FALSE(value); } } /** * Test getting program list. * * Verifies that: * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED; * - the complete list is fetched within timeout::programListScan; * - stopProgramListUpdates does not crash. */ TEST_P(BroadcastRadioHalTest, GetProgramList) { ASSERT_TRUE(openSession()); getProgramList(); } /** * Test HD_STATION_NAME correctness. * * Verifies that if a program on the list contains HD_STATION_NAME identifier: * - the program provides station name in its metadata; * - the identifier matches the name; * - there is only one identifier of that type. */ TEST_P(BroadcastRadioHalTest, HdRadioStationNameId) { ASSERT_TRUE(openSession()); auto list = getProgramList(); if (!list) return; for (auto&& program : *list) { auto nameIds = utils::getAllIds(program.selector, IdentifierType::HD_STATION_NAME); EXPECT_LE(nameIds.size(), 1u); if (nameIds.size() == 0) continue; auto name = utils::getMetadataString(program, MetadataKey::PROGRAM_NAME); if (!name) name = utils::getMetadataString(program, MetadataKey::RDS_PS); ASSERT_TRUE(name.has_value()); auto expectedId = utils::make_hdradio_station_name(*name); EXPECT_EQ(expectedId.value, nameIds[0]); } } /** * Test announcement listener registration. * * Verifies that: * - registerAnnouncementListener either succeeds or returns NOT_SUPPORTED; * - if it succeeds, it returns a valid close handle (which is a nullptr otherwise); * - closing handle does not crash. */ TEST_P(BroadcastRadioHalTest, AnnouncementListenerRegistration) { sp listener = new AnnouncementListenerMock(); Result halResult = Result::UNKNOWN_ERROR; sp closeHandle = nullptr; auto cb = [&](Result result, const sp& closeHandle_) { halResult = result; closeHandle = closeHandle_; }; auto hidlResult = mModule->registerAnnouncementListener({AnnouncementType::EMERGENCY}, listener, cb); ASSERT_TRUE(hidlResult.isOk()); if (halResult == Result::NOT_SUPPORTED) { ASSERT_EQ(nullptr, closeHandle.get()); printSkipped("Announcements not supported"); return; } ASSERT_EQ(Result::OK, halResult); ASSERT_NE(nullptr, closeHandle.get()); closeHandle->close(); } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BroadcastRadioHalTest); INSTANTIATE_TEST_SUITE_P( PerInstance, BroadcastRadioHalTest, testing::ValuesIn(android::hardware::getAllHalInstanceNames(IBroadcastRadio::descriptor)), android::hardware::PrintInstanceNameToString); } // namespace vts } // namespace V2_0 } // namespace broadcastradio } // namespace hardware } // namespace android int main(int argc, char** argv) { android::base::SetDefaultTag("BcRadio.vts"); android::base::SetMinimumLogSeverity(android::base::VERBOSE); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }