You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
890 lines
28 KiB
890 lines
28 KiB
4 months ago
|
/*
|
||
|
* 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();
|
||
|
|
||
|
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() {
|
||
|
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<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) {
|
||
|
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<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.
|
||
|
*
|
||
|
* 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<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();
|
||
|
}
|