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