/* * 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 LOG_TAG "broadcastradio.vts" #include #include #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 V1_1 { namespace vts { using namespace std::chrono_literals; using testing::_; using testing::AnyNumber; using testing::ByMove; using testing::DoAll; using testing::Invoke; using testing::SaveArg; using broadcastradio::V1_0::vts::RadioClassFromString; using broadcastradio::vts::CallBarrier; using V1_0::BandConfig; using V1_0::Class; using V1_0::MetaData; using V1_0::MetadataKey; using V1_0::MetadataType; using broadcastradio::vts::clearAndWait; static constexpr auto kConfigTimeout = 10s; static constexpr auto kConnectModuleTimeout = 1s; static constexpr auto kTuneTimeout = 30s; static constexpr auto kEventPropagationTimeout = 1s; static constexpr auto kFullScanTimeout = 1min; static constexpr ProgramType kStandardProgramTypes[] = { ProgramType::AM, ProgramType::FM, ProgramType::AM_HD, ProgramType::FM_HD, ProgramType::DAB, ProgramType::DRMO, ProgramType::SXM}; static void printSkipped(std::string msg) { std::cout << "[ SKIPPED ] " << msg << std::endl; } struct TunerCallbackMock : public ITunerCallback { TunerCallbackMock() { EXPECT_CALL(*this, hardwareFailure()).Times(0); } MOCK_METHOD0(hardwareFailure, Return()); MOCK_TIMEOUT_METHOD2(configChange, Return(Result, const BandConfig&)); MOCK_METHOD2(tuneComplete, Return(Result, const V1_0::ProgramInfo&)); MOCK_TIMEOUT_METHOD2(tuneComplete_1_1, Return(Result, const ProgramSelector&)); MOCK_METHOD1(afSwitch, Return(const V1_0::ProgramInfo&)); MOCK_METHOD1(antennaStateChange, Return(bool connected)); MOCK_METHOD1(trafficAnnouncement, Return(bool active)); MOCK_METHOD1(emergencyAnnouncement, Return(bool active)); MOCK_METHOD3(newMetadata, Return(uint32_t ch, uint32_t subCh, const hidl_vec&)); MOCK_METHOD1(backgroundScanAvailable, Return(bool)); MOCK_TIMEOUT_METHOD1(backgroundScanComplete, Return(ProgramListResult)); MOCK_METHOD0(programListChanged, Return()); MOCK_TIMEOUT_METHOD1(currentProgramInfoChanged, Return(const ProgramInfo&)); }; class BroadcastRadioHalTest : public ::testing::TestWithParam> { protected: virtual void SetUp() override; virtual void TearDown() override; bool openTuner(); bool nextBand(); bool getProgramList(std::function& list)> cb); Class radioClass; bool skipped = false; sp mRadioModule; sp mTuner; sp mCallback = new TunerCallbackMock(); private: const BandConfig& getBand(unsigned idx); unsigned currentBandIndex = 0; hidl_vec mBands; }; void BroadcastRadioHalTest::SetUp() { radioClass = RadioClassFromString(std::get<1>(GetParam())); // lookup HIDL service auto factory = IBroadcastRadioFactory::getService(std::get<0>(GetParam())); ASSERT_NE(nullptr, factory.get()); // connect radio module Result connectResult; CallBarrier onConnect; factory->connectModule(radioClass, [&](Result ret, const sp& radio) { connectResult = ret; if (ret == Result::OK) mRadioModule = IBroadcastRadio::castFrom(radio); onConnect.call(); }); ASSERT_TRUE(onConnect.waitForCall(kConnectModuleTimeout)); if (connectResult == Result::INVALID_ARGUMENTS) { printSkipped("This device class is not supported."); skipped = true; return; } ASSERT_EQ(connectResult, Result::OK); ASSERT_NE(nullptr, mRadioModule.get()); // get module properties Properties prop11; auto& prop10 = prop11.base; auto propResult = mRadioModule->getProperties_1_1([&](const Properties& properties) { prop11 = properties; }); ASSERT_TRUE(propResult.isOk()); EXPECT_EQ(radioClass, prop10.classId); EXPECT_GT(prop10.numTuners, 0u); EXPECT_GT(prop11.supportedProgramTypes.size(), 0u); EXPECT_GT(prop11.supportedIdentifierTypes.size(), 0u); if (radioClass == Class::AM_FM) { EXPECT_GT(prop10.bands.size(), 0u); } mBands = prop10.bands; } void BroadcastRadioHalTest::TearDown() { mTuner.clear(); mRadioModule.clear(); clearAndWait(mCallback, 1s); } bool BroadcastRadioHalTest::openTuner() { EXPECT_EQ(nullptr, mTuner.get()); if (radioClass == Class::AM_FM) { EXPECT_TIMEOUT_CALL(*mCallback, configChange, Result::OK, _); } Result halResult = Result::NOT_INITIALIZED; auto openCb = [&](Result result, const sp& tuner) { halResult = result; if (result != Result::OK) return; mTuner = ITuner::castFrom(tuner); }; currentBandIndex = 0; auto hidlResult = mRadioModule->openTuner(getBand(0), true, mCallback, openCb); EXPECT_TRUE(hidlResult.isOk()); EXPECT_EQ(Result::OK, halResult); EXPECT_NE(nullptr, mTuner.get()); if (radioClass == Class::AM_FM && mTuner != nullptr) { EXPECT_TIMEOUT_CALL_WAIT(*mCallback, configChange, kConfigTimeout); BandConfig halConfig; Result halResult = Result::NOT_INITIALIZED; mTuner->getConfiguration([&](Result result, const BandConfig& config) { halResult = result; halConfig = config; }); EXPECT_EQ(Result::OK, halResult); EXPECT_TRUE(halConfig.antennaConnected); } EXPECT_NE(nullptr, mTuner.get()); return nullptr != mTuner.get(); } const BandConfig& BroadcastRadioHalTest::getBand(unsigned idx) { static const BandConfig dummyBandConfig = {}; if (radioClass != Class::AM_FM) { ALOGD("Not AM/FM radio, returning dummy band config"); return dummyBandConfig; } EXPECT_GT(mBands.size(), idx); if (mBands.size() <= idx) { ALOGD("Band index out of bound, returning dummy band config"); return dummyBandConfig; } auto& band = mBands[idx]; ALOGD("Returning %s band", toString(band.type).c_str()); return band; } bool BroadcastRadioHalTest::nextBand() { if (currentBandIndex + 1 >= mBands.size()) return false; currentBandIndex++; BandConfig bandCb; EXPECT_TIMEOUT_CALL(*mCallback, configChange, Result::OK, _) .WillOnce(DoAll(SaveArg<1>(&bandCb), testing::Return(ByMove(Void())))); auto hidlResult = mTuner->setConfiguration(getBand(currentBandIndex)); EXPECT_EQ(Result::OK, hidlResult); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, configChange, kConfigTimeout); EXPECT_EQ(getBand(currentBandIndex), bandCb); return true; } bool BroadcastRadioHalTest::getProgramList( std::function& list)> cb) { ProgramListResult getListResult = ProgramListResult::NOT_INITIALIZED; bool isListEmpty = true; auto getListCb = [&](ProgramListResult result, const hidl_vec& list) { ALOGD("getListCb(%s, ProgramInfo[%zu])", toString(result).c_str(), list.size()); getListResult = result; if (result != ProgramListResult::OK) return; isListEmpty = (list.size() == 0); if (!isListEmpty) cb(list); }; // first try... EXPECT_TIMEOUT_CALL(*mCallback, backgroundScanComplete, ProgramListResult::OK) .Times(AnyNumber()); auto hidlResult = mTuner->getProgramList({}, getListCb); EXPECT_TRUE(hidlResult.isOk()); if (!hidlResult.isOk()) return false; if (getListResult == ProgramListResult::NOT_STARTED) { auto result = mTuner->startBackgroundScan(); EXPECT_EQ(ProgramListResult::OK, result); getListResult = ProgramListResult::NOT_READY; // continue as in NOT_READY case } if (getListResult == ProgramListResult::NOT_READY) { EXPECT_TIMEOUT_CALL_WAIT(*mCallback, backgroundScanComplete, kFullScanTimeout); // second (last) try... hidlResult = mTuner->getProgramList({}, getListCb); EXPECT_TRUE(hidlResult.isOk()); if (!hidlResult.isOk()) return false; EXPECT_EQ(ProgramListResult::OK, getListResult); } return !isListEmpty; } /** * Test IBroadcastRadio::openTuner() method called twice. * * Verifies that: * - the openTuner method succeeds when called for the second time without * deleting previous ITuner instance. * * This is a more strict requirement than in 1.0, where a second openTuner * might fail. */ TEST_P(BroadcastRadioHalTest, OpenTunerTwice) { if (skipped) return; ASSERT_TRUE(openTuner()); auto secondTuner = mTuner; mTuner.clear(); ASSERT_TRUE(openTuner()); } /** * Test tuning to program list entry. * * Verifies that: * - getProgramList either succeeds or returns NOT_STARTED/NOT_READY status; * - if the program list is NOT_STARTED, startBackgroundScan makes it completed * within a full scan timeout and the next getProgramList call succeeds; * - if the program list is not empty, tuneByProgramSelector call succeeds; * - getProgramInformation_1_1 returns the same selector as returned in tuneComplete_1_1 call. */ TEST_P(BroadcastRadioHalTest, TuneFromProgramList) { if (skipped) return; ASSERT_TRUE(openTuner()); ProgramInfo firstProgram; bool foundAny = false; do { auto getCb = [&](const hidl_vec& list) { // don't copy the whole list out, it might be heavy firstProgram = list[0]; }; if (getProgramList(getCb)) foundAny = true; } while (nextBand()); if (HasFailure()) return; if (!foundAny) { printSkipped("Program list is empty."); return; } ProgramInfo infoCb; ProgramSelector selCb; EXPECT_CALL(*mCallback, tuneComplete(_, _)).Times(0); EXPECT_TIMEOUT_CALL(*mCallback, tuneComplete_1_1, Result::OK, _) .WillOnce(DoAll(SaveArg<1>(&selCb), testing::Return(ByMove(Void())))); EXPECT_TIMEOUT_CALL(*mCallback, currentProgramInfoChanged, _) .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(Void())))); auto tuneResult = mTuner->tuneByProgramSelector(firstProgram.selector); ASSERT_EQ(Result::OK, tuneResult); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, tuneComplete_1_1, kTuneTimeout); EXPECT_TIMEOUT_CALL_WAIT(*mCallback, currentProgramInfoChanged, kEventPropagationTimeout); EXPECT_EQ(firstProgram.selector.primaryId, selCb.primaryId); EXPECT_EQ(infoCb.selector, selCb); bool called = false; auto getResult = mTuner->getProgramInformation_1_1([&](Result result, ProgramInfo info) { called = true; EXPECT_EQ(Result::OK, result); EXPECT_EQ(selCb, info.selector); }); ASSERT_TRUE(getResult.isOk()); ASSERT_TRUE(called); } /** * Test that primary vendor identifier isn't used for standard program types. * * Verifies that: * - tuneByProgramSelector fails when VENDORn_PRIMARY is set as a primary * identifier for program types other than VENDORn. */ TEST_P(BroadcastRadioHalTest, TuneFailsForPrimaryVendor) { if (skipped) return; ASSERT_TRUE(openTuner()); for (auto ptype : kStandardProgramTypes) { ALOGD("Checking %s...", toString(ptype).c_str()); ProgramSelector sel = {}; sel.programType = static_cast(ptype); sel.primaryId.type = static_cast(IdentifierType::VENDOR_PRIMARY_START); auto tuneResult = mTuner->tuneByProgramSelector(sel); ASSERT_NE(Result::OK, tuneResult); } } /** * Test that tune with unknown program type fails. * * Verifies that: * - tuneByProgramSelector fails with INVALID_ARGUMENT when unknown program type is passed. */ TEST_P(BroadcastRadioHalTest, TuneFailsForUnknownProgram) { if (skipped) return; ASSERT_TRUE(openTuner()); // Program type is 1-based, so 0 will be always invalid. ProgramSelector sel = {}; auto tuneResult = mTuner->tuneByProgramSelector(sel); ASSERT_EQ(Result::INVALID_ARGUMENTS, tuneResult); } /** * Test cancelling announcement. * * Verifies that: * - cancelAnnouncement succeeds either when there is an announcement or there is none. */ TEST_P(BroadcastRadioHalTest, CancelAnnouncement) { if (skipped) return; ASSERT_TRUE(openTuner()); auto hidlResult = mTuner->cancelAnnouncement(); EXPECT_EQ(Result::OK, hidlResult); } /** * Test getImage call with invalid image ID. * * Verifies that: * - getImage call handles argument 0 gracefully. */ TEST_P(BroadcastRadioHalTest, GetNoImage) { if (skipped) return; size_t len = 0; auto hidlResult = mRadioModule->getImage(0, [&](hidl_vec rawImage) { len = rawImage.size(); }); ASSERT_TRUE(hidlResult.isOk()); ASSERT_EQ(0u, len); } /** * Test proper image format in metadata. * * Verifies that: * - all images in metadata are provided out-of-band (by id, not as a binary blob); * - images are available for getImage call. */ TEST_P(BroadcastRadioHalTest, OobImagesOnly) { if (skipped) return; ASSERT_TRUE(openTuner()); std::vector imageIds; do { auto getCb = [&](const hidl_vec& list) { for (auto&& program : list) { for (auto&& entry : program.base.metadata) { EXPECT_NE(MetadataType::RAW, entry.type); if (entry.key != MetadataKey::ICON && entry.key != MetadataKey::ART) continue; EXPECT_NE(0, entry.intValue); EXPECT_EQ(0u, entry.rawValue.size()); if (entry.intValue != 0) imageIds.push_back(entry.intValue); } } }; getProgramList(getCb); } while (nextBand()); if (imageIds.size() == 0) { printSkipped("No images found"); return; } for (auto id : imageIds) { ALOGD("Checking image %d", id); size_t len = 0; auto hidlResult = mRadioModule->getImage(id, [&](hidl_vec rawImage) { len = rawImage.size(); }); ASSERT_TRUE(hidlResult.isOk()); ASSERT_GT(len, 0u); } } /** * Test AnalogForced switch. * * Verifies that: * - setAnalogForced results either with INVALID_STATE, or isAnalogForced replying the same. */ TEST_P(BroadcastRadioHalTest, AnalogForcedSwitch) { if (skipped) return; ASSERT_TRUE(openTuner()); bool forced; Result halIsResult; auto isCb = [&](Result result, bool isForced) { halIsResult = result; forced = isForced; }; // set analog mode auto setResult = mTuner->setAnalogForced(true); ASSERT_TRUE(setResult.isOk()); if (Result::INVALID_STATE == setResult) { // if setter fails, getter should fail too - it means the switch is not supported at all auto isResult = mTuner->isAnalogForced(isCb); ASSERT_TRUE(isResult.isOk()); EXPECT_EQ(Result::INVALID_STATE, halIsResult); return; } ASSERT_EQ(Result::OK, setResult); // check, if it's analog auto isResult = mTuner->isAnalogForced(isCb); ASSERT_TRUE(isResult.isOk()); EXPECT_EQ(Result::OK, halIsResult); ASSERT_TRUE(forced); // set digital mode setResult = mTuner->setAnalogForced(false); ASSERT_EQ(Result::OK, setResult); // check, if it's digital isResult = mTuner->isAnalogForced(isCb); ASSERT_TRUE(isResult.isOk()); EXPECT_EQ(Result::OK, halIsResult); ASSERT_FALSE(forced); } static void verifyIdentifier(const ProgramIdentifier& id) { EXPECT_NE(id.type, 0u); auto val = id.value; switch (static_cast(id.type)) { case IdentifierType::AMFM_FREQUENCY: case IdentifierType::DAB_FREQUENCY: case IdentifierType::DRMO_FREQUENCY: EXPECT_GT(val, 100u) << "Expected f > 100kHz"; EXPECT_LT(val, 10000000u) << "Expected f < 10GHz"; break; case IdentifierType::RDS_PI: EXPECT_GT(val, 0u); EXPECT_LE(val, 0xFFFFu) << "Expected 16bit id"; break; case IdentifierType::HD_STATION_ID_EXT: { auto stationId = val & 0xFFFFFFFF; // 32bit val >>= 32; auto subchannel = val & 0xF; // 4bit val >>= 4; auto freq = val & 0x3FFFF; // 18bit EXPECT_GT(stationId, 0u); EXPECT_LT(subchannel, 8u) << "Expected ch < 8"; EXPECT_GT(freq, 100u) << "Expected f > 100kHz"; EXPECT_LT(freq, 10000000u) << "Expected f < 10GHz"; break; } case IdentifierType::HD_SUBCHANNEL: EXPECT_LT(val, 8u) << "Expected ch < 8"; break; case IdentifierType::DAB_SIDECC: { auto sid = val & 0xFFFF; // 16bit val >>= 16; auto ecc = val & 0xFF; // 8bit EXPECT_NE(sid, 0u); EXPECT_GE(ecc, 0xA0u) << "Invalid ECC, see ETSI TS 101 756 V2.1.1"; EXPECT_LE(ecc, 0xF6u) << "Invalid ECC, see ETSI TS 101 756 V2.1.1"; break; } case IdentifierType::DAB_ENSEMBLE: EXPECT_GT(val, 0u); EXPECT_LE(val, 0xFFFFu) << "Expected 16bit id"; break; case IdentifierType::DAB_SCID: EXPECT_GT(val, 0xFu) << "Expected 12bit SCId (not 4bit SCIdS)"; EXPECT_LE(val, 0xFFFu) << "Expected 12bit id"; break; case IdentifierType::DRMO_SERVICE_ID: EXPECT_GT(val, 0u); EXPECT_LE(val, 0xFFFFFFu) << "Expected 24bit id"; break; case IdentifierType::DRMO_MODULATION: EXPECT_GE(val, static_cast(Modulation::AM)); EXPECT_LE(val, static_cast(Modulation::FM)); break; case IdentifierType::SXM_SERVICE_ID: EXPECT_GT(val, 0u); EXPECT_LE(val, 0xFFFFFFFFu) << "Expected 32bit id"; break; case IdentifierType::SXM_CHANNEL: EXPECT_LT(val, 1000u); break; case IdentifierType::VENDOR_PRIMARY_START: case IdentifierType::VENDOR_PRIMARY_END: // skip break; } } /** * Test ProgramIdentifier format. * * Verifies that: * - values of ProgramIdentifier match their definitions at IdentifierType. */ TEST_P(BroadcastRadioHalTest, VerifyIdentifiersFormat) { if (skipped) return; ASSERT_TRUE(openTuner()); do { auto getCb = [&](const hidl_vec& list) { for (auto&& program : list) { verifyIdentifier(program.selector.primaryId); for (auto&& id : program.selector.secondaryIds) { verifyIdentifier(id); } } }; getProgramList(getCb); } while (nextBand()); } INSTANTIATE_TEST_CASE_P( PerInstance, BroadcastRadioHalTest, testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames( IBroadcastRadioFactory::descriptor)), ::testing::Values("AM_FM", "SAT", "DT")), android::hardware::PrintInstanceTupleNameToString<>); } // namespace vts } // namespace V1_1 } // namespace broadcastradio } // namespace hardware } // namespace android