Merge "Implement program list fetching."

This commit is contained in:
Tomasz Wasilczyk
2017-12-22 20:44:15 +00:00
committed by Android (Google) Code Review
12 changed files with 622 additions and 34 deletions

View File

@@ -21,9 +21,11 @@ hidl_interface {
"IdentifierType",
"Metadata",
"MetadataKey",
"ProgramFilter",
"ProgramIdentifier",
"ProgramInfo",
"ProgramInfoFlags",
"ProgramListChunk",
"ProgramSelector",
"Properties",
"Result",

View File

@@ -38,6 +38,21 @@ interface ITunerCallback {
*/
oneway onCurrentProgramInfoChanged(ProgramInfo info);
/**
* A delta update of the program list, called whenever there's a change in
* the list.
*
* If there are frequent changes, HAL implementation must throttle the rate
* of the updates.
*
* There is a hard limit on binder transaction buffer, and the list must
* not exceed it. For large lists, HAL implementation must split them to
* multiple chunks, no larger than 500kiB each.
*
* @param chunk A chunk of the program list update.
*/
oneway onProgramListUpdated(ProgramListChunk chunk);
/**
* Method called by the HAL when the antenna gets connected or disconnected.
*

View File

@@ -76,6 +76,32 @@ interface ITunerSession {
*/
cancel();
/**
* Applies a filter to the program list and starts sending program list
* updates over onProgramListUpdated callback.
*
* There may be only one updates stream active at the moment. Calling this
* method again must result in cancelling the previous update request.
*
* This call clears the program list on the client side, the HAL must send
* the whole list again.
*
* If the program list scanning hardware (i.e. background tuner) is
* unavailable at the moment, the call must succeed and start updates
* when it becomes available.
*
* @param filter Filter to apply on the fetched program list.
* @return result OK successfully started fetching list updates.
* NOT_SUPPORTED program list scanning is not supported
* by the hardware.
*/
startProgramListUpdates(ProgramFilter filter) generates (Result result);
/**
* Stops sending program list updates.
*/
stopProgramListUpdates();
/**
* Fetches the current setting of a given config flag.
*

View File

@@ -45,6 +45,7 @@ namespace delay {
static constexpr auto scan = 200ms;
static constexpr auto step = 100ms;
static constexpr auto tune = 150ms;
static constexpr auto list = 1s;
} // namespace delay
@@ -205,6 +206,38 @@ Return<void> TunerSession::cancel() {
return {};
}
Return<Result> TunerSession::startProgramListUpdates(const ProgramFilter& filter) {
ALOGV("%s(%s)", __func__, toString(filter).c_str());
lock_guard<mutex> lk(mMut);
if (mIsClosed) return Result::INVALID_STATE;
auto list = virtualRadio().getProgramList();
vector<VirtualProgram> filteredList;
auto filterCb = [&filter](const VirtualProgram& program) {
return utils::satisfies(filter, program.selector);
};
std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb);
auto task = [this, list]() {
lock_guard<mutex> lk(mMut);
ProgramListChunk chunk = {};
chunk.purge = true;
chunk.complete = true;
chunk.modified = hidl_vec<ProgramInfo>(list.begin(), list.end());
mCallback->onProgramListUpdated(chunk);
};
mThread.schedule(task, delay::list);
return Result::OK;
}
Return<void> TunerSession::stopProgramListUpdates() {
ALOGV("%s", __func__);
return {};
}
Return<void> TunerSession::getConfigFlag(ConfigFlag flag, getConfigFlag_cb _hidl_cb) {
ALOGV("%s(%s)", __func__, toString(flag).c_str());

View File

@@ -38,6 +38,8 @@ struct TunerSession : public ITunerSession {
virtual Return<Result> scan(bool directionUp, bool skipSubChannel) override;
virtual Return<Result> step(bool directionUp) override;
virtual Return<void> cancel() override;
virtual Return<Result> startProgramListUpdates(const ProgramFilter& filter);
virtual Return<void> stopProgramListUpdates();
virtual Return<void> getConfigFlag(ConfigFlag flag, getConfigFlag_cb _hidl_cb);
virtual Return<Result> setConfigFlag(ConfigFlag flag, bool value);
virtual Return<void> setParameters(const hidl_vec<VendorKeyValue>& parameters,

View File

@@ -25,6 +25,8 @@ enum Constants : int32_t {
* onAntennaStateChange callback must be called within this time.
*/
ANTENNA_DISCONNECTED_TIMEOUT_MS = 100,
LIST_COMPLETE_TIMEOUT_MS = 300000,
};
enum Result : int32_t {
@@ -451,6 +453,42 @@ enum MetadataKey : int32_t {
/** Album art (uint32_t, see IBroadcastRadio::getImage) */
ALBUM_ART,
/**
* Station name.
*
* This is a generic field to cover any radio technology.
*
* If the PROGRAM_NAME has the same content as DAB_*_NAME or RDS_PS,
* it may not be present, to preserve space - framework must repopulate
* it on the client side.
*/
PROGRAM_NAME,
/** DAB ensemble name (string) */
DAB_ENSEMBLE_NAME,
/**
* DAB ensemble name abbreviated (string).
*
* The string must be up to 8 characters long.
*
* If the short variant is present, the long (DAB_ENSEMBLE_NAME) one must be
* present as well.
*/
DAB_ENSEMBLE_NAME_SHORT,
/** DAB service name (string) */
DAB_SERVICE_NAME,
/** DAB service name abbreviated (see DAB_ENSEMBLE_NAME_SHORT) (string) */
DAB_SERVICE_NAME_SHORT,
/** DAB component name (string) */
DAB_COMPONENT_NAME,
/** DAB component name abbreviated (see DAB_ENSEMBLE_NAME_SHORT) (string) */
DAB_COMPONENT_NAME_SHORT,
};
/**
@@ -472,3 +510,102 @@ struct Metadata {
int64_t intValue;
string stringValue;
};
/**
* An update packet of the program list.
*
* The order of entries in the vectors is unspecified.
*/
struct ProgramListChunk {
/**
* Treats all previously added entries as removed.
*
* This is meant to save binder transaction bandwidth on 'removed' vector
* and provide a clear empty state.
*
* If set, 'removed' vector must be empty.
*
* The client may wait with taking action on this until it received the
* chunk with complete flag set (to avoid part of stations temporarily
* disappearing from the list).
*/
bool purge;
/**
* If false, it means there are still programs not transmitted,
* due for transmission in following updates.
*
* Used by UIs that wait for complete list instead of displaying
* programs while scanning.
*
* After the whole channel range was scanned and all discovered programs
* were transmitted, the last chunk must have set this flag to true.
* This must happen within Constants::LIST_COMPLETE_TIMEOUT_MS from the
* startProgramListUpdates call. If it doesn't, client may assume the tuner
* came into a bad state and display error message.
*/
bool complete;
/**
* Added or modified program list entries.
*
* Two entries with the same primaryId (ProgramSelector member)
* are considered the same.
*/
vec<ProgramInfo> modified;
/**
* Removed program list entries.
*
* Contains primaryId (ProgramSelector member) of a program to remove.
*/
vec<ProgramIdentifier> removed;
};
/**
* Large-grain filter to the program list.
*
* This is meant to reduce binder transaction bandwidth, not for fine-grained
* filtering user might expect.
*
* The filter is designed as conjunctive normal form: the entry that passes the
* filter must satisfy all the clauses (members of this struct). Vector clauses
* are disjunctions of literals. In other words, there is AND between each
* high-level group and OR inside it.
*/
struct ProgramFilter {
/**
* List of identifier types that satisfy the filter.
*
* If the program list entry contains at least one identifier of the type
* listed, it satisfies this condition.
*
* Empty list means no filtering on identifier type.
*/
vec<uint32_t> identifierTypes;
/**
* List of identifiers that satisfy the filter.
*
* If the program list entry contains at least one listed identifier,
* it satisfies this condition.
*
* Empty list means no filtering on identifier.
*/
vec<ProgramIdentifier> identifiers;
/**
* Includes non-tunable entries that define tree structure on the
* program list (i.e. DAB ensembles).
*/
bool includeCategories;
/**
* Disable updates on entry modifications.
*
* If true, 'modified' vector of ProgramListChunk must contain list
* additions only. Once the program is added to the list, it's not
* updated anymore.
*/
bool excludeModifications;
};

View File

@@ -38,6 +38,7 @@ namespace vts {
using namespace std::chrono_literals;
using std::unordered_set;
using std::vector;
using testing::_;
using testing::AnyNumber;
@@ -54,6 +55,7 @@ using utils::make_selector_amfm;
namespace timeout {
static constexpr auto tune = 30s;
static constexpr auto programListScan = 5min;
} // namespace timeout
@@ -69,16 +71,20 @@ static const ConfigFlag gConfigFlagValues[] = {
ConfigFlag::DAB_FM_SOFT_LINKING,
};
struct TunerCallbackMock : public ITunerCallback {
TunerCallbackMock() {
// we expect the antenna is connected through the whole test
EXPECT_CALL(*this, onAntennaStateChange(false)).Times(0);
}
class TunerCallbackMock : public ITunerCallback {
public:
TunerCallbackMock();
MOCK_METHOD2(onTuneFailed, Return<void>(Result, const ProgramSelector&));
MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChanged, Return<void>(const ProgramInfo&));
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;
};
class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase {
@@ -94,6 +100,25 @@ class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase {
sp<TunerCallbackMock> mCallback = new TunerCallbackMock();
};
static void printSkipped(std::string msg) {
std::cout << "[ SKIPPED ] " << msg << std::endl;
}
TunerCallbackMock::TunerCallbackMock() {
// we expect the antenna is connected through the whole test
EXPECT_CALL(*this, onAntennaStateChange(false)).Times(0);
}
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";
@@ -469,6 +494,32 @@ TEST_F(BroadcastRadioHalTest, SetConfigFlags) {
}
}
/**
* 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_F(BroadcastRadioHalTest, GetProgramList) {
ASSERT_TRUE(openSession());
EXPECT_TIMEOUT_CALL(*mCallback, onProgramListReady).Times(AnyNumber());
auto startResult = mSession->startProgramListUpdates({});
if (startResult == Result::NOT_SUPPORTED) {
printSkipped("Program list not supported");
return;
}
ASSERT_EQ(Result::OK, startResult);
EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onProgramListReady, timeout::programListScan);
auto stopResult = mSession->stopProgramListUpdates();
EXPECT_TRUE(stopResult.isOk());
}
} // namespace vts
} // namespace V2_0
} // namespace broadcastradio

View File

@@ -14,20 +14,6 @@
// limitations under the License.
//
cc_test {
name: "android.hardware.broadcastradio@common-utils-tests",
vendor: true,
cflags: [
"-Wall",
"-Wextra",
"-Werror",
],
srcs: [
"WorkerThread_test.cpp",
],
static_libs: ["android.hardware.broadcastradio@common-utils-lib"],
}
cc_test {
name: "android.hardware.broadcastradio@common-utils-xx-tests",
vendor: true,
@@ -48,3 +34,36 @@ cc_test {
"android.hardware.broadcastradio@2.0",
],
}
cc_test {
name: "android.hardware.broadcastradio@common-utils-2x-tests",
vendor: true,
cflags: [
"-Wall",
"-Wextra",
"-Werror",
],
srcs: [
"IdentifierIterator_test.cpp",
],
static_libs: [
"android.hardware.broadcastradio@common-utils-2x-lib",
],
shared_libs: [
"android.hardware.broadcastradio@2.0",
],
}
cc_test {
name: "android.hardware.broadcastradio@common-utils-tests",
vendor: true,
cflags: [
"-Wall",
"-Wextra",
"-Werror",
],
srcs: [
"WorkerThread_test.cpp",
],
static_libs: ["android.hardware.broadcastradio@common-utils-lib"],
}

View File

@@ -0,0 +1,123 @@
/*
* 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.
*/
#include <broadcastradio-utils-2x/Utils.h>
#include <gtest/gtest.h>
namespace {
namespace V2_0 = android::hardware::broadcastradio::V2_0;
namespace utils = android::hardware::broadcastradio::utils;
using V2_0::IdentifierType;
using V2_0::ProgramSelector;
TEST(IdentifierIteratorTest, singleSecondary) {
// clang-format off
V2_0::ProgramSelector sel {
utils::make_identifier(IdentifierType::RDS_PI, 0xBEEF),
{utils::make_identifier(IdentifierType::AMFM_FREQUENCY, 100100)}
};
// clang-format on
auto it = utils::begin(sel);
auto end = utils::end(sel);
ASSERT_NE(end, it);
EXPECT_EQ(sel.primaryId, *it);
ASSERT_NE(end, ++it);
EXPECT_EQ(sel.secondaryIds[0], *it);
ASSERT_EQ(end, ++it);
}
TEST(IdentifierIteratorTest, empty) {
V2_0::ProgramSelector sel{};
auto it = utils::begin(sel);
auto end = utils::end(sel);
ASSERT_NE(end, it++); // primary id is always present
ASSERT_EQ(end, it);
}
TEST(IdentifierIteratorTest, twoSelectors) {
V2_0::ProgramSelector sel1{};
V2_0::ProgramSelector sel2{};
auto it1 = utils::begin(sel1);
auto it2 = utils::begin(sel2);
EXPECT_NE(it1, it2);
}
TEST(IdentifierIteratorTest, increments) {
V2_0::ProgramSelector sel{{}, {{}, {}}};
auto it = utils::begin(sel);
auto end = utils::end(sel);
auto pre = it;
auto post = it;
EXPECT_NE(++pre, post++);
EXPECT_EQ(pre, post);
EXPECT_EQ(pre, it + 1);
ASSERT_NE(end, pre);
}
TEST(IdentifierIteratorTest, findType) {
using namespace std::placeholders;
uint64_t rds_pi1 = 0xDEAD;
uint64_t rds_pi2 = 0xBEEF;
uint64_t freq1 = 100100;
uint64_t freq2 = 107900;
// clang-format off
V2_0::ProgramSelector sel {
utils::make_identifier(IdentifierType::RDS_PI, rds_pi1),
{
utils::make_identifier(IdentifierType::AMFM_FREQUENCY, freq1),
utils::make_identifier(IdentifierType::RDS_PI, rds_pi2),
utils::make_identifier(IdentifierType::AMFM_FREQUENCY, freq2),
}
};
// clang-format on
auto typeEquals = [](const V2_0::ProgramIdentifier& id, V2_0::IdentifierType type) {
return utils::getType(id) == type;
};
auto isRdsPi = std::bind(typeEquals, _1, IdentifierType::RDS_PI);
auto isFreq = std::bind(typeEquals, _1, IdentifierType::AMFM_FREQUENCY);
auto end = utils::end(sel);
auto it = std::find_if(utils::begin(sel), end, isRdsPi);
ASSERT_NE(end, it);
EXPECT_EQ(rds_pi1, it->value);
it = std::find_if(it + 1, end, isRdsPi);
ASSERT_NE(end, it);
EXPECT_EQ(rds_pi2, it->value);
it = std::find_if(utils::begin(sel), end, isFreq);
ASSERT_NE(end, it);
EXPECT_EQ(freq1, it->value);
it = std::find_if(++it, end, isFreq);
ASSERT_NE(end, it);
EXPECT_EQ(freq2, it->value);
}
} // anonymous namespace

View File

@@ -18,6 +18,7 @@
#include <broadcastradio-utils-2x/Utils.h>
#include <android-base/logging.h>
#include <log/log.h>
namespace android {
@@ -28,14 +29,64 @@ namespace utils {
using V2_0::IdentifierType;
using V2_0::Metadata;
using V2_0::MetadataKey;
using V2_0::ProgramFilter;
using V2_0::ProgramIdentifier;
using V2_0::ProgramInfo;
using V2_0::ProgramListChunk;
using V2_0::ProgramSelector;
using V2_0::Properties;
using std::string;
using std::vector;
IdentifierType getType(uint32_t typeAsInt) {
return static_cast<IdentifierType>(typeAsInt);
}
IdentifierType getType(const ProgramIdentifier& id) {
return static_cast<IdentifierType>(id.type);
return getType(id.type);
}
IdentifierIterator::IdentifierIterator(const V2_0::ProgramSelector& sel)
: IdentifierIterator(sel, 0) {}
IdentifierIterator::IdentifierIterator(const V2_0::ProgramSelector& sel, size_t pos)
: mSel(sel), mPos(pos) {}
IdentifierIterator IdentifierIterator::operator++(int) {
auto i = *this;
mPos++;
return i;
}
IdentifierIterator& IdentifierIterator::operator++() {
++mPos;
return *this;
}
IdentifierIterator::ref_type IdentifierIterator::operator*() const {
if (mPos == 0) return sel().primaryId;
// mPos is 1-based for secondary identifiers
DCHECK(mPos <= sel().secondaryIds.size());
return sel().secondaryIds[mPos - 1];
}
bool IdentifierIterator::operator==(const IdentifierIterator& rhs) const {
// Check, if both iterators points at the same selector.
if (reinterpret_cast<uintptr_t>(&sel()) != reinterpret_cast<uintptr_t>(&rhs.sel())) {
return false;
}
return mPos == rhs.mPos;
}
IdentifierIterator begin(const V2_0::ProgramSelector& sel) {
return IdentifierIterator(sel);
}
IdentifierIterator end(const V2_0::ProgramSelector& sel) {
return IdentifierIterator(sel) + 1 /* primary id */ + sel.secondaryIds.size();
}
static bool bothHaveId(const ProgramSelector& a, const ProgramSelector& b,
@@ -88,6 +139,7 @@ static bool maybeGetId(const ProgramSelector& sel, const IdentifierType type, ui
return true;
}
// TODO(twasilczyk): use IdentifierIterator
// not optimal, but we don't care in default impl
for (auto&& id : sel.secondaryIds) {
if (id.type == itype) {
@@ -125,6 +177,7 @@ vector<uint64_t> getAllIds(const ProgramSelector& sel, const IdentifierType type
if (sel.primaryId.type == itype) ret.push_back(sel.primaryId.value);
// TODO(twasilczyk): use IdentifierIterator
for (auto&& id : sel.secondaryIds) {
if (id.type == itype) ret.push_back(id.value);
}
@@ -132,11 +185,11 @@ vector<uint64_t> getAllIds(const ProgramSelector& sel, const IdentifierType type
return ret;
}
bool isSupported(const V2_0::Properties& prop, const V2_0::ProgramSelector& sel) {
bool isSupported(const Properties& prop, const ProgramSelector& sel) {
// TODO(twasilczyk): use IdentifierIterator
// Not optimal, but it doesn't matter for default impl nor VTS tests.
for (auto&& idTypeI : prop.supportedIdentifierTypes) {
auto idType = static_cast<IdentifierType>(idTypeI);
if (hasId(sel, idType)) return true;
for (auto&& idType : prop.supportedIdentifierTypes) {
if (hasId(sel, getType(idType))) return true;
}
return false;
}
@@ -152,7 +205,7 @@ static bool isValid(const ProgramIdentifier& id) {
}
};
switch (static_cast<IdentifierType>(id.type)) {
switch (getType(id)) {
case IdentifierType::AMFM_FREQUENCY:
case IdentifierType::DAB_FREQUENCY:
case IdentifierType::DRMO_FREQUENCY:
@@ -211,8 +264,9 @@ static bool isValid(const ProgramIdentifier& id) {
return valid;
}
bool isValid(const V2_0::ProgramSelector& sel) {
bool isValid(const ProgramSelector& sel) {
if (!isValid(sel.primaryId)) return false;
// TODO(twasilczyk): use IdentifierIterator
for (auto&& id : sel.secondaryIds) {
if (!isValid(id)) return false;
}
@@ -243,6 +297,59 @@ Metadata make_metadata(MetadataKey key, string value) {
return meta;
}
bool satisfies(const ProgramFilter& filter, const ProgramSelector& sel) {
if (filter.identifierTypes.size() > 0) {
auto typeEquals = [](const V2_0::ProgramIdentifier& id, uint32_t type) {
return id.type == type;
};
auto it = std::find_first_of(begin(sel), end(sel), filter.identifierTypes.begin(),
filter.identifierTypes.end(), typeEquals);
if (it == end(sel)) return false;
}
if (filter.identifiers.size() > 0) {
auto it = std::find_first_of(begin(sel), end(sel), filter.identifiers.begin(),
filter.identifiers.end());
if (it == end(sel)) return false;
}
if (!filter.includeCategories) {
if (getType(sel.primaryId) == IdentifierType::DAB_ENSEMBLE) return false;
}
return true;
}
size_t ProgramInfoHasher::operator()(const ProgramInfo& info) const {
auto& id = info.selector.primaryId;
/* This is not the best hash implementation, but good enough for default HAL
* implementation and tests. */
auto h = std::hash<uint32_t>{}(id.type);
h += 0x9e3779b9;
h ^= std::hash<uint64_t>{}(id.value);
return h;
}
bool ProgramInfoKeyEqual::operator()(const ProgramInfo& info1, const ProgramInfo& info2) const {
auto& id1 = info1.selector.primaryId;
auto& id2 = info2.selector.primaryId;
return id1.type == id2.type && id1.value == id2.value;
}
void updateProgramList(ProgramInfoSet& list, const ProgramListChunk& chunk) {
if (chunk.purge) list.clear();
list.insert(chunk.modified.begin(), chunk.modified.end());
for (auto&& id : chunk.removed) {
ProgramInfo info = {};
info.selector.primaryId = id;
list.erase(info);
}
}
} // namespace utils
} // namespace broadcastradio
} // namespace hardware

View File

@@ -20,14 +20,49 @@
#include <chrono>
#include <queue>
#include <thread>
#include <unordered_set>
namespace android {
namespace hardware {
namespace broadcastradio {
namespace utils {
V2_0::IdentifierType getType(uint32_t typeAsInt);
V2_0::IdentifierType getType(const V2_0::ProgramIdentifier& id);
class IdentifierIterator
: public std::iterator<std::random_access_iterator_tag, V2_0::ProgramIdentifier, ssize_t,
const V2_0::ProgramIdentifier*, const V2_0::ProgramIdentifier&> {
using traits = std::iterator_traits<IdentifierIterator>;
using ptr_type = typename traits::pointer;
using ref_type = typename traits::reference;
using diff_type = typename traits::difference_type;
public:
explicit IdentifierIterator(const V2_0::ProgramSelector& sel);
IdentifierIterator operator++(int);
IdentifierIterator& operator++();
ref_type operator*() const;
inline ptr_type operator->() const { return &operator*(); }
IdentifierIterator operator+(diff_type v) const { return IdentifierIterator(mSel, mPos + v); }
bool operator==(const IdentifierIterator& rhs) const;
inline bool operator!=(const IdentifierIterator& rhs) const { return !operator==(rhs); };
private:
explicit IdentifierIterator(const V2_0::ProgramSelector& sel, size_t pos);
std::reference_wrapper<const V2_0::ProgramSelector> mSel;
const V2_0::ProgramSelector& sel() const { return mSel.get(); }
/** 0 is the primary identifier, 1-n are secondary identifiers. */
size_t mPos = 0;
};
IdentifierIterator begin(const V2_0::ProgramSelector& sel);
IdentifierIterator end(const V2_0::ProgramSelector& sel);
/**
* Checks, if {@code pointer} tunes to {@channel}.
*
@@ -77,6 +112,21 @@ V2_0::ProgramSelector make_selector_amfm(uint32_t frequency);
V2_0::Metadata make_metadata(V2_0::MetadataKey key, int64_t value);
V2_0::Metadata make_metadata(V2_0::MetadataKey key, std::string value);
bool satisfies(const V2_0::ProgramFilter& filter, const V2_0::ProgramSelector& sel);
struct ProgramInfoHasher {
size_t operator()(const V2_0::ProgramInfo& info) const;
};
struct ProgramInfoKeyEqual {
bool operator()(const V2_0::ProgramInfo& info1, const V2_0::ProgramInfo& info2) const;
};
typedef std::unordered_set<V2_0::ProgramInfo, ProgramInfoHasher, ProgramInfoKeyEqual>
ProgramInfoSet;
void updateProgramList(ProgramInfoSet& list, const V2_0::ProgramListChunk& chunk);
} // namespace utils
} // namespace broadcastradio
} // namespace hardware

View File

@@ -29,19 +29,42 @@
std::mutex egmock_mut_##Method; \
std::condition_variable egmock_cond_##Method;
/**
* Function similar to comma operator, to make it possible to return any value returned by mocked
* function (which may be void) and discard the result of the other operation (notification about
* a call).
*
* We need to invoke the mocked function (which result is returned) before the notification (which
* result is dropped) - that's exactly the opposite of comma operator.
*
* INTERNAL IMPLEMENTATION - don't use in user code.
*/
template <typename T>
static T EGMockFlippedComma_(std::function<T()> returned, std::function<void()> discarded) {
auto ret = returned();
discarded();
return ret;
}
template <>
inline void EGMockFlippedComma_(std::function<void()> returned, std::function<void()> discarded) {
returned();
discarded();
}
/**
* Common method body for gmock timeout extension.
*
* INTERNAL IMPLEMENTATION - don't use in user code.
*/
#define EGMOCK_TIMEOUT_METHOD_BODY_(Method, ...) \
auto ret = egmock_##Method(__VA_ARGS__); \
{ \
std::lock_guard<std::mutex> lk(egmock_mut_##Method); \
egmock_called_##Method = true; \
egmock_cond_##Method.notify_all(); \
} \
return ret;
#define EGMOCK_TIMEOUT_METHOD_BODY_(Method, ...) \
auto invokeMock = [&]() { return egmock_##Method(__VA_ARGS__); }; \
auto notify = [&]() { \
std::lock_guard<std::mutex> lk(egmock_mut_##Method); \
egmock_called_##Method = true; \
egmock_cond_##Method.notify_all(); \
}; \
return EGMockFlippedComma_<decltype(invokeMock())>(invokeMock, notify);
/**
* Gmock MOCK_METHOD0 timeout-capable extension.