Implement broadcast radio HAL 2.0 VTS tests.

Test: VTS
Bug: 69958777
Change-Id: I671c033519a6a41421b9ad73b0b897f832a1c3c0
diff --git a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
index 9efb942..823d14c 100644
--- a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
+++ b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
@@ -26,6 +26,7 @@
 #include <broadcastradio-utils-1x/Utils.h>
 #include <broadcastradio-vts-utils/call-barrier.h>
 #include <broadcastradio-vts-utils/mock-timeout.h>
+#include <broadcastradio-vts-utils/pointer-utils.h>
 #include <cutils/native_handle.h>
 #include <cutils/properties.h>
 #include <gmock/gmock.h>
@@ -56,8 +57,7 @@
 using V1_0::MetadataKey;
 using V1_0::MetadataType;
 
-using std::chrono::steady_clock;
-using std::this_thread::sleep_for;
+using broadcastradio::vts::clearAndWait;
 
 static constexpr auto kConfigTimeout = 10s;
 static constexpr auto kConnectModuleTimeout = 1s;
@@ -115,27 +115,6 @@
     hidl_vec<BandConfig> mBands;
 };
 
-/**
- * Clears strong pointer and waits until the object gets destroyed.
- *
- * @param ptr The pointer to get cleared.
- * @param timeout Time to wait for other references.
- */
-template <typename T>
-static void clearAndWait(sp<T>& ptr, std::chrono::milliseconds timeout) {
-    wp<T> wptr = ptr;
-    ptr.clear();
-    auto limit = steady_clock::now() + timeout;
-    while (wptr.promote() != nullptr) {
-        constexpr auto step = 10ms;
-        if (steady_clock::now() + step > limit) {
-            FAIL() << "Pointer was not released within timeout";
-            break;
-        }
-        sleep_for(step);
-    }
-}
-
 void BroadcastRadioHalTest::SetUp() {
     radioClass = GetParam();
 
diff --git a/broadcastradio/1.2/vts/functional/VtsHalBroadcastradioV1_2TargetTest.cpp b/broadcastradio/1.2/vts/functional/VtsHalBroadcastradioV1_2TargetTest.cpp
index 0204191..085206b 100644
--- a/broadcastradio/1.2/vts/functional/VtsHalBroadcastradioV1_2TargetTest.cpp
+++ b/broadcastradio/1.2/vts/functional/VtsHalBroadcastradioV1_2TargetTest.cpp
@@ -17,14 +17,15 @@
 #define LOG_TAG "broadcastradio.vts"
 
 #include <VtsHalHidlTargetTestBase.h>
+#include <android-base/logging.h>
 #include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
 #include <android/hardware/broadcastradio/1.2/IBroadcastRadioFactory.h>
 #include <android/hardware/broadcastradio/1.2/ITuner.h>
 #include <android/hardware/broadcastradio/1.2/ITunerCallback.h>
 #include <android/hardware/broadcastradio/1.2/types.h>
-#include <android-base/logging.h>
 #include <broadcastradio-vts-utils/call-barrier.h>
 #include <broadcastradio-vts-utils/mock-timeout.h>
+#include <broadcastradio-vts-utils/pointer-utils.h>
 #include <cutils/native_handle.h>
 #include <cutils/properties.h>
 #include <gmock/gmock.h>
@@ -60,8 +61,7 @@
 using V1_1::ProgramSelector;
 using V1_1::Properties;
 
-using std::chrono::steady_clock;
-using std::this_thread::sleep_for;
+using broadcastradio::vts::clearAndWait;
 
 static constexpr auto kConfigTimeout = 10s;
 static constexpr auto kConnectModuleTimeout = 1s;
@@ -110,27 +110,6 @@
     hidl_vec<BandConfig> mBands;
 };
 
-/**
- * Clears strong pointer and waits until the object gets destroyed.
- *
- * @param ptr The pointer to get cleared.
- * @param timeout Time to wait for other references.
- */
-template <typename T>
-static void clearAndWait(sp<T>& ptr, std::chrono::milliseconds timeout) {
-    wp<T> wptr = ptr;
-    ptr.clear();
-    auto limit = steady_clock::now() + timeout;
-    while (wptr.promote() != nullptr) {
-        constexpr auto step = 10ms;
-        if (steady_clock::now() + step > limit) {
-            FAIL() << "Pointer was not released within timeout";
-            break;
-        }
-        sleep_for(step);
-    }
-}
-
 void BroadcastRadioHalTest::SetUp() {
     radioClass = GetParam();
 
diff --git a/broadcastradio/2.0/vts/OWNERS b/broadcastradio/2.0/vts/OWNERS
new file mode 100644
index 0000000..12adf57
--- /dev/null
+++ b/broadcastradio/2.0/vts/OWNERS
@@ -0,0 +1,7 @@
+# Automotive team
+egranata@google.com
+twasilczyk@google.com
+
+# VTS team
+yuexima@google.com
+yim@google.com
diff --git a/broadcastradio/2.0/vts/functional/Android.bp b/broadcastradio/2.0/vts/functional/Android.bp
new file mode 100644
index 0000000..6017b15
--- /dev/null
+++ b/broadcastradio/2.0/vts/functional/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsHalBroadcastradioV2_0TargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: ["VtsHalBroadcastradioV2_0TargetTest.cpp"],
+    static_libs: [
+        "android.hardware.broadcastradio@2.0",
+        "android.hardware.broadcastradio@common-utils-2x-lib",
+        "android.hardware.broadcastradio@vts-utils-lib",
+        "android.hardware.broadcastradio@vts-utils-lib",
+        "libgmock",
+    ],
+}
diff --git a/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
new file mode 100644
index 0000000..a12afd6
--- /dev/null
+++ b/broadcastradio/2.0/vts/functional/VtsHalBroadcastradioV2_0TargetTest.cpp
@@ -0,0 +1,392 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BcRadio.vts"
+
+#include <VtsHalHidlTargetTestBase.h>
+#include <android-base/logging.h>
+#include <android/hardware/broadcastradio/2.0/IBroadcastRadio.h>
+#include <android/hardware/broadcastradio/2.0/ITunerCallback.h>
+#include <android/hardware/broadcastradio/2.0/ITunerSession.h>
+#include <android/hardware/broadcastradio/2.0/types.h>
+#include <broadcastradio-utils-2x/Utils.h>
+#include <broadcastradio-vts-utils/call-barrier.h>
+#include <broadcastradio-vts-utils/mock-timeout.h>
+#include <broadcastradio-vts-utils/pointer-utils.h>
+#include <gmock/gmock.h>
+
+#include <chrono>
+
+namespace android {
+namespace hardware {
+namespace broadcastradio {
+namespace V2_0 {
+namespace vts {
+
+using namespace std::chrono_literals;
+
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::ByMove;
+using testing::DoAll;
+using testing::Invoke;
+using testing::SaveArg;
+
+using broadcastradio::vts::CallBarrier;
+using broadcastradio::vts::clearAndWait;
+using utils::make_identifier;
+using utils::make_selector_amfm;
+
+namespace timeout {
+
+static constexpr auto tune = 30s;
+
+}  // namespace timeout
+
+struct TunerCallbackMock : public ITunerCallback {
+    TunerCallbackMock() {
+        // we expect the antenna is connected through the whole test
+        EXPECT_CALL(*this, onAntennaStateChange(false)).Times(0);
+    }
+
+    MOCK_METHOD2(onTuneFailed, Return<void>(Result, const ProgramSelector&));
+    MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChanged, Return<void>(const ProgramInfo&));
+    MOCK_METHOD1(onAntennaStateChange, Return<void>(bool connected));
+    MOCK_METHOD1(onParametersUpdated, Return<void>(const hidl_vec<VendorKeyValue>& parameters));
+};
+
+class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase {
+   protected:
+    virtual void SetUp() override;
+    virtual void TearDown() override;
+
+    bool openSession();
+
+    sp<IBroadcastRadio> mModule;
+    Properties mProperties;
+    sp<ITunerSession> mSession;
+    sp<TunerCallbackMock> mCallback = new TunerCallbackMock();
+};
+
+void BroadcastRadioHalTest::SetUp() {
+    EXPECT_EQ(nullptr, mModule.get()) << "Module is already open";
+
+    // lookup HIDL service (radio module)
+    mModule = getService<IBroadcastRadio>();
+    ASSERT_NE(nullptr, mModule.get()) << "Couldn't find broadcast radio HAL implementation";
+
+    // get module properties
+    auto propResult = mModule->getProperties([&](const Properties& p) { mProperties = p; });
+    ASSERT_TRUE(propResult.isOk());
+
+    EXPECT_FALSE(mProperties.maker.empty());
+    EXPECT_FALSE(mProperties.product.empty());
+    EXPECT_GT(mProperties.supportedIdentifierTypes.size(), 0u);
+}
+
+void BroadcastRadioHalTest::TearDown() {
+    mSession.clear();
+    mModule.clear();
+    clearAndWait(mCallback, 1s);
+}
+
+bool BroadcastRadioHalTest::openSession() {
+    EXPECT_EQ(nullptr, mSession.get()) << "Session is already open";
+
+    Result halResult = Result::UNKNOWN_ERROR;
+    auto openCb = [&](Result result, const sp<ITunerSession>& session) {
+        halResult = result;
+        if (result != Result::OK) return;
+        mSession = session;
+    };
+    auto hidlResult = mModule->openSession(mCallback, openCb);
+
+    EXPECT_TRUE(hidlResult.isOk());
+    EXPECT_EQ(Result::OK, halResult);
+    EXPECT_NE(nullptr, mSession.get());
+
+    return nullptr != mSession.get();
+}
+
+/**
+ * Test session opening.
+ *
+ * Verifies that:
+ *  - the method succeeds on a first and subsequent calls;
+ *  - the method succeeds when called for the second time without
+ *    closing previous session.
+ */
+TEST_F(BroadcastRadioHalTest, OpenSession) {
+    // simply open session for the first time
+    ASSERT_TRUE(openSession());
+
+    // drop (without explicit close) and re-open the session
+    mSession.clear();
+    ASSERT_TRUE(openSession());
+
+    // open the second session (the first one should be forcibly closed)
+    auto secondSession = mSession;
+    mSession.clear();
+    ASSERT_TRUE(openSession());
+}
+
+/**
+ * Test tuning with FM selector.
+ *
+ * Verifies that:
+ *  - if AM/FM selector is not supported, the method returns NOT_SUPPORTED;
+ *  - if it is supported, the method succeeds;
+ *  - after a successful tune call, onCurrentProgramInfoChanged callback is
+ *    invoked carrying a proper selector;
+ *  - program changes exactly to what was requested.
+ */
+TEST_F(BroadcastRadioHalTest, FmTune) {
+    ASSERT_TRUE(openSession());
+
+    uint64_t freq = 100100;  // 100.1 FM
+    auto sel = make_selector_amfm(freq);
+
+    // try tuning
+    ProgramInfo infoCb = {};
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged, _)
+        .Times(AnyNumber())
+        .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(Void()))));
+    auto result = mSession->tune(sel);
+
+    // expect a failure if it's not supported
+    if (!utils::isSupported(mProperties, sel)) {
+        EXPECT_EQ(Result::NOT_SUPPORTED, result);
+        return;
+    }
+
+    // expect a callback if it succeeds
+    EXPECT_EQ(Result::OK, result);
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged, timeout::tune);
+
+    // it should tune exactly to what was requested
+    auto freqs = utils::getAllIds(infoCb.selector, IdentifierType::AMFM_FREQUENCY);
+    EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq));
+}
+
+/**
+ * Test tuning with invalid selectors.
+ *
+ * Verifies that:
+ *  - if the selector is not supported, it's ignored;
+ *  - if it is supported, an invalid value results with INVALID_ARGUMENTS;
+ */
+TEST_F(BroadcastRadioHalTest, TuneFailsWithInvalid) {
+    ASSERT_TRUE(openSession());
+
+    vector<ProgramIdentifier> invalid = {
+        make_identifier(IdentifierType::AMFM_FREQUENCY, 0),
+        make_identifier(IdentifierType::RDS_PI, 0x10000),
+        make_identifier(IdentifierType::HD_STATION_ID_EXT, 0x100000000),
+        make_identifier(IdentifierType::DAB_SID_EXT, 0),
+        make_identifier(IdentifierType::DRMO_SERVICE_ID, 0x100000000),
+        make_identifier(IdentifierType::SXM_SERVICE_ID, 0x100000000),
+    };
+
+    for (auto&& id : invalid) {
+        ProgramSelector sel{id, {}};
+
+        auto result = mSession->tune(sel);
+
+        if (utils::isSupported(mProperties, sel)) {
+            EXPECT_EQ(Result::INVALID_ARGUMENTS, result);
+        } else {
+            EXPECT_EQ(Result::NOT_SUPPORTED, result);
+        }
+    }
+}
+
+/**
+ * Test tuning with empty program selector.
+ *
+ * Verifies that:
+ *  - tune fails with NOT_SUPPORTED when program selector is not initialized.
+ */
+TEST_F(BroadcastRadioHalTest, TuneFailsWithEmpty) {
+    ASSERT_TRUE(openSession());
+
+    // Program type is 1-based, so 0 will always be invalid.
+    ProgramSelector sel = {};
+    auto result = mSession->tune(sel);
+    ASSERT_EQ(Result::NOT_SUPPORTED, result);
+}
+
+/**
+ * Test scanning to next/prev station.
+ *
+ * Verifies that:
+ *  - the method succeeds;
+ *  - the program info is changed within timeout::tune;
+ *  - works both directions and with or without skipping sub-channel.
+ */
+TEST_F(BroadcastRadioHalTest, Scan) {
+    ASSERT_TRUE(openSession());
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged, _);
+    auto result = mSession->scan(true /* up */, true /* skip subchannel */);
+    EXPECT_EQ(Result::OK, result);
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged, timeout::tune);
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged, _);
+    result = mSession->scan(false /* down */, false /* don't skip subchannel */);
+    EXPECT_EQ(Result::OK, result);
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged, timeout::tune);
+}
+
+/**
+ * Test step operation.
+ *
+ * Verifies that:
+ *  - the method succeeds or returns NOT_SUPPORTED;
+ *  - the program info is changed within timeout::tune if the method succeeded;
+ *  - works both directions.
+ */
+TEST_F(BroadcastRadioHalTest, Step) {
+    ASSERT_TRUE(openSession());
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged, _).Times(AnyNumber());
+    auto result = mSession->step(true /* up */);
+    if (result == Result::NOT_SUPPORTED) return;
+    EXPECT_EQ(Result::OK, result);
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged, timeout::tune);
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChanged, _);
+    result = mSession->step(false /* down */);
+    EXPECT_EQ(Result::OK, result);
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChanged, timeout::tune);
+}
+
+/**
+ * Test tune cancellation.
+ *
+ * Verifies that:
+ *  - the method does not crash after being invoked multiple times.
+ */
+TEST_F(BroadcastRadioHalTest, Cancel) {
+    ASSERT_TRUE(openSession());
+
+    for (int i = 0; i < 10; i++) {
+        auto scanResult = mSession->scan(true /* up */, true /* skip subchannel */);
+        ASSERT_EQ(Result::OK, scanResult);
+
+        auto cancelResult = mSession->cancel();
+        ASSERT_TRUE(cancelResult.isOk());
+    }
+}
+
+/**
+ * Test IBroadcastRadio::get|setParameters() methods called with no parameters.
+ *
+ * Verifies that:
+ *  - callback is called for empty parameters set.
+ */
+TEST_F(BroadcastRadioHalTest, NoParameters) {
+    ASSERT_TRUE(openSession());
+
+    hidl_vec<VendorKeyValue> halResults = {};
+    bool wasCalled = false;
+    auto cb = [&](hidl_vec<VendorKeyValue> results) {
+        wasCalled = true;
+        halResults = results;
+    };
+
+    auto hidlResult = mSession->setParameters({}, cb);
+    ASSERT_TRUE(hidlResult.isOk());
+    ASSERT_TRUE(wasCalled);
+    ASSERT_EQ(0u, halResults.size());
+
+    wasCalled = false;
+    hidlResult = mSession->getParameters({}, cb);
+    ASSERT_TRUE(hidlResult.isOk());
+    ASSERT_TRUE(wasCalled);
+    ASSERT_EQ(0u, halResults.size());
+}
+
+/**
+ * Test IBroadcastRadio::get|setParameters() methods called with unknown parameters.
+ *
+ * Verifies that:
+ *  - unknown parameters are ignored;
+ *  - callback is called also for empty results set.
+ */
+TEST_F(BroadcastRadioHalTest, UnknownParameters) {
+    ASSERT_TRUE(openSession());
+
+    hidl_vec<VendorKeyValue> halResults = {};
+    bool wasCalled = false;
+    auto cb = [&](hidl_vec<VendorKeyValue> results) {
+        wasCalled = true;
+        halResults = results;
+    };
+
+    auto hidlResult = mSession->setParameters({{"com.google.unknown", "dummy"}}, cb);
+    ASSERT_TRUE(hidlResult.isOk());
+    ASSERT_TRUE(wasCalled);
+    ASSERT_EQ(0u, halResults.size());
+
+    wasCalled = false;
+    hidlResult = mSession->getParameters({{"com.google.unknown*", "dummy"}}, cb);
+    ASSERT_TRUE(hidlResult.isOk());
+    ASSERT_TRUE(wasCalled);
+    ASSERT_EQ(0u, halResults.size());
+}
+
+/**
+ * Test session closing.
+ *
+ * Verifies that:
+ *  - the method does not crash after being invoked multiple times.
+ */
+TEST_F(BroadcastRadioHalTest, Close) {
+    ASSERT_TRUE(openSession());
+
+    for (int i = 0; i < 10; i++) {
+        auto cancelResult = mSession->close();
+        ASSERT_TRUE(cancelResult.isOk());
+    }
+}
+
+/**
+ * Test geting image of invalid ID.
+ *
+ * Verifies that:
+ * - getImage call handles argument 0 gracefully.
+ */
+TEST_F(BroadcastRadioHalTest, GetNoImage) {
+    size_t len = 0;
+    auto result = mModule->getImage(0, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); });
+
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(0u, len);
+}
+
+}  // namespace vts
+}  // namespace V2_0
+}  // namespace broadcastradio
+}  // namespace hardware
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    int status = RUN_ALL_TESTS();
+    ALOGI("Test result = %d", status);
+    return status;
+}
diff --git a/broadcastradio/common/utils2x/Utils.cpp b/broadcastradio/common/utils2x/Utils.cpp
index 3c9fba7..d157108 100644
--- a/broadcastradio/common/utils2x/Utils.cpp
+++ b/broadcastradio/common/utils2x/Utils.cpp
@@ -32,6 +32,7 @@
 using V2_0::ProgramSelector;
 
 using std::string;
+using std::vector;
 
 IdentifierType getType(const ProgramIdentifier& id) {
     return static_cast<IdentifierType>(id.type);
@@ -118,6 +119,19 @@
     return getId(sel, type);
 }
 
+vector<uint64_t> getAllIds(const ProgramSelector& sel, const IdentifierType type) {
+    vector<uint64_t> ret;
+    auto itype = static_cast<uint32_t>(type);
+
+    if (sel.primaryId.type == itype) ret.push_back(sel.primaryId.value);
+
+    for (auto&& id : sel.secondaryIds) {
+        if (id.type == itype) ret.push_back(id.value);
+    }
+
+    return ret;
+}
+
 bool isSupported(const V2_0::Properties& prop, const V2_0::ProgramSelector& sel) {
     // Not optimal, but it doesn't matter for default impl nor VTS tests.
     for (auto&& idTypeI : prop.supportedIdentifierTypes) {
diff --git a/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h b/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h
index e9ac864..dd01852 100644
--- a/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h
+++ b/broadcastradio/common/utils2x/include/broadcastradio-utils-2x/Utils.h
@@ -57,6 +57,11 @@
 uint64_t getId(const V2_0::ProgramSelector& sel, const V2_0::IdentifierType type, uint64_t defval);
 
 /**
+ * Returns all IDs of a given type.
+ */
+std::vector<uint64_t> getAllIds(const V2_0::ProgramSelector& sel, const V2_0::IdentifierType type);
+
+/**
  * Checks, if a given selector is supported by the radio module.
  *
  * @param prop Module description.
diff --git a/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/pointer-utils.h b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/pointer-utils.h
new file mode 100644
index 0000000..0b6f5eb
--- /dev/null
+++ b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/pointer-utils.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_BROADCASTRADIO_VTS_POINTER_UTILS
+#define ANDROID_HARDWARE_BROADCASTRADIO_VTS_POINTER_UTILS
+
+#include <chrono>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace broadcastradio {
+namespace vts {
+
+/**
+ * Clears strong pointer and waits until the object gets destroyed.
+ *
+ * @param ptr The pointer to get cleared.
+ * @param timeout Time to wait for other references.
+ */
+template <typename T>
+static void clearAndWait(sp<T>& ptr, std::chrono::milliseconds timeout) {
+    using std::chrono::steady_clock;
+
+    constexpr auto step = 10ms;
+
+    wp<T> wptr = ptr;
+    ptr.clear();
+
+    auto limit = steady_clock::now() + timeout;
+    while (wptr.promote() != nullptr) {
+        if (steady_clock::now() + step > limit) {
+            FAIL() << "Pointer was not released within timeout";
+            break;
+        }
+        std::this_thread::sleep_for(step);
+    }
+}
+
+}  // namespace vts
+}  // namespace broadcastradio
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_BROADCASTRADIO_VTS_POINTER_UTILS