Files
hardware_interfaces/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
Weilin Xu 8de55ffd99 Fix programList update bug in default radio HAL
In default implementation of broadcast radio HIDL2 HAL, the
filtered program list is actually not used in callback function.
Use the filtered program list rather than the whole list for the
program list chunk used in onProgramListUpdated callback. Extra
VTS tests checking whether program list update using AMFM and DAB
filters gets the expected program list results are also added.

Bug: 237412013
Test: m -j
Test: atest VtsHalBroadcastradioV2_0TargetTest
Change-Id: I5969638f14a39b9aea2d8b465f3a7fbcd93a8409
2022-06-29 18:25:33 +00:00

991 lines
32 KiB
C++

/*
* 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 <android-base/logging.h>
#include <android-base/strings.h>
#include <android/hardware/broadcastradio/2.0/IBroadcastRadio.h>
#include <android/hardware/broadcastradio/2.0/ITunerCallback.h>
#include <android/hardware/broadcastradio/2.0/ITunerSession.h>
#include <android/hardware/broadcastradio/2.0/types.h>
#include <broadcastradio-utils-2x/Utils.h>
#include <broadcastradio-vts-utils/call-barrier.h>
#include <broadcastradio-vts-utils/mock-timeout.h>
#include <broadcastradio-vts-utils/pointer-utils.h>
#include <cutils/bitops.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
#include <chrono>
#include <optional>
#include <regex>
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<void>(Result, const ProgramSelector&));
MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChanged_, Return<void>(const ProgramInfo&));
virtual Return<void> onCurrentProgramInfoChanged(const ProgramInfo& info);
Return<void> onProgramListUpdated(const ProgramListChunk& chunk);
MOCK_METHOD1(onAntennaStateChange, Return<void>(bool connected));
MOCK_METHOD1(onParametersUpdated, Return<void>(const hidl_vec<VendorKeyValue>& parameters));
MOCK_TIMEOUT_METHOD0(onProgramListReady, void());
std::mutex mLock;
utils::ProgramInfoSet mProgramList;
};
struct AnnouncementListenerMock : public IAnnouncementListener {
MOCK_METHOD1(onListUpdated, Return<void>(const hidl_vec<Announcement>&));
};
class BroadcastRadioHalTest : public ::testing::TestWithParam<std::string> {
protected:
virtual void SetUp() override;
virtual void TearDown() override;
bool openSession();
bool getAmFmRegionConfig(bool full, AmFmRegionConfig* config);
std::optional<utils::ProgramInfoSet> getProgramList();
std::optional<utils::ProgramInfoSet> getProgramList(const ProgramFilter& filter);
sp<IBroadcastRadio> mModule;
Properties mProperties;
sp<ITunerSession> mSession;
sp<TunerCallbackMock> 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<void> 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<void> TunerCallbackMock::onProgramListUpdated(const ProgramListChunk& chunk) {
std::lock_guard<std::mutex> 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<ITunerSession>& 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<utils::ProgramInfoSet> BroadcastRadioHalTest::getProgramList() {
ProgramFilter emptyFilter = {};
return getProgramList(emptyFilter);
}
std::optional<utils::ProgramInfoSet> BroadcastRadioHalTest::getProgramList(
const ProgramFilter& filter) {
EXPECT_TIMEOUT_CALL(*mCallback, onProgramListReady).Times(AnyNumber());
auto startResult = mSession->startProgramListUpdates(filter);
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<DabTableEntry> config;
auto cb = [&](Result result, hidl_vec<DabTableEntry> 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<ProgramIdentifier> 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) {
Result halResult;
hidl_vec<DabTableEntry> config;
auto cb = [&](Result result, hidl_vec<DabTableEntry> 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);
ASSERT_NE(config.size(), 0U);
ASSERT_TRUE(openSession());
ProgramSelector sel = {};
uint64_t freq = config[config.size() / 2].frequency;
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<VendorKeyValue> halResults = {};
bool wasCalled = false;
auto cb = [&](hidl_vec<VendorKeyValue> 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<VendorKeyValue> halResults = {};
bool wasCalled = false;
auto cb = [&](hidl_vec<VendorKeyValue> 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<uint8_t> 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 using empty program filter.
*
* Verifies that:
* - startProgramListUpdates either succeeds or returns NOT_SUPPORTED;
* - the complete list is fetched within timeout::programListScan;
* - stopProgramListUpdates does not crash.
*/
TEST_P(BroadcastRadioHalTest, GetProgramListFromEmptyFilter) {
ASSERT_TRUE(openSession());
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 timeout::programListScan;
* - stopProgramListUpdates does not crash;
* - result for startProgramListUpdates using a filter with AMFM_FREQUENCY value of the first AMFM
* program matches the expected result.
*/
TEST_P(BroadcastRadioHalTest, GetProgramListFromAmFmFilter) {
ASSERT_TRUE(openSession());
auto completeList = getProgramList();
if (!completeList) return;
ProgramFilter amfmFilter = {};
int expectedResultSize = 0;
uint64_t expectedFreq = 0;
for (auto&& program : *completeList) {
auto amfmIds = utils::getAllIds(program.selector, IdentifierType::AMFM_FREQUENCY);
EXPECT_LE(amfmIds.size(), 1u);
if (amfmIds.size() == 0) continue;
if (expectedResultSize == 0) {
expectedFreq = amfmIds[0];
amfmFilter.identifiers = {
make_identifier(IdentifierType::AMFM_FREQUENCY, expectedFreq)};
expectedResultSize = 1;
} else if (amfmIds[0] == expectedFreq) {
expectedResultSize++;
}
}
if (expectedResultSize == 0) return;
auto amfmList = getProgramList(amfmFilter);
ASSERT_EQ(expectedResultSize, amfmList->size()) << "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 timeout::programListScan;
* - 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) {
ASSERT_TRUE(openSession());
auto completeList = getProgramList();
if (!completeList) return;
ProgramFilter dabFilter = {};
int expectedResultSize = 0;
uint64_t expectedEnsemble = 0;
for (auto&& program : *completeList) {
auto dabEnsembles = utils::getAllIds(program.selector, IdentifierType::DAB_ENSEMBLE);
EXPECT_LE(dabEnsembles.size(), 1u);
if (dabEnsembles.size() == 0) continue;
if (expectedResultSize == 0) {
expectedEnsemble = dabEnsembles[0];
dabFilter.identifiers = {
make_identifier(IdentifierType::DAB_ENSEMBLE, expectedEnsemble)};
expectedResultSize = 1;
} else if (dabEnsembles[0] == expectedEnsemble) {
expectedResultSize++;
}
}
if (expectedResultSize == 0) return;
auto dabList = getProgramList(dabFilter);
ASSERT_EQ(expectedResultSize, dabList->size()) << "dab filter result size is wrong";
}
/**
* 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<AnnouncementListenerMock> listener = new AnnouncementListenerMock();
Result halResult = Result::UNKNOWN_ERROR;
sp<ICloseHandle> closeHandle = nullptr;
auto cb = [&](Result result, const sp<ICloseHandle>& 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();
}