mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 16:50:18 +00:00
Merge "Implement program list fetching."
This commit is contained in:
committed by
Android (Google) Code Review
commit
eb2b87ba3e
@@ -21,9 +21,11 @@ hidl_interface {
|
||||
"IdentifierType",
|
||||
"Metadata",
|
||||
"MetadataKey",
|
||||
"ProgramFilter",
|
||||
"ProgramIdentifier",
|
||||
"ProgramInfo",
|
||||
"ProgramInfoFlags",
|
||||
"ProgramListChunk",
|
||||
"ProgramSelector",
|
||||
"Properties",
|
||||
"Result",
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
}
|
||||
|
||||
123
broadcastradio/common/tests/IdentifierIterator_test.cpp
Normal file
123
broadcastradio/common/tests/IdentifierIterator_test.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user