From bceb88585dc6c89c6689956b8354b630c8425ae5 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Mon, 18 Dec 2017 13:59:29 -0800 Subject: [PATCH] Implement program list fetching. Bug: 69860743 Test: VTS Change-Id: I04eb43c1e0e1bb7bad86e123594a473454eed983 --- broadcastradio/2.0/Android.bp | 2 + broadcastradio/2.0/ITunerCallback.hal | 15 ++ broadcastradio/2.0/ITunerSession.hal | 26 ++++ broadcastradio/2.0/default/TunerSession.cpp | 33 +++++ broadcastradio/2.0/default/TunerSession.h | 2 + broadcastradio/2.0/types.hal | 137 ++++++++++++++++++ .../VtsHalBroadcastradioV2_0TargetTest.cpp | 61 +++++++- broadcastradio/common/tests/Android.bp | 47 ++++-- .../common/tests/IdentifierIterator_test.cpp | 123 ++++++++++++++++ broadcastradio/common/utils2x/Utils.cpp | 121 +++++++++++++++- .../include/broadcastradio-utils-2x/Utils.h | 50 +++++++ .../broadcastradio-vts-utils/mock-timeout.h | 39 ++++- 12 files changed, 622 insertions(+), 34 deletions(-) create mode 100644 broadcastradio/common/tests/IdentifierIterator_test.cpp diff --git a/broadcastradio/2.0/Android.bp b/broadcastradio/2.0/Android.bp index afbd6d4cfc..1d7861e458 100644 --- a/broadcastradio/2.0/Android.bp +++ b/broadcastradio/2.0/Android.bp @@ -21,9 +21,11 @@ hidl_interface { "IdentifierType", "Metadata", "MetadataKey", + "ProgramFilter", "ProgramIdentifier", "ProgramInfo", "ProgramInfoFlags", + "ProgramListChunk", "ProgramSelector", "Properties", "Result", diff --git a/broadcastradio/2.0/ITunerCallback.hal b/broadcastradio/2.0/ITunerCallback.hal index 1aefc4eb58..ede8350eff 100644 --- a/broadcastradio/2.0/ITunerCallback.hal +++ b/broadcastradio/2.0/ITunerCallback.hal @@ -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. * diff --git a/broadcastradio/2.0/ITunerSession.hal b/broadcastradio/2.0/ITunerSession.hal index 8a21768cc4..a3f93fd946 100644 --- a/broadcastradio/2.0/ITunerSession.hal +++ b/broadcastradio/2.0/ITunerSession.hal @@ -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. * diff --git a/broadcastradio/2.0/default/TunerSession.cpp b/broadcastradio/2.0/default/TunerSession.cpp index 54af33899d..36a22c562e 100644 --- a/broadcastradio/2.0/default/TunerSession.cpp +++ b/broadcastradio/2.0/default/TunerSession.cpp @@ -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 TunerSession::cancel() { return {}; } +Return TunerSession::startProgramListUpdates(const ProgramFilter& filter) { + ALOGV("%s(%s)", __func__, toString(filter).c_str()); + lock_guard lk(mMut); + if (mIsClosed) return Result::INVALID_STATE; + + auto list = virtualRadio().getProgramList(); + vector 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 lk(mMut); + + ProgramListChunk chunk = {}; + chunk.purge = true; + chunk.complete = true; + chunk.modified = hidl_vec(list.begin(), list.end()); + + mCallback->onProgramListUpdated(chunk); + }; + mThread.schedule(task, delay::list); + + return Result::OK; +} + +Return TunerSession::stopProgramListUpdates() { + ALOGV("%s", __func__); + return {}; +} + Return TunerSession::getConfigFlag(ConfigFlag flag, getConfigFlag_cb _hidl_cb) { ALOGV("%s(%s)", __func__, toString(flag).c_str()); diff --git a/broadcastradio/2.0/default/TunerSession.h b/broadcastradio/2.0/default/TunerSession.h index 9a72182afe..a58aa1952c 100644 --- a/broadcastradio/2.0/default/TunerSession.h +++ b/broadcastradio/2.0/default/TunerSession.h @@ -38,6 +38,8 @@ struct TunerSession : public ITunerSession { virtual Return scan(bool directionUp, bool skipSubChannel) override; virtual Return step(bool directionUp) override; virtual Return cancel() override; + virtual Return startProgramListUpdates(const ProgramFilter& filter); + virtual Return stopProgramListUpdates(); virtual Return getConfigFlag(ConfigFlag flag, getConfigFlag_cb _hidl_cb); virtual Return setConfigFlag(ConfigFlag flag, bool value); virtual Return setParameters(const hidl_vec& parameters, diff --git a/broadcastradio/2.0/types.hal b/broadcastradio/2.0/types.hal index fc5809f3e9..d50d485968 100644 --- a/broadcastradio/2.0/types.hal +++ b/broadcastradio/2.0/types.hal @@ -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 { @@ -455,6 +457,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, }; /** @@ -476,3 +514,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 modified; + + /** + * Removed program list entries. + * + * Contains primaryId (ProgramSelector member) of a program to remove. + */ + vec 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 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 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; +}; diff --git a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp index d0e41448ad..46b3f19de6 100644 --- a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp +++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp @@ -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 @@ -63,16 +65,20 @@ static const ConfigFlag gConfigFlagValues[] = { ConfigFlag::DAB_HARD_LINKING, ConfigFlag::DAB_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(Result, const ProgramSelector&)); MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChanged, Return(const ProgramInfo&)); + Return onProgramListUpdated(const ProgramListChunk& chunk); MOCK_METHOD1(onAntennaStateChange, Return(bool connected)); MOCK_METHOD1(onParametersUpdated, Return(const hidl_vec& parameters)); + + MOCK_TIMEOUT_METHOD0(onProgramListReady, void()); + + std::mutex mLock; + utils::ProgramInfoSet mProgramList; }; class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase { @@ -88,6 +94,25 @@ class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase { sp 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 TunerCallbackMock::onProgramListUpdated(const ProgramListChunk& chunk) { + std::lock_guard lk(mLock); + + updateProgramList(mProgramList, chunk); + + if (chunk.complete) onProgramListReady(); + + return {}; +} + void BroadcastRadioHalTest::SetUp() { EXPECT_EQ(nullptr, mModule.get()) << "Module is already open"; @@ -463,6 +488,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 diff --git a/broadcastradio/common/tests/Android.bp b/broadcastradio/common/tests/Android.bp index bbad527c6d..512c02e496 100644 --- a/broadcastradio/common/tests/Android.bp +++ b/broadcastradio/common/tests/Android.bp @@ -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"], +} diff --git a/broadcastradio/common/tests/IdentifierIterator_test.cpp b/broadcastradio/common/tests/IdentifierIterator_test.cpp new file mode 100644 index 0000000000..5bf222bfd2 --- /dev/null +++ b/broadcastradio/common/tests/IdentifierIterator_test.cpp @@ -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 +#include + +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 diff --git a/broadcastradio/common/utils2x/Utils.cpp b/broadcastradio/common/utils2x/Utils.cpp index d15710810a..10a155b136 100644 --- a/broadcastradio/common/utils2x/Utils.cpp +++ b/broadcastradio/common/utils2x/Utils.cpp @@ -18,6 +18,7 @@ #include +#include #include 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(typeAsInt); +} + IdentifierType getType(const ProgramIdentifier& id) { - return static_cast(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(&sel()) != reinterpret_cast(&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 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 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(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(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{}(id.type); + h += 0x9e3779b9; + h ^= std::hash{}(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 diff --git a/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h b/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h index dd01852e8e..bac11fd028 100644 --- a/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h +++ b/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h @@ -20,14 +20,49 @@ #include #include #include +#include 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 { + using traits = std::iterator_traits; + 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 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 + ProgramInfoSet; + +void updateProgramList(ProgramInfoSet& list, const V2_0::ProgramListChunk& chunk); + } // namespace utils } // namespace broadcastradio } // namespace hardware diff --git a/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h index b0ce08806d..12453bbe26 100644 --- a/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h +++ b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h @@ -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 +static T EGMockFlippedComma_(std::function returned, std::function discarded) { + auto ret = returned(); + discarded(); + return ret; +} + +template <> +inline void EGMockFlippedComma_(std::function returned, std::function 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 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 lk(egmock_mut_##Method); \ + egmock_called_##Method = true; \ + egmock_cond_##Method.notify_all(); \ + }; \ + return EGMockFlippedComma_(invokeMock, notify); /** * Gmock MOCK_METHOD0 timeout-capable extension.