diff --git a/broadcastradio/aidl/vts/Android.bp b/broadcastradio/aidl/vts/Android.bp new file mode 100644 index 0000000000..b60387ef49 --- /dev/null +++ b/broadcastradio/aidl/vts/Android.bp @@ -0,0 +1,47 @@ +// 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_test { + name: "VtsHalBroadcastradioAidlTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + tidy_timeout_srcs: ["src/*.cpp"], + srcs: ["src/*.cpp"], + shared_libs: [ + "libbinder_ndk", + "libbase", + "libxml2", + ], + static_libs: [ + "android.hardware.broadcastradio-V1-ndk", + "android.hardware.broadcastradio@common-utils-aidl-lib", + "android.hardware.broadcastradio@vts-utils-lib", + "libgmock", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/broadcastradio/aidl/vts/OWNERS b/broadcastradio/aidl/vts/OWNERS new file mode 100644 index 0000000000..302fdd73cc --- /dev/null +++ b/broadcastradio/aidl/vts/OWNERS @@ -0,0 +1,4 @@ +xuweilin@google.com +oscarazu@google.com +ericjeong@google.com +keunyoung@google.com diff --git a/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp new file mode 100644 index 0000000000..02c9356f0c --- /dev/null +++ b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp @@ -0,0 +1,712 @@ +/* + * 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. + */ + +#define EGMOCK_VERBOSE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace aidl::android::hardware::broadcastradio::vts { + +namespace { + +using ::aidl::android::hardware::broadcastradio::utils::makeIdentifier; +using ::aidl::android::hardware::broadcastradio::utils::makeSelectorAmfm; +using ::aidl::android::hardware::broadcastradio::utils::resultToInt; +using ::ndk::ScopedAStatus; +using ::ndk::SharedRefBase; +using ::std::string; +using ::std::vector; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::ByMove; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::SaveArg; + +namespace bcutils = ::aidl::android::hardware::broadcastradio::utils; + +inline constexpr std::chrono::seconds kTuneTimeoutSec = + std::chrono::seconds(IBroadcastRadio::TUNER_TIMEOUT_MS * 1000); +inline constexpr std::chrono::seconds kProgramListScanTimeoutSec = + std::chrono::seconds(IBroadcastRadio::LIST_COMPLETE_TIMEOUT_MS * 1000); + +void printSkipped(const string& msg) { + const auto testInfo = testing::UnitTest::GetInstance()->current_test_info(); + LOG(INFO) << "[ SKIPPED ] " << testInfo->test_case_name() << "." << testInfo->name() + << " with message: " << msg; +} + +} // namespace + +class TunerCallbackMock : public BnTunerCallback { + public: + TunerCallbackMock(); + + MOCK_METHOD2(onTuneFailed, ScopedAStatus(Result, const ProgramSelector&)); + MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChangedMock, ScopedAStatus(const ProgramInfo&)); + ScopedAStatus onCurrentProgramInfoChanged(const ProgramInfo& info) override; + ScopedAStatus onProgramListUpdated(const ProgramListChunk& chunk) override; + MOCK_METHOD1(onAntennaStateChange, ScopedAStatus(bool connected)); + MOCK_METHOD1(onParametersUpdated, ScopedAStatus(const vector& parameters)); + MOCK_METHOD2(onConfigFlagUpdated, ScopedAStatus(ConfigFlag in_flag, bool in_value)); + MOCK_TIMEOUT_METHOD0(onProgramListReady, void()); + + std::mutex mLock; + bcutils::ProgramInfoSet mProgramList GUARDED_BY(mLock); +}; + +struct AnnouncementListenerMock : public BnAnnouncementListener { + MOCK_METHOD1(onListUpdated, ScopedAStatus(const vector&)); +}; + +class BroadcastRadioHalTest : public testing::TestWithParam { + protected: + void SetUp() override; + void TearDown() override; + + bool getAmFmRegionConfig(bool full, AmFmRegionConfig* config); + std::optional getProgramList(); + std::optional getProgramList(const ProgramFilter& filter); + + std::shared_ptr mModule; + Properties mProperties; + std::shared_ptr mCallback = SharedRefBase::make(); +}; + +MATCHER_P(InfoHasId, id, string(negation ? "does not contain" : "contains") + " " + id.toString()) { + vector ids = bcutils::getAllIds(arg.selector, id.type); + return ids.end() != find(ids.begin(), ids.end(), id.value); +} + +TunerCallbackMock::TunerCallbackMock() { + EXPECT_TIMEOUT_CALL(*this, onCurrentProgramInfoChangedMock, _).Times(AnyNumber()); + + // we expect the antenna is connected through the whole test + EXPECT_CALL(*this, onAntennaStateChange(false)).Times(0); +} + +ScopedAStatus TunerCallbackMock::onCurrentProgramInfoChanged(const ProgramInfo& info) { + for (const auto& id : info.selector) { + EXPECT_NE(id.type, IdentifierType::INVALID); + } + + IdentifierType logically = info.logicallyTunedTo.type; + // This field is required for currently tuned program and should be INVALID + // for entries from the program list. + EXPECT_TRUE(logically == IdentifierType::AMFM_FREQUENCY_KHZ || + 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); + + IdentifierType physically = info.physicallyTunedTo.type; + // ditto (see "logically" above) + EXPECT_TRUE(physically == IdentifierType::AMFM_FREQUENCY_KHZ || + physically == IdentifierType::DAB_ENSEMBLE || + physically == IdentifierType::DRMO_FREQUENCY_KHZ || + physically == IdentifierType::SXM_CHANNEL || + (physically >= IdentifierType::VENDOR_START && + physically <= IdentifierType::VENDOR_END) || + physically > IdentifierType::SXM_CHANNEL); + + if (logically == IdentifierType::AMFM_FREQUENCY_KHZ) { + std::optional ps = bcutils::getMetadataString(info, Metadata::rdsPs); + 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 onCurrentProgramInfoChangedMock(info); +} + +ScopedAStatus TunerCallbackMock::onProgramListUpdated(const ProgramListChunk& chunk) { + std::lock_guard lk(mLock); + + updateProgramList(chunk, &mProgramList); + + if (chunk.complete) { + onProgramListReady(); + } + + return ndk::ScopedAStatus::ok(); +} + +void BroadcastRadioHalTest::SetUp() { + EXPECT_EQ(mModule.get(), nullptr) << "Module is already open"; + + // lookup AIDL service (radio module) + AIBinder* binder = AServiceManager_waitForService(GetParam().c_str()); + ASSERT_NE(binder, nullptr); + mModule = IBroadcastRadio::fromBinder(ndk::SpAIBinder(binder)); + ASSERT_NE(mModule, nullptr) << "Couldn't find broadcast radio HAL implementation"; + + // get module properties + auto propResult = mModule->getProperties(&mProperties); + + ASSERT_TRUE(propResult.isOk()); + EXPECT_FALSE(mProperties.maker.empty()); + EXPECT_FALSE(mProperties.product.empty()); + EXPECT_GT(mProperties.supportedIdentifierTypes.size(), 0u); + + // set callback + EXPECT_TRUE(mModule->setTunerCallback(mCallback).isOk()); +} + +void BroadcastRadioHalTest::TearDown() { + if (mModule) { + ASSERT_TRUE(mModule->unsetTunerCallback().isOk()); + } +} + +bool BroadcastRadioHalTest::getAmFmRegionConfig(bool full, AmFmRegionConfig* config) { + auto halResult = mModule->getAmFmRegionConfig(full, config); + + if (halResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) { + return false; + } + + EXPECT_TRUE(halResult.isOk()); + return halResult.isOk(); +} + +std::optional BroadcastRadioHalTest::getProgramList() { + ProgramFilter emptyFilter = {}; + return getProgramList(emptyFilter); +} + +std::optional BroadcastRadioHalTest::getProgramList( + const ProgramFilter& filter) { + EXPECT_TIMEOUT_CALL(*mCallback, onProgramListReady).Times(AnyNumber()); + + auto startResult = mModule->startProgramListUpdates(filter); + + if (startResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) { + printSkipped("Program list not supported"); + return std::nullopt; + } + EXPECT_TRUE(startResult.isOk()); + if (!startResult.isOk()) { + return std::nullopt; + } + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onProgramListReady, kProgramListScanTimeoutSec); + + auto stopResult = mModule->stopProgramListUpdates(); + + EXPECT_TRUE(stopResult.isOk()); + + return mCallback->mProgramList; +} + +/** + * Test setting tuner callback to null. + * + * Verifies that: + * - Setting to a null tuner callback results with INVALID_ARGUMENTS. + */ +TEST_P(BroadcastRadioHalTest, TunerCallbackFailsWithNull) { + LOG(DEBUG) << "TunerCallbackFailsWithNull Test"; + + auto halResult = mModule->setTunerCallback(nullptr); + + EXPECT_EQ(halResult.getServiceSpecificError(), resultToInt(Result::INVALID_ARGUMENTS)); +} + +/** + * Test tuning without tuner callback set. + * + * Verifies that: + * - No tuner callback set results in INVALID_STATE, regardless of whether the selector is + * supported. + */ +TEST_P(BroadcastRadioHalTest, TuneFailsWithoutTunerCallback) { + LOG(DEBUG) << "TuneFailsWithoutTunerCallback Test"; + + mModule->unsetTunerCallback(); + int64_t freq = 90900; // 90.9 FM + ProgramSelector sel = makeSelectorAmfm(freq); + + auto result = mModule->tune(sel); + + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE)); +} + +/** + * Test tuning with selectors that can be not supported. + * + * Verifies that: + * - if the selector is not supported, an invalid value results with NOT_SUPPORTED, regardless of + * whether it is valid; + * - if it is supported, the test is ignored; + */ +TEST_P(BroadcastRadioHalTest, TuneFailsWithNotSupported) { + LOG(DEBUG) << "TuneFailsWithInvalid Test"; + + vector supportTestId = { + makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, 0), // invalid + makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, 94900), // valid + makeIdentifier(IdentifierType::RDS_PI, 0x10000), // invalid + makeIdentifier(IdentifierType::RDS_PI, 0x1001), // valid + makeIdentifier(IdentifierType::HD_STATION_ID_EXT, 0x100000000), // invalid + makeIdentifier(IdentifierType::HD_STATION_ID_EXT, 0x10000001), // valid + makeIdentifier(IdentifierType::DAB_SID_EXT, 0), // invalid + makeIdentifier(IdentifierType::DAB_SID_EXT, 0xA00001), // valid + makeIdentifier(IdentifierType::DRMO_SERVICE_ID, 0x100000000), // invalid + makeIdentifier(IdentifierType::DRMO_SERVICE_ID, 0x10000001), // valid + makeIdentifier(IdentifierType::SXM_SERVICE_ID, 0x100000000), // invalid + makeIdentifier(IdentifierType::SXM_SERVICE_ID, 0x10000001), // valid + }; + + auto notSupportedError = resultToInt(Result::NOT_SUPPORTED); + for (const auto& id : supportTestId) { + ProgramSelector sel{id, {}}; + + auto result = mModule->tune(sel); + + if (!bcutils::isSupported(mProperties, sel)) { + EXPECT_EQ(result.getServiceSpecificError(), notSupportedError); + } + } +} + +/** + * 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) { + LOG(DEBUG) << "TuneFailsWithInvalid Test"; + + vector invalidId = { + makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, 0), + makeIdentifier(IdentifierType::RDS_PI, 0x10000), + makeIdentifier(IdentifierType::HD_STATION_ID_EXT, 0x100000000), + makeIdentifier(IdentifierType::DAB_SID_EXT, 0), + makeIdentifier(IdentifierType::DRMO_SERVICE_ID, 0x100000000), + makeIdentifier(IdentifierType::SXM_SERVICE_ID, 0x100000000), + }; + + auto invalidArgumentsError = resultToInt(Result::INVALID_ARGUMENTS); + for (const auto& id : invalidId) { + ProgramSelector sel{id, {}}; + + auto result = mModule->tune(sel); + + if (bcutils::isSupported(mProperties, sel)) { + EXPECT_EQ(result.getServiceSpecificError(), invalidArgumentsError); + } + } +} + +/** + * Test tuning with empty program selector. + * + * Verifies that: + * - tune fails with NOT_SUPPORTED when program selector is not initialized. + */ +TEST_P(BroadcastRadioHalTest, TuneFailsWithEmpty) { + LOG(DEBUG) << "TuneFailsWithEmpty Test"; + + // Program type is 1-based, so 0 will always be invalid. + ProgramSelector sel = {}; + + auto result = mModule->tune(sel); + + ASSERT_EQ(result.getServiceSpecificError(), resultToInt(Result::NOT_SUPPORTED)); +} + +/** + * 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) { + LOG(DEBUG) << "FmTune Test"; + + int64_t freq = 90900; // 90.9 FM + ProgramSelector sel = makeSelectorAmfm(freq); + // try tuning + ProgramInfo infoCb = {}; + EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, + InfoHasId(makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, freq))) + .Times(AnyNumber()) + .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(ndk::ScopedAStatus::ok())))) + .WillRepeatedly(testing::InvokeWithoutArgs([] { return ndk::ScopedAStatus::ok(); })); + + auto result = mModule->tune(sel); + + // expect a failure if it's not supported + if (!bcutils::isSupported(mProperties, sel)) { + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::NOT_SUPPORTED)); + return; + } + + // expect a callback if it succeeds + EXPECT_TRUE(result.isOk()); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec); + + LOG(DEBUG) << "Current program info: " << infoCb.toString(); + + // it should tune exactly to what was requested + vector freqs = bcutils::getAllIds(infoCb.selector, IdentifierType::AMFM_FREQUENCY_KHZ); + EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq)) + << "FM freq " << freq << " kHz is not sent back by callback."; +} + +/** + * 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) { + LOG(DEBUG) << "DabTune Test"; + vector config; + + auto halResult = mModule->getDabRegionConfig(&config); + + if (halResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) { + printSkipped("DAB not supported"); + return; + } + ASSERT_TRUE(halResult.isOk()); + ASSERT_NE(config.size(), 0U); + + // TODO(245787803): use a DAB frequency that can actually be tuned to. + ProgramSelector sel = {}; + int64_t freq = config[config.size() / 2].frequencyKhz; + sel.primaryId = makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, freq); + + // try tuning + ProgramInfo infoCb = {}; + EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, + InfoHasId(makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, freq))) + .Times(AnyNumber()) + .WillOnce( + DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(ndk::ScopedAStatus::ok())))); + + auto result = mModule->tune(sel); + + // expect a failure if it's not supported + if (!bcutils::isSupported(mProperties, sel)) { + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::NOT_SUPPORTED)); + return; + } + + // expect a callback if it succeeds + EXPECT_TRUE(result.isOk()); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec); + LOG(DEBUG) << "Current program info: " << infoCb.toString(); + + // it should tune exactly to what was requested + vector freqs = bcutils::getAllIds(infoCb.selector, IdentifierType::DAB_FREQUENCY_KHZ); + EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq)) + << "DAB freq " << freq << " kHz is not sent back by callback."; + ; +} + +/** + * Test seeking to next/prev station via IBroadcastRadio::seek(). + * + * Verifies that: + * - the method succeeds; + * - the program info is changed within kTuneTimeoutSec; + * - works both directions and with or without skipping sub-channel. + */ +TEST_P(BroadcastRadioHalTest, Seek) { + LOG(DEBUG) << "Seek Test"; + + EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber()); + + auto result = mModule->seek(/* in_directionUp= */ true, /* in_skipSubChannel= */ true); + + if (result.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) { + printSkipped("Seek not supported"); + return; + } + + EXPECT_TRUE(result.isOk()); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec); + + EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber()); + + result = mModule->seek(/* in_directionUp= */ false, /* in_skipSubChannel= */ false); + + EXPECT_TRUE(result.isOk()); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec); +} + +/** + * Test seeking without tuner callback set. + * + * Verifies that: + * - No tuner callback set results in INVALID_STATE. + */ +TEST_P(BroadcastRadioHalTest, SeekFailsWithoutTunerCallback) { + LOG(DEBUG) << "SeekFailsWithoutTunerCallback Test"; + + mModule->unsetTunerCallback(); + + auto result = mModule->seek(/* in_directionUp= */ true, /* in_skipSubChannel= */ true); + + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE)); + + result = mModule->seek(/* in_directionUp= */ false, /* in_skipSubChannel= */ false); + + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE)); +} + +/** + * Test step operation. + * + * Verifies that: + * - the method succeeds or returns NOT_SUPPORTED; + * - the program info is changed within kTuneTimeoutSec if the method succeeded; + * - works both directions. + */ +TEST_P(BroadcastRadioHalTest, Step) { + LOG(DEBUG) << "Step Test"; + + EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber()); + + auto result = mModule->step(/* in_directionUp= */ true); + + if (result.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) { + printSkipped("Step not supported"); + return; + } + EXPECT_TRUE(result.isOk()); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec); + + EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber()); + + result = mModule->step(/* in_directionUp= */ false); + + EXPECT_TRUE(result.isOk()); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec); +} + +/** + * Test step operation without tuner callback set. + * + * Verifies that: + * - No tuner callback set results in INVALID_STATE. + */ +TEST_P(BroadcastRadioHalTest, StepFailsWithoutTunerCallback) { + LOG(DEBUG) << "StepFailsWithoutTunerCallback Test"; + + mModule->unsetTunerCallback(); + + auto result = mModule->step(/* in_directionUp= */ true); + + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE)); + + result = mModule->step(/* in_directionUp= */ false); + + EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE)); +} + +/** + * Test tune cancellation. + * + * Verifies that: + * - the method does not crash after being invoked multiple times. + * + * Since cancel() might be called after the HAL completes an operation (tune, seek, and step) + * and before the callback completions, the operation might not be actually canceled and the + * effect of cancel() is not deterministic to be tested here. + */ +TEST_P(BroadcastRadioHalTest, Cancel) { + LOG(DEBUG) << "Cancel Test"; + + auto notSupportedError = resultToInt(Result::NOT_SUPPORTED); + for (int i = 0; i < 10; i++) { + auto result = mModule->seek(/* in_directionUp= */ true, /* in_skipSubChannel= */ true); + + if (result.getServiceSpecificError() == notSupportedError) { + printSkipped("Cancel is skipped because of seek not supported"); + return; + } + EXPECT_TRUE(result.isOk()); + + auto cancelResult = mModule->cancel(); + + ASSERT_TRUE(cancelResult.isOk()); + } +} + +/** + * Test getting program list using empty program filter. + * + * Verifies that: + * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED; + * - the complete list is fetched within kProgramListScanTimeoutSec; + * - stopProgramListUpdates does not crash. + */ +TEST_P(BroadcastRadioHalTest, GetProgramListFromEmptyFilter) { + LOG(DEBUG) << "GetProgramListFromEmptyFilter Test"; + + getProgramList(); +} + +/** + * Test getting program list using AMFM frequency program filter. + * + * Verifies that: + * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED; + * - the complete list is fetched within kProgramListScanTimeoutSec; + * - stopProgramListUpdates does not crash; + * - result for startProgramListUpdates using a filter with AMFM_FREQUENCY_KHZ value of the first + * AMFM program matches the expected result. + */ +TEST_P(BroadcastRadioHalTest, GetProgramListFromAmFmFilter) { + LOG(DEBUG) << "GetProgramListFromAmFmFilter Test"; + + std::optional completeList = getProgramList(); + if (!completeList) { + printSkipped("No program list available"); + return; + } + + ProgramFilter amfmFilter = {}; + int expectedResultSize = 0; + uint64_t expectedFreq = 0; + for (const auto& program : *completeList) { + vector amfmIds = + bcutils::getAllIds(program.selector, IdentifierType::AMFM_FREQUENCY_KHZ); + EXPECT_LE(amfmIds.size(), 1u); + if (amfmIds.size() == 0) { + continue; + } + + if (expectedResultSize == 0) { + expectedFreq = amfmIds[0]; + amfmFilter.identifiers = { + makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, expectedFreq)}; + expectedResultSize = 1; + } else if (amfmIds[0] == expectedFreq) { + expectedResultSize++; + } + } + + if (expectedResultSize == 0) { + printSkipped("No Am/FM programs available"); + return; + } + std::optional amfmList = getProgramList(amfmFilter); + ASSERT_EQ(amfmList->size(), expectedResultSize) << "amfm filter result size is wrong"; +} + +/** + * Test getting program list using DAB ensemble program filter. + * + * Verifies that: + * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED; + * - the complete list is fetched within kProgramListScanTimeoutSec; + * - stopProgramListUpdates does not crash; + * - result for startProgramListUpdates using a filter with DAB_ENSEMBLE value of the first DAB + * program matches the expected result. + */ +TEST_P(BroadcastRadioHalTest, GetProgramListFromDabFilter) { + LOG(DEBUG) << "GetProgramListFromDabFilter Test"; + + std::optional completeList = getProgramList(); + if (!completeList) { + printSkipped("No program list available"); + return; + } + + ProgramFilter dabFilter = {}; + int expectedResultSize = 0; + uint64_t expectedEnsemble = 0; + for (const auto& program : *completeList) { + auto dabEnsembles = bcutils::getAllIds(program.selector, IdentifierType::DAB_ENSEMBLE); + EXPECT_LE(dabEnsembles.size(), 1u); + if (dabEnsembles.size() == 0) { + continue; + } + + if (expectedResultSize == 0) { + expectedEnsemble = dabEnsembles[0]; + dabFilter.identifiers = { + makeIdentifier(IdentifierType::DAB_ENSEMBLE, expectedEnsemble)}; + expectedResultSize = 1; + } else if (dabEnsembles[0] == expectedEnsemble) { + expectedResultSize++; + } + } + + if (expectedResultSize == 0) { + printSkipped("No DAB programs available"); + return; + } + std::optional dabList = getProgramList(dabFilter); + ASSERT_EQ(dabList->size(), expectedResultSize) << "dab filter result size is wrong"; +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BroadcastRadioHalTest); +INSTANTIATE_TEST_SUITE_P( + PerInstance, BroadcastRadioHalTest, + testing::ValuesIn(::android::getAidlHalInstanceNames(IBroadcastRadio::descriptor)), + ::android::PrintInstanceNameToString); + +} // namespace aidl::android::hardware::broadcastradio::vts + +int main(int argc, char** argv) { + android::base::SetDefaultTag("BcRadio.vts"); + android::base::SetMinimumLogSeverity(android::base::VERBOSE); + ::testing::InitGoogleTest(&argc, argv); + ABinderProcess_setThreadPoolMaxThreadCount(4); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +}