Implement out-of-band metadata images.

This saves a lot of HIDL bandwidth, by not including raw image data in
metadata vector.

Bug: b/63702941
Test: VTS
Change-Id: I73d5218095e4af34c58da8dcfc520abd4cb46c26
diff --git a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
index 9e7c00b..d20452b 100644
--- a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
+++ b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
@@ -54,6 +54,8 @@
 using V1_0::BandConfig;
 using V1_0::Class;
 using V1_0::MetaData;
+using V1_0::MetadataKey;
+using V1_0::MetadataType;
 
 static constexpr auto kConfigTimeout = 10s;
 static constexpr auto kConnectModuleTimeout = 1s;
@@ -93,6 +95,7 @@
     // any stations on the list, so don't pick AM blindly).
     bool openTuner(unsigned band);
     const BandConfig& getBand(unsigned idx);
+    bool getProgramList(std::function<void(const hidl_vec<ProgramInfo>& list)> cb);
 
     Class radioClass;
     bool skipped = false;
@@ -207,6 +210,47 @@
     return band;
 }
 
+bool BroadcastRadioHalTest::getProgramList(
+    std::function<void(const hidl_vec<ProgramInfo>& list)> cb) {
+    ProgramListResult getListResult = ProgramListResult::NOT_INITIALIZED;
+    bool isListEmpty = true;
+    auto getListCb = [&](ProgramListResult result, const hidl_vec<ProgramInfo>& list) {
+        ALOGD("getListCb(%s, ProgramInfo[%zu])", toString(result).c_str(), list.size());
+        getListResult = result;
+        if (result != ProgramListResult::OK) return;
+        isListEmpty = (list.size() == 0);
+        if (!isListEmpty) cb(list);
+    };
+
+    // first try...
+    EXPECT_TIMEOUT_CALL(*mCallback, backgroundScanComplete, ProgramListResult::OK)
+        .Times(AnyNumber());
+    auto hidlResult = mTuner->getProgramList("", getListCb);
+    EXPECT_TRUE(hidlResult.isOk());
+    if (!hidlResult.isOk()) return false;
+
+    if (getListResult == ProgramListResult::NOT_STARTED) {
+        auto result = mTuner->startBackgroundScan();
+        EXPECT_EQ(ProgramListResult::OK, result);
+        getListResult = ProgramListResult::NOT_READY;  // continue as in NOT_READY case
+    }
+    if (getListResult == ProgramListResult::NOT_READY) {
+        EXPECT_TIMEOUT_CALL_WAIT(*mCallback, backgroundScanComplete, kFullScanTimeout);
+
+        // second (last) try...
+        hidlResult = mTuner->getProgramList("", getListCb);
+        EXPECT_TRUE(hidlResult.isOk());
+        if (!hidlResult.isOk()) return false;
+        EXPECT_EQ(ProgramListResult::OK, getListResult);
+    }
+
+    if (isListEmpty) {
+        printSkipped("Program list is empty.");
+        return false;
+    }
+    return true;
+}
+
 /**
  * Test IBroadcastRadio::openTuner() method called twice.
  *
@@ -242,41 +286,11 @@
     ASSERT_TRUE(openTuner(0));
 
     ProgramInfo firstProgram;
-    bool isListEmpty;
-    ProgramListResult getListResult = ProgramListResult::NOT_INITIALIZED;
-    auto getListCb = [&](ProgramListResult result, const hidl_vec<ProgramInfo>& list) {
-        ALOGD("getListCb(%s, ProgramInfo[%zu])", toString(result).c_str(), list.size());
-        getListResult = result;
-        if (result != ProgramListResult::OK) return;
-        isListEmpty = (list.size() == 0);
+    auto getCb = [&](const hidl_vec<ProgramInfo>& list) {
         // don't copy the whole list out, it might be heavy
-        if (!isListEmpty) firstProgram = list[0];
+        firstProgram = list[0];
     };
-
-    // first try...
-    EXPECT_TIMEOUT_CALL(*mCallback, backgroundScanComplete, ProgramListResult::OK)
-        .Times(AnyNumber());
-    auto hidlResult = mTuner->getProgramList("", getListCb);
-    ASSERT_TRUE(hidlResult.isOk());
-
-    if (getListResult == ProgramListResult::NOT_STARTED) {
-        auto result = mTuner->startBackgroundScan();
-        ASSERT_EQ(ProgramListResult::OK, result);
-        getListResult = ProgramListResult::NOT_READY;  // continue as in NOT_READY case
-    }
-    if (getListResult == ProgramListResult::NOT_READY) {
-        EXPECT_TIMEOUT_CALL_WAIT(*mCallback, backgroundScanComplete, kFullScanTimeout);
-
-        // second (last) try...
-        hidlResult = mTuner->getProgramList("", getListCb);
-        ASSERT_TRUE(hidlResult.isOk());
-        ASSERT_EQ(ProgramListResult::OK, getListResult);
-    }
-
-    if (isListEmpty) {
-        printSkipped("Program list is empty.");
-        return;
-    }
+    if (!getProgramList(getCb)) return;
 
     ProgramSelector selCb;
     EXPECT_CALL(*mCallback, tuneComplete(_, _));
@@ -296,6 +310,67 @@
     EXPECT_EQ(Result::OK, hidlResult);
 }
 
+/**
+ * Test getImage call with invalid image ID.
+ *
+ * Verifies that:
+ * - getImage call handles argument 0 gracefully
+ */
+TEST_P(BroadcastRadioHalTest, GetNoImage) {
+    if (skipped) return;
+
+    size_t len = 0;
+    auto hidlResult =
+        mRadioModule->getImage(0, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); });
+
+    ASSERT_TRUE(hidlResult.isOk());
+    ASSERT_EQ(0u, len);
+}
+
+/**
+ * Test proper image format in metadata.
+ *
+ * Verifies that:
+ * - all images in metadata are provided out-of-band (by id, not as a binary blob)
+ * - images are available for getImage call
+ */
+TEST_P(BroadcastRadioHalTest, OobImagesOnly) {
+    if (skipped) return;
+    ASSERT_TRUE(openTuner(0));
+
+    std::vector<int> imageIds;
+
+    ProgramInfo firstProgram;
+    auto getCb = [&](const hidl_vec<ProgramInfo>& list) {
+        for (auto&& program : list) {
+            for (auto&& entry : program.base.metadata) {
+                EXPECT_NE(MetadataType::RAW, entry.type);
+                if (entry.key != MetadataKey::ICON && entry.key != MetadataKey::ART) continue;
+                EXPECT_NE(0, entry.intValue);
+                EXPECT_EQ(0u, entry.rawValue.size());
+                if (entry.intValue != 0) imageIds.push_back(entry.intValue);
+            }
+        }
+    };
+    if (!getProgramList(getCb)) return;
+
+    if (imageIds.size() == 0) {
+        printSkipped("No images found");
+        return;
+    }
+
+    for (auto id : imageIds) {
+        ALOGD("Checking image %d", id);
+
+        size_t len = 0;
+        auto hidlResult =
+            mRadioModule->getImage(id, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); });
+
+        ASSERT_TRUE(hidlResult.isOk());
+        ASSERT_GT(len, 0u);
+    }
+}
+
 INSTANTIATE_TEST_CASE_P(BroadcastRadioHalTestCases, BroadcastRadioHalTest,
                         ::testing::Values(Class::AM_FM, Class::SAT, Class::DT));