Implement program list fetching.
Bug: 69860743
Test: VTS
Change-Id: I04eb43c1e0e1bb7bad86e123594a473454eed983
diff --git a/broadcastradio/2.0/Android.bp b/broadcastradio/2.0/Android.bp
index afbd6d4..1d7861e 100644
--- a/broadcastradio/2.0/Android.bp
+++ b/broadcastradio/2.0/Android.bp
@@ -21,9 +21,11 @@
"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 1aefc4e..ede8350 100644
--- a/broadcastradio/2.0/ITunerCallback.hal
+++ b/broadcastradio/2.0/ITunerCallback.hal
@@ -39,6 +39,21 @@
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.
*
* For a new tuner session, client must assume the antenna is connected.
diff --git a/broadcastradio/2.0/ITunerSession.hal b/broadcastradio/2.0/ITunerSession.hal
index 8a21768..a3f93fd 100644
--- a/broadcastradio/2.0/ITunerSession.hal
+++ b/broadcastradio/2.0/ITunerSession.hal
@@ -77,6 +77,32 @@
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.
*
* The success/failure result must be consistent with setConfigFlag.
diff --git a/broadcastradio/2.0/default/TunerSession.cpp b/broadcastradio/2.0/default/TunerSession.cpp
index 54af3389..36a22c5 100644
--- a/broadcastradio/2.0/default/TunerSession.cpp
+++ b/broadcastradio/2.0/default/TunerSession.cpp
@@ -45,6 +45,7 @@
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 {};
}
+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());
diff --git a/broadcastradio/2.0/default/TunerSession.h b/broadcastradio/2.0/default/TunerSession.h
index 9a72182..a58aa19 100644
--- a/broadcastradio/2.0/default/TunerSession.h
+++ b/broadcastradio/2.0/default/TunerSession.h
@@ -38,6 +38,8 @@
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,
diff --git a/broadcastradio/2.0/types.hal b/broadcastradio/2.0/types.hal
index fc5809f..d50d485 100644
--- a/broadcastradio/2.0/types.hal
+++ b/broadcastradio/2.0/types.hal
@@ -25,6 +25,8 @@
* 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 @@
/** 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 @@
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;
+};
diff --git a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
index d0e4144..46b3f19 100644
--- a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
+++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
@@ -38,6 +38,7 @@
using namespace std::chrono_literals;
+using std::unordered_set;
using std::vector;
using testing::_;
using testing::AnyNumber;
@@ -54,6 +55,7 @@
namespace timeout {
static constexpr auto tune = 30s;
+static constexpr auto programListScan = 5min;
} // namespace timeout
@@ -63,16 +65,20 @@
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<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 {
@@ -88,6 +94,25 @@
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";
@@ -463,6 +488,32 @@
}
}
+/**
+ * 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