psh_utils: Update ServiceSingleton code
The new code handles service death.
Flag: EXEMPT Bugfix
Test: atest service_singleton_tests
Test: power stats show when enabled, after stats service killed
Test: atest powerstats_collector_tests
Test: atest audio_powerstats_benchmark
Test: atest audio_token_benchmark
Test: atest audio_powerstatscollector_benchmark
Bug: 350114693
Change-Id: I0783b3c58ed8412b363b613127873f01ef8d6737
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index e340b40..762984e 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -53,6 +53,7 @@
"Process.cpp",
"ProcessInfo.cpp",
"SchedulingPolicyService.cpp",
+ "ServiceSingleton.cpp",
"ServiceUtilities.cpp",
"ThreadSnapshot.cpp",
"TimeCheck.cpp",
@@ -89,6 +90,7 @@
"libaudioutils", // for clock.h, Statistics.h
"libbase",
"libbinder",
+ "libbinder_ndk",
"libcutils",
"libhidlbase",
"liblog",
@@ -112,6 +114,8 @@
],
export_shared_lib_headers: [
+ "libaudioutils",
+ "libbinder_ndk",
"libpermission",
"packagemanager_aidl-cpp",
],
diff --git a/media/utils/ServiceSingleton.cpp b/media/utils/ServiceSingleton.cpp
new file mode 100644
index 0000000..ade7a3e
--- /dev/null
+++ b/media/utils/ServiceSingleton.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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_NDEBUG 0
+#define LOG_TAG "ServiceSingleton"
+
+#include <mediautils/ServiceSingleton.h>
+
+namespace android::mediautils {
+
+namespace details {
+
+// To prevent multiple instances in different linkages,
+// we anchor the singleton in a .cpp instead of inlining in the header.
+
+template<typename T>
+requires (std::is_same_v<T, const char*> || std::is_same_v<T, String16>)
+std::shared_ptr<ServiceHandler> ServiceHandler::getInstance(const T& name) {
+ using Key = std::conditional_t<std::is_same_v<T, String16>, String16, std::string>;
+ [[clang::no_destroy]] static constinit std::mutex mutex;
+ [[clang::no_destroy]] static constinit std::shared_ptr<
+ std::map<Key, std::shared_ptr<ServiceHandler>>> map GUARDED_BY(mutex);
+ static constinit bool init GUARDED_BY(mutex) = false;
+
+ std::lock_guard l(mutex);
+ if (!init) {
+ map = std::make_shared<std::map<Key, std::shared_ptr<ServiceHandler>>>();
+ init = true;
+ }
+
+ auto& handler = (*map)[name];
+ if (!handler) {
+ handler = std::make_shared<ServiceHandler>();
+ if constexpr (std::is_same_v<T, String16>) {
+ handler->init_cpp();
+ } else /* constexpr */ {
+ handler->init_ndk();
+ }
+ }
+ return handler;
+}
+
+// Explicit template function instantiation.
+template
+std::shared_ptr<ServiceHandler> ServiceHandler::getInstance<const char*>(const char* const& name);
+
+template
+std::shared_ptr<ServiceHandler> ServiceHandler::getInstance<String16>(const String16& name);
+
+} // details
+
+} // namespace android::mediautils
+
diff --git a/media/utils/include/mediautils/BinderGenericUtils.h b/media/utils/include/mediautils/BinderGenericUtils.h
new file mode 100644
index 0000000..c2bbde1
--- /dev/null
+++ b/media/utils/include/mediautils/BinderGenericUtils.h
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#include <android/binder_auto_utils.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+
+namespace android::mediautils {
+// General Template Binder Utilities.
+//
+// In order to write generic Template methods, we need to have utility methods
+// that provide seamless template overload resolution between NDK and CPP variants.
+//
+
+// Returns true or false based on whether the Interface is a NDK Interface.
+template<typename Interface>
+inline constexpr bool is_ndk = std::derived_from<Interface, ::ndk::ICInterface>;
+
+// Returns the Interface ptr type (shared_ptr or sp) based on the Interface.
+template<typename Interface>
+using InterfaceType =
+ std::conditional_t <is_ndk<Interface>, std::shared_ptr<Interface>, sp<Interface>>;
+
+template<typename Interface>
+using BaseInterfaceType = std::conditional_t <is_ndk<Interface>,
+std::shared_ptr<::ndk::ICInterface>, sp<::android::IInterface>>;
+
+/**
+ * Returns either a sp<IBinder> or an SpAIBinder object
+ * for the AIDL interface given.
+ *
+ * A -cpp interface will return sp<IBinder>.
+ * A -ndk interface will return SpAIBinder
+ */
+template<typename Interface>
+sp<IBinder> binderFromInterface(const sp<Interface> &interface) {
+ return IInterface::asBinder(interface);
+}
+
+template<typename Interface>
+::ndk::SpAIBinder binderFromInterface(const std::shared_ptr<Interface> &interface) {
+ return interface->asBinder();
+}
+
+/**
+ * Returns either a sp<Interface> or a std::shared_ptr<Interface> from a Binder object.
+ *
+ * A -cpp interface will return sp<Interface>.
+ * A -ndk interface will return std::shared_ptr<Interface>
+ */
+template<typename Interface>
+sp<Interface> interfaceFromBinder(const sp<IBinder> &binder) {
+ return interface_cast<Interface>(binder);
+}
+
+template<typename Interface>
+std::shared_ptr<Interface> interfaceFromBinder(const ::ndk::SpAIBinder &binder) {
+ return Interface::fromBinder(binder);
+}
+
+/**
+ * Returns either a sp<Interface> or a std::shared_ptr<Interface> from
+ * the NDK/CPP base interface class.
+ */
+template<typename Interface>
+sp<Interface> interfaceFromBase(const sp<::android::IInterface> &interface) {
+ // this is unvalidated, though could verify getInterfaceDescriptor() == Interface::descriptor
+ return sp<Interface>::cast(interface);
+}
+
+template<typename Interface>
+std::shared_ptr<Interface> interfaceFromBase(
+ const std::shared_ptr<::ndk::ICInterface> &interface) {
+ // this is unvalidated, though could verify
+ // !strcmp(AIBinder_Class_getDescriptor(AIBinder_getClass(...), Interface::descriptor)
+ return std::static_pointer_cast<Interface>(interface);
+}
+
+/**
+ * Returns a fully qualified service name.
+ *
+ * @param name
+ * If name is empty, it returns the name from the Service descriptor.
+ * If name starts with '/', it appends the name as a version to the Service descriptor,
+ * e.g. "/default".
+ * Otherwise the name is assumed to be the full Service name, overriding the
+ * Service descriptor.
+ */
+template<typename Service>
+auto fullyQualifiedServiceName(const char* const name) {
+ using StringType = std::conditional_t<is_ndk<Service>, std::string, String16>;
+ return name == nullptr ? StringType(Service::descriptor)
+ : name[0] != 0 && name[0] != '/' ? StringType(name)
+ : StringType(Service::descriptor) + StringType(name);
+}
+
+/**
+ * Returns either a std::shared_ptr<Interface> or sp<Interface>
+ * for the AIDL interface given.
+ *
+ * A -cpp interface will return sp<Service>.
+ * A -ndk interface will return std::shared_ptr<Service>
+ *
+ * @param name if non-empty should contain either a suffix if it starts
+ * with a '/' such as "/default", or the full service name.
+ */
+template<typename Service>
+auto checkServicePassThrough(const char *const name = "") {
+ if constexpr(is_ndk<Service>)
+ {
+ const auto serviceName = fullyQualifiedServiceName<Service>(name);
+ return Service::fromBinder(
+ ::ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+ } else /* constexpr */ {
+ const auto serviceName = fullyQualifiedServiceName<Service>(name);
+ auto binder = defaultServiceManager()->checkService(serviceName);
+ return interface_cast<Service>(binder);
+ }
+}
+
+template<typename Service>
+void addService(const std::shared_ptr<Service> &service) {
+ AServiceManager_addService(binderFromInterface(service), Service::descriptor);
+}
+
+template<typename Service>
+void addService(const sp<Service> &service) {
+ defaultServiceManager()->addService(Service::descriptor, binderFromInterface(service));
+}
+
+namespace details {
+
+// Use the APIs below, not the details here.
+
+/**
+ * RequestServiceManagerCallback(Cpp|Ndk) is a RAII class that
+ * requests a ServiceManager callback.
+ *
+ * Note the ServiceManager is a single threaded "apartment" and only one
+ * transaction is active, hence:
+ *
+ * 1) After the RequestServiceManagerCallback object is destroyed no
+ * calls to the onBinder function is pending or will occur.
+ * 2) To prevent deadlock, do not construct or destroy the class with
+ * a lock held that the onService function also requires.
+ */
+template<typename Service>
+class RequestServiceManagerCallbackCpp {
+public:
+ explicit RequestServiceManagerCallbackCpp(
+ std::function<void(const sp<Service> &)> &&onService,
+ const char *const serviceName = ""
+ )
+ : mServiceName{fullyQualifiedServiceName<Service>(serviceName)},
+ mWaiter{sp<Waiter>::make(std::move(onService))},
+ mStatus{defaultServiceManager()->registerForNotifications(mServiceName,
+ mWaiter)} {
+ }
+
+ ~RequestServiceManagerCallbackCpp() {
+ if (mStatus == OK) {
+ defaultServiceManager()->unregisterForNotifications(mServiceName, mWaiter);
+ }
+ }
+
+ status_t getStatus() const {
+ return mStatus;
+ }
+
+private:
+ const String16 mServiceName;
+ const sp<IServiceManager::LocalRegistrationCallback> mWaiter;
+ const status_t mStatus;
+
+ // With some work here, we could make this a singleton to improve
+ // performance and reduce binder clutter.
+ class Waiter : public IServiceManager::LocalRegistrationCallback {
+ public:
+ explicit Waiter(std::function<void(const sp<Service> &)> &&onService)
+ : mOnService{std::move(onService)} {}
+
+ private:
+ void onServiceRegistration(
+ const String16 & /*name*/, const sp<IBinder> &binder) final {
+ mOnService(interface_cast<Service>(binder));
+ }
+
+ const std::function<void(const sp<Service> &)> mOnService;
+ };
+};
+
+template<typename Service>
+class RequestServiceManagerCallbackNdk {
+public:
+ explicit RequestServiceManagerCallbackNdk(
+ std::function<void(const std::shared_ptr<Service> &)> &&onService,
+ const char *const serviceName = ""
+ )
+ : mServiceName{fullyQualifiedServiceName<Service>(serviceName)},
+ mOnService{std::move(onService)},
+ mWaiter{AServiceManager_registerForServiceNotifications(
+ mServiceName.c_str(),
+ onRegister, this)} // must be registered after mOnService.
+ {}
+
+ ~RequestServiceManagerCallbackNdk() {
+ if (mWaiter) {
+ AServiceManager_NotificationRegistration_delete(mWaiter);
+ }
+ }
+
+ status_t getStatus() const {
+ return mWaiter != nullptr ? OK : INVALID_OPERATION;
+ }
+
+private:
+ const std::string mServiceName; // must keep a local copy.
+ const std::function<void(const std::shared_ptr<Service> &)> mOnService;
+ AServiceManager_NotificationRegistration *const mWaiter; // last.
+
+ static void onRegister(const char *instance, AIBinder *registered, void *cookie) {
+ (void) instance;
+ auto *callbackHandler = static_cast<RequestServiceManagerCallbackNdk<Service> *>(cookie);
+ callbackHandler->mOnService(Service::fromBinder(::ndk::SpAIBinder(registered)));
+ }
+};
+
+/**
+ * RequestDeathNotification(Cpp|Ndk) is a RAII class that
+ * requests a death notification.
+ *
+ * Note the ServiceManager is a single threaded "apartment" and only one
+ * transaction is active, hence:
+ *
+ * 1) After the RequestDeathNotification object is destroyed no
+ * calls to the onBinder function is pending or will occur.
+ * 2) To prevent deadlock, do not construct or destroy the class with
+ * a lock held that the onBinderDied function also requires.
+ */
+
+class RequestDeathNotificationCpp {
+ class DeathRecipientHelper : public IBinder::DeathRecipient {
+ public:
+ explicit DeathRecipientHelper(std::function<void()> &&onBinderDied)
+ : mOnBinderDied{std::move(onBinderDied)} {
+ }
+
+ void binderDied(const wp<IBinder> &weakBinder) final {
+ (void) weakBinder;
+ mOnBinderDied();
+ }
+
+ private:
+ const std::function<void()> mOnBinderDied;
+ };
+
+public:
+ RequestDeathNotificationCpp(const sp<IBinder> &binder,
+ std::function<void()> &&onBinderDied)
+ : mHelper{sp<DeathRecipientHelper>::make(std::move(onBinderDied))},
+ mWeakBinder{binder}, mStatus{binder->linkToDeath(mHelper)} {
+ ALOGW_IF(mStatus != OK, "%s: linkToDeath status:%d", __func__, mStatus);
+ }
+
+ ~RequestDeathNotificationCpp() {
+ if (mStatus == OK) {
+ const auto binder = mWeakBinder.promote();
+ if (binder) binder->unlinkToDeath(mHelper);
+ }
+ }
+
+ status_t getStatus() const {
+ return mStatus;
+ }
+
+private:
+ const sp<DeathRecipientHelper> mHelper;
+ const wp<IBinder> mWeakBinder;
+ const status_t mStatus;
+};
+
+class RequestDeathNotificationNdk {
+public:
+ RequestDeathNotificationNdk(
+ const ::ndk::SpAIBinder &binder, std::function<void()> &&onBinderDied)
+ : mOnBinderDied(std::move(onBinderDied)),
+ mRecipient(::AIBinder_DeathRecipient_new(OnBinderDiedStatic),
+ &AIBinder_DeathRecipient_delete), mStatus{AIBinder_linkToDeath(
+ binder.get(), mRecipient.get(), /* cookie */ this)} {
+ ALOGW_IF(mStatus != OK, "%s: AIBinder_linkToDeath status:%d", __func__, mStatus);
+ // We do not use AIBinder_DeathRecipient_setOnUnlinked() to do resource deallocation
+ // as the functor mOnBinderDied is kept alive by this class.
+ }
+
+ ~RequestDeathNotificationNdk() {
+ // The AIBinder_DeathRecipient dtor automatically unlinks all registered notifications,
+ // so AIBinder_unlinkToDeath() is not needed here (elsewise we need to maintain a
+ // AIBinder_Weak here).
+ }
+
+ status_t getStatus() const {
+ return mStatus;
+ }
+
+private:
+ void onBinderDied() {
+ mOnBinderDied();
+ }
+
+ static void OnBinderDiedStatic(void *cookie) {
+ reinterpret_cast<RequestDeathNotificationNdk *>(cookie)->onBinderDied();
+ }
+
+ const std::function<void()> mOnBinderDied;
+ const std::unique_ptr<AIBinder_DeathRecipient, decltype(
+ &AIBinder_DeathRecipient_delete)>
+ mRecipient;
+ const status_t mStatus; // binder_status_t is a limited subset of status_t
+};
+
+} // details
+
+/**
+ * Requests a notification that service is available.
+ *
+ * An opaque handle is returned - after clearing it is guaranteed that
+ * no callback will occur.
+ *
+ * The callback will be of form:
+ * onService(const sp<Service>& service);
+ * onService(const std::shared_ptr<Service>& service);
+ */
+template<typename Service, typename F>
+std::shared_ptr<void> requestServiceNotification(
+ F onService, const char *const serviceName = "") {
+ // the following are used for callbacks but placed here for invalidate.
+ using RequestServiceManagerCallback = std::conditional_t<is_ndk<Service>,
+ details::RequestServiceManagerCallbackNdk<Service>,
+ details::RequestServiceManagerCallbackCpp<Service>>;
+ const auto ptr = std::make_shared<RequestServiceManagerCallback>(
+ onService, serviceName);
+ const auto status = ptr->getStatus();
+ return status == OK ? ptr : nullptr;
+}
+
+/**
+ * Requests a death notification.
+ *
+ * An opaque handle is returned - after clearing it is guaranteed that
+ * no notification will occur.
+ *
+ * The callback will be of form void onBinderDied();
+ */
+template<typename Service>
+std::shared_ptr<void> requestDeathNotification(
+ const sp<Service> &service, std::function<void()> &&onBinderDied) {
+ const auto ptr = std::make_shared<details::RequestDeathNotificationCpp>(
+ binderFromInterface(service), std::move(onBinderDied));
+ const auto status = ptr->getStatus();
+ return status == OK ? ptr : nullptr;
+}
+
+template<typename Service>
+std::shared_ptr<void> requestDeathNotification(
+ const std::shared_ptr<Service> &service, std::function<void()> &&onBinderDied) {
+ const auto ptr = std::make_shared<details::RequestDeathNotificationNdk>(
+ binderFromInterface(service), std::move(onBinderDied));
+ const auto status = ptr->getStatus();
+ return status == OK ? ptr : nullptr;
+}
+
+} // namespace android::mediautils
diff --git a/media/utils/include/mediautils/ServiceSingleton.h b/media/utils/include/mediautils/ServiceSingleton.h
new file mode 100644
index 0000000..644d9cd
--- /dev/null
+++ b/media/utils/include/mediautils/ServiceSingleton.h
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#pragma once
+
+#include "BinderGenericUtils.h"
+
+#include <android-base/thread_annotations.h>
+#include <audio_utils/mutex.h>
+#include <chrono>
+#include <map>
+#include <mutex>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+
+/**
+ * ServiceSingleton provides a non-blocking NDK/CPP compatible service cache.
+ *
+ * This is a specialized cache that allows per-service configuration.
+ *
+ * Features:
+ *
+ * 1) Seamless compatibility with NDK and CPP based interfaces.
+ * 2) Time-out based service acquisition.
+ * Set the maximum time to wait for any service.
+ * 3) Service prefetch:
+ * Reduce start-up by prefetching service in advance (not on demand).
+ * Prefetch is automatically installed by getService().
+ * 4) Manual interface setting for test and non-service manager acquisition support.
+ *
+ * If both NDK and CPP interfaces are available, we prefer the CPP version
+ * for the following reasons:
+ * 1) Established sp<> reference counting avoids mistakes. NDK tends to be error-prone.
+ * 2) Possible reduced binder object clutter by a singleton notification binder object.
+ * Fewer binder objects are more efficient for the binder driver and ServiceManager.
+ * For example, fewer binder deaths means less ServiceManager (linear time) cleanup.
+ * A single binder object also offers binder access serialization.
+ * 3) CPP offers slightly better efficiency as it is closer to the
+ * actual implementation, a minor detail and effect.
+ *
+ * We use a per-service ServiceHandler object to collect methods and implementation details.
+ * Currently this is separate for NDK and CPP interfaces to the same service;
+ * unification is possible by using ibinder_internals.h.
+ */
+namespace android::mediautils {
+
+enum ServiceOptions {
+ kNone = 0,
+ kNonNull = (1 << 0), // don't return a null interface unless disabled.
+ // partially implemented and experimental.
+};
+
+// Traits may come through a constexpr static function collection.
+// This participates in small buffer optimization SBO in std::function impl.
+template <typename Service>
+struct DefaultServiceTraits {
+ // getServiceName() returns the name associated with Service.
+ //
+ // If name is empty, it returns the name from the Service descriptor.
+ // If name starts with '/', it appends the name as a version to the Service descriptor,
+ // e.g. "/default".
+ // Otherwise the name is assumed to be the Service name.
+ static constexpr const char* getServiceName() { return "/default"; }
+
+ // This callback is called when a new service is received.
+ // The callback requires at least one thread in the Binder threadpool.
+ static constexpr void onNewService(const InterfaceType<Service>&) {}
+
+ // This callback is called if the service has died.
+ // The callback requires at least one thread in the Binder threadpool.
+ static constexpr void onServiceDied(const InterfaceType<Service>&) {}
+
+ // ServiceOptions configured for the Service.
+ static constexpr ServiceOptions options() { return ServiceOptions::kNone; }
+};
+
+// We store the traits as functors.
+template <typename Service>
+struct FunctionalServiceTraits {
+ template <typename ServiceTraits>
+ explicit FunctionalServiceTraits(const ServiceTraits& serviceTraits)
+ : getServiceName{serviceTraits.getServiceName}
+ , onNewService{serviceTraits.onNewService}
+ , onServiceDied{serviceTraits.onServiceDied}
+ , options{serviceTraits.options} {
+ }
+ std::function<const char*()> getServiceName;
+ std::function<void(const InterfaceType<Service>& service)> onNewService;
+ std::function<void(const InterfaceType<Service>& service)> onServiceDied;
+ std::function<ServiceOptions()> options;
+};
+
+namespace details {
+
+class ServiceHandler
+{
+public:
+ /**
+ * Returns a ServiceHandler, templated type T is String16 for the native type
+ * of the CPP service descriptors and const char* for the native type of the NDK
+ * service descriptors.
+ */
+ template<typename T>
+ requires (std::is_same_v<T, const char*> || std::is_same_v<T, String16>)
+ static std::shared_ptr<ServiceHandler> getInstance(const T& name);
+
+ /**
+ * Initializes the service handler with new service traits
+ * (methods that are triggered on service events).
+ *
+ * This is optional. Default construction of traits is allowed for
+ * services that do not require special handling.
+ *
+ * @param serviceTraits
+ * @return true if the service handler had been previously initialized.
+ */
+ template<typename Service, typename ServiceTraits>
+ bool init(const ServiceTraits& serviceTraits) {
+ auto traits = std::make_shared<FunctionalServiceTraits<Service>>(serviceTraits);
+ std::shared_ptr<void> oldTraits;
+ std::lock_guard l(mMutex);
+ std::swap(oldTraits, mTraits);
+ const bool existing = oldTraits != nullptr;
+ mTraits = std::move(traits);
+ mSkip = false;
+ return existing;
+ }
+
+ /**
+ * Returns the service based on a timeout.
+ *
+ * @param waitNs the time to wait, internally clamped to (0, INT64_MAX / 2) to
+ * avoid numeric overflow.
+ * @param useCallback installs a callback instead of polling.
+ * the Callback persists if the call timeouts. A Callback requires
+ * at least one thread in the threadpool.
+ * @return Service interface.
+ */
+ template <typename Service>
+ auto get(std::chrono::nanoseconds waitNs, bool useCallback) {
+ audio_utils::unique_lock ul(mMutex);
+ auto& service = std::get<BaseInterfaceType<Service>>(mService);
+
+ if (mSkip || (service && mValid)) return service; // early check.
+
+ // clamp to avoid numeric overflow. INT64_MAX / 2 is effectively forever for a device.
+ std::chrono::nanoseconds kWaitLimitNs(
+ std::numeric_limits<decltype(waitNs.count())>::max() / 2);
+ waitNs = std::clamp(waitNs, decltype(waitNs)(0), kWaitLimitNs);
+ const auto end = std::chrono::steady_clock::now() + waitNs;
+
+ for (bool first = true; true; first = false) {
+ // we may have released mMutex, so see if service has been obtained.
+ if (mSkip || (service && mValid)) return service;
+
+ const auto traits = getTraits_l<Service>();
+
+ // first time or not using callback, check the service.
+ if (first || !useCallback) {
+ auto service_new = checkServicePassThrough<Service>(
+ traits->getServiceName());
+ if (service_new) {
+ mValid = true;
+ service = std::move(service_new);
+ setDeathNotifier_l<Service>();
+ auto service_fixed = service; // we're releasing the mutex.
+ ul.unlock();
+ traits->onNewService(interfaceFromBase<Service>(service_fixed));
+ mCv.notify_all();
+ return service_fixed;
+ }
+ }
+
+ // install service callback if needed.
+ if (useCallback && !mServiceNotificationHandle) {
+ setServiceNotifier_l<Service>();
+ }
+
+ // check time expiration.
+ const auto now = std::chrono::steady_clock::now();
+ if (now >= end
+ && (service || !(traits->options() & ServiceOptions::kNonNull))) {
+ return service;
+ }
+
+ // compute time to wait, then wait.
+ if (mServiceNotificationHandle) {
+ mCv.wait_until(ul, end);
+ } else {
+ const auto target = now + kPollTime;
+ mCv.wait_until(ul, std::min(target, end));
+ }
+ // loop back to see if we have any state change.
+ }
+ }
+
+ /**
+ * Sets an externally provided service override.
+ *
+ * @param Service
+ * @param service_new
+ */
+ template<typename Service>
+ void set(const InterfaceType<Service>& service_new) {
+ audio_utils::unique_lock ul(mMutex);
+ auto& service = std::get<BaseInterfaceType<Service>>(mService);
+ const auto traits = getTraits_l<Service>();
+ if (service) {
+ auto orig_service = service;
+ invalidateService_l<Service>();
+ ul.unlock();
+ traits->onServiceDied(interfaceFromBase<Service>(orig_service));
+ }
+ service = service_new;
+ ul.unlock();
+ // should we set the death notifier? It could be a local service.
+ if (service_new) traits->onNewService(service_new);
+ mCv.notify_all();
+ }
+
+ /**
+ * Disables cache management in the ServiceHandler. init() needs to be
+ * called to restart.
+ *
+ * All notifiers removed.
+ * Service pointer is released.
+ */
+ template<typename Service>
+ void skip() {
+ audio_utils::unique_lock ul(mMutex);
+ mSkip = true;
+ // remove notifiers. OK to hold lock as presuming notifications one-way
+ // or manually triggered outside of lock.
+ mDeathNotificationHandle.reset();
+ mServiceNotificationHandle.reset();
+ auto& service = std::get<BaseInterfaceType<Service>>(mService);
+ const auto traits = getTraits_l<Service>();
+ std::shared_ptr<void> oldTraits;
+ std::swap(oldTraits, mTraits); // destroyed outside of lock.
+ if (service) {
+ auto orig_service = service; // keep reference to service to manually notify death.
+ invalidateService_l<Service>(); // sets service to nullptr
+ ul.unlock();
+ traits->onServiceDied(interfaceFromBase<Service>(orig_service));
+ } else {
+ ul.unlock();
+ }
+ mCv.notify_all();
+ }
+
+private:
+
+ // invalidateService_l is called to remove the old death notifier,
+ // invalidate the service, and optionally clear the service pointer.
+ template <typename Service>
+ void invalidateService_l() REQUIRES(mMutex) {
+ mDeathNotificationHandle.reset();
+ const auto traits = getTraits_l<Service>();
+ mValid = false;
+ if (!(traits->options() & ServiceOptions::kNonNull) || mSkip) {
+ auto &service = std::get<BaseInterfaceType<Service>>(mService);
+ service = nullptr;
+ }
+ }
+
+ // gets the traits set by init(), initializes with default if init() not called.
+ template <typename Service>
+ std::shared_ptr<FunctionalServiceTraits<Service>> getTraits_l() REQUIRES(mMutex) {
+ if (!mTraits) {
+ mTraits = std::make_shared<FunctionalServiceTraits<Service>>(
+ DefaultServiceTraits<Service>{});
+ }
+ return std::static_pointer_cast<FunctionalServiceTraits<Service>>(mTraits);
+ }
+
+ // sets the service notification
+ template <typename Service>
+ void setServiceNotifier_l() REQUIRES(mMutex) {
+ const auto traits = getTraits_l<Service>();
+ mServiceNotificationHandle = requestServiceNotification<Service>(
+ [traits, this](const InterfaceType<Service>& service) {
+ audio_utils::unique_lock ul(mMutex);
+ auto originalService = std::get<BaseInterfaceType<Service>>(mService);
+ if (originalService != service) {
+ mService = service;
+ mValid = true;
+ setDeathNotifier_l<Service>();
+ traits->onNewService(service);
+ }
+ ul.unlock();
+ mCv.notify_all();
+ }, traits->getServiceName());
+ ALOGW_IF(!mServiceNotificationHandle, "%s: cannot register service notification %s"
+ " (do we have permission?)",
+ __func__, toString(Service::descriptor).c_str());
+ }
+
+ // sets the death notifier for mService (mService must be non-null).
+ template <typename Service>
+ void setDeathNotifier_l() REQUIRES(mMutex) {
+ auto base = std::get<BaseInterfaceType<Service>>(mService);
+ auto service = interfaceFromBase<Service>(base);
+ const auto binder = binderFromInterface(service);
+ if (binder.get()) {
+ auto traits = getTraits_l<Service>();
+ mDeathNotificationHandle = requestDeathNotification(
+ base, [traits, service, this]() {
+ // as only one death notification is dispatched,
+ // we do not need to generation count.
+ {
+ std::lock_guard l(mMutex);
+ invalidateService_l<Service>();
+ }
+ traits->onServiceDied(service);
+ });
+ ALOGW_IF(!mDeathNotificationHandle, "%s: cannot register death notification %s"
+ " (already died?)",
+ __func__, toString(Service::descriptor).c_str());
+ }
+ }
+
+ // initializes the variant for NDK use (called on first creation in the cache map).
+ void init_ndk() EXCLUDES(mMutex) {
+ std::lock_guard l(mMutex);
+ mService = std::shared_ptr<::ndk::ICInterface>{};
+ }
+
+ // initializes the variant for CPP use (called on first creation in the cache map).
+ void init_cpp() EXCLUDES(mMutex) {
+ std::lock_guard l(mMutex);
+ mService = sp<::android::IInterface>{};
+ }
+
+ static std::string toString(const std::string& s) { return s; }
+ static std::string toString(const String16& s) { return String8(s).c_str(); }
+
+ mutable std::mutex mMutex;
+ std::condition_variable mCv;
+ static constexpr auto kPollTime = std::chrono::seconds(1);
+
+ std::variant<std::shared_ptr<::ndk::ICInterface>,
+ sp<::android::IInterface>> mService GUARDED_BY(mMutex);
+ // aesthetically we place these last, but a ServiceHandler is never deleted in
+ // current operation, so there is no deadlock on destruction.
+ std::shared_ptr<void> mDeathNotificationHandle GUARDED_BY(mMutex);
+ std::shared_ptr<void> mServiceNotificationHandle GUARDED_BY(mMutex);
+ std::shared_ptr<void> mTraits GUARDED_BY(mMutex);
+
+ // mValid is true iff the service is non-null and alive.
+ bool mValid GUARDED_BY(mMutex) = false;
+
+ // mSkip indicates that the service is not cached.
+ bool mSkip GUARDED_BY(mMutex) = false;
+};
+
+} // details
+
+//----------------------------------
+// ServiceSingleton API
+//
+
+/*
+ * Implementation detail:
+ *
+ * Each CPP or NDK service interface has a unique ServiceHandler that
+ * is stored in a singleton cache. The cache key is based on the service descriptor string
+ * so only one version can be chosen. (The particular version may be changed using
+ * ServiceTraits.getName()).
+ */
+
+/**
+ * Sets the service trait parameters for acquiring the Service interface.
+ *
+ * If this is not set before the first service fetch, then default service traits are used.
+ *
+ * @return true if there is a preexisting (including prior default set) traits.
+ */
+template<typename Service, typename ServiceTraits>
+bool initService(const ServiceTraits& serviceTraits = {}) {
+ const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
+ return serviceHandler->template init<Service>(serviceTraits);
+}
+
+/**
+ * Returns either a std::shared_ptr<Interface> or sp<Interface>
+ * for the AIDL service. If the service is not available within waitNs,
+ * the method will return nullptr
+ * (or the previous invalidated service if Service.options() & kNonNull).
+ *
+ * This method installs a callback to obtain the service, so with waitNs == 0, it may be used to
+ * prefetch the service before it is actually needed.
+ *
+ * @param waitNs wait time for the service to become available.
+ * @return
+ * a sp<> for a CPP interface
+ * a std::shared_ptr<> for a NDK interface
+ *
+ */
+template<typename Service>
+auto getService(std::chrono::nanoseconds waitNs = {}) {
+ const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
+ return interfaceFromBase<Service>(serviceHandler->template get<Service>(
+ waitNs, true /* useCallback */));
+}
+
+/**
+ * Returns either a std::shared_ptr<Interface> or sp<Interface>
+ * for the AIDL service. If the service is not available within waitNs,
+ * the method will return nullptr
+ * (or the previous invalidated service if Service.options() & kNonNull).
+ *
+ * This method polls to obtain the service, which
+ * is useful if the service is restricted due to permissions or
+ * one is concerned about ThreadPool starvation.
+ *
+ * @param waitNs wait time for the service to become available.
+ * @return
+ * a sp<> for a CPP interface
+ * a std::shared_ptr<> for a NDK interface
+ */
+template<typename Service>
+auto checkService(std::chrono::nanoseconds waitNs = {}) {
+ const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
+ return interfaceFromBase<Service>(serviceHandler->template get<Service>(
+ waitNs, false /* useCallback */));
+}
+
+/**
+ * Sets a service implementation override, replacing any fetched service from ServiceManager.
+ *
+ * An empty service clears the cache.
+ */
+template<typename Service>
+void setService(const InterfaceType<Service>& service) {
+ const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
+ serviceHandler->template set<Service>(service);
+}
+
+/**
+ * Disables the service cache.
+ *
+ * This releases any service and notification callbacks. After this,
+ * another initService() can be called seamlessly.
+ */
+template<typename Service>
+void skipService() {
+ const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
+ serviceHandler->template skip<Service>();
+}
+
+} // namespace android::mediautils
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index ff11b42..4456df2 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -12,8 +12,6 @@
cc_defaults {
name: "libmediautils_tests_config",
- host_supported: true,
-
cflags: [
"-Wall",
"-Werror",
@@ -67,6 +65,22 @@
],
}
+aidl_interface {
+ name: "ServiceSingletonTestInterface",
+ unstable: true,
+ srcs: [
+ "IServiceSingletonTest.aidl",
+ ],
+ backend: {
+ cpp: {
+ enabled: true,
+ },
+ ndk: {
+ enabled: true,
+ },
+ },
+}
+
cc_test_library {
name: "libsharedtest",
@@ -178,6 +192,34 @@
}
cc_test {
+ name: "service_singleton_tests",
+
+ defaults: ["libmediautils_tests_config"],
+
+ // to add and get services, we need to be root.
+ require_root: true,
+ host_supported: false,
+
+ srcs: [
+ "service_singleton_tests.cpp",
+ ],
+
+ shared_libs: [
+ "libaudioutils",
+ "libbinder",
+ "libbinder_ndk",
+ "liblog",
+ "libmediautils",
+ "libutils",
+ ],
+
+ static_libs: [
+ "ServiceSingletonTestInterface-cpp",
+ "ServiceSingletonTestInterface-ndk",
+ ],
+}
+
+cc_test {
name: "static_string_tests",
defaults: ["libmediautils_tests_defaults"],
diff --git a/media/utils/tests/IServiceSingletonTest.aidl b/media/utils/tests/IServiceSingletonTest.aidl
new file mode 100644
index 0000000..9f889a6
--- /dev/null
+++ b/media/utils/tests/IServiceSingletonTest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+interface IServiceSingletonTest {
+ int inc();
+}
diff --git a/media/utils/tests/service_singleton_tests.cpp b/media/utils/tests/service_singleton_tests.cpp
new file mode 100644
index 0000000..8656a20
--- /dev/null
+++ b/media/utils/tests/service_singleton_tests.cpp
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2024 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 "service_singleton_tests"
+
+#include <mediautils/ServiceSingleton.h>
+
+#include "BnServiceSingletonTest.h"
+#include "aidl/BnServiceSingletonTest.h"
+#include <audio_utils/RunRemote.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+using namespace android;
+
+/**
+ * Service Singleton Test uses a worker process to spawn new binder services.
+ *
+ * A worker process is required since we cannot fork after registering
+ * with the binder driver.
+ *
+ * Test Process -> Worker_Process -> Service Process(1)
+ * -> Service Process(2)
+ * -> ....
+ */
+
+// Service implementation.
+class ServiceSingletonTestCpp : public BnServiceSingletonTest {
+public:
+ binder::Status inc(int32_t* _aidl_return) final {
+ *_aidl_return = ++mValue;
+ return binder::Status::ok();
+ }
+ std::atomic_int32_t mValue = 0;
+};
+
+// The service traits increment static atomic counters, which
+// validates that the trait callbacks are invoked.
+static std::atomic_int32_t sNewService = 0;
+static std::atomic_int32_t sServiceDied = 0;
+
+template <typename Service>
+struct TestServiceTraits : public mediautils::DefaultServiceTraits<Service> {
+ static constexpr const char* getServiceName() { return ""; }
+ static constexpr void onNewService(const mediautils::InterfaceType<Service>&) {
+ ++sNewService;
+ }
+ static constexpr void onServiceDied(const mediautils::InterfaceType<Service>&) {
+ ++sServiceDied;
+ }
+};
+
+// Here we have an alternative set of service traits,
+// used to validate that we can switch traits for the service singleton.
+static std::atomic_int32_t sNewService2 = 0;
+static std::atomic_int32_t sServiceDied2 = 0;
+
+template <typename Service>
+struct TestServiceTraits2 : public mediautils::DefaultServiceTraits<Service> {
+ static constexpr const char* getServiceName() { return ""; }
+ static constexpr void onNewService(const mediautils::InterfaceType<Service>&) {
+ ++sNewService2;
+ }
+ static constexpr void onServiceDied(const mediautils::InterfaceType<Service>&) {
+ ++sServiceDied2;
+ }
+};
+
+/*
+ * ServiceThreads run in a remote process.
+ *
+ * The WorkerThread is used to launch and kill the ServiceThread in a remote process.
+ */
+static void ServiceThread(audio_utils::RunRemote& runRemote) {
+ int c = runRemote.getc(); // requires any character to launch
+ auto service = sp<IServiceSingletonTest>::cast(sp<ServiceSingletonTestCpp>::make());
+ mediautils::addService(service);
+ ProcessState::self()->startThreadPool();
+ runRemote.putc(c); // echo character.
+ IPCThreadState::self()->joinThreadPool();
+}
+
+/*
+ * The WorkerThread is run in a remote process from the test. It communicates with
+ * the test process through pipes.
+ */
+static void WorkerThread(audio_utils::RunRemote& runRemote) {
+ std::shared_ptr<audio_utils::RunRemote> remoteService;
+ while (true) {
+ const int c = runRemote.getc();
+ switch (c) {
+ case 'a': // launch a new service.
+ // if the old service isn't destroyed, it will be destroyed here
+ // when the RunRemote is replaced.
+ remoteService = std::make_shared<audio_utils::RunRemote>(ServiceThread);
+ remoteService->run();
+ remoteService->putc('a'); // create service.
+ (void)remoteService->getc(); // ensure it is created.
+ runRemote.putc(c); // echo
+ break;
+ case 'b': // destroys the old service.
+ remoteService.reset(); // this kills the service.
+ runRemote.putc(c); // echo
+ break;
+ default: // respond that we don't know what happened!
+ runRemote.putc('?');
+ break;
+ }
+ }
+}
+
+// This is a monolithic test.
+TEST(service_singleton_tests, one_and_only) {
+ std::atomic_int32_t listenerServiceCreated = 0;
+ std::atomic_int32_t listenerServiceDied = 0;
+
+ // initialize the service cache with a custom handler.
+ mediautils::initService<
+ IServiceSingletonTest, TestServiceTraits<IServiceSingletonTest>>({});
+ mediautils::initService<
+ aidl::IServiceSingletonTest, TestServiceTraits<aidl::IServiceSingletonTest>>({});
+
+ // start the worker thread that spawns the services.
+ auto remoteWorker = std::make_shared<audio_utils::RunRemote>(WorkerThread);
+ remoteWorker->run();
+
+ // now we are ready for binder.
+ ProcessState::self()->startThreadPool();
+
+ // check that our service isn't preexisting.
+ {
+ auto service = mediautils::checkServicePassThrough<IServiceSingletonTest>();
+ EXPECT_FALSE(service);
+
+ auto service2 = mediautils::checkServicePassThrough<aidl::IServiceSingletonTest>();
+ EXPECT_FALSE(service2);
+ }
+ EXPECT_EQ(0, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ {
+ auto service = mediautils::checkService<IServiceSingletonTest>();
+ EXPECT_FALSE(service);
+
+ auto service2 = mediautils::checkService<aidl::IServiceSingletonTest>();
+ EXPECT_FALSE(service2);
+ }
+ EXPECT_EQ(0, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ // getService will register a notification handler that fetches the
+ // service in the background.
+ {
+ auto service = mediautils::getService<IServiceSingletonTest>();
+ EXPECT_FALSE(service);
+
+ auto service2 = mediautils::getService<aidl::IServiceSingletonTest>();
+ EXPECT_FALSE(service2);
+ }
+ EXPECT_EQ(0, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ // now spawn the service.
+ remoteWorker->putc('a');
+ EXPECT_EQ('a', remoteWorker->getc());
+
+ sleep(1); // In the background, 2 services were fetched.
+
+ EXPECT_EQ(2, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ // we repeat the prior checks, but the service is cached now.
+ {
+ auto service = mediautils::checkServicePassThrough<IServiceSingletonTest>();
+ EXPECT_TRUE(service);
+
+ auto service2 = mediautils::checkServicePassThrough<aidl::IServiceSingletonTest>();
+ EXPECT_TRUE(service2);
+ }
+ EXPECT_EQ(2, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ {
+ auto service = mediautils::checkService<IServiceSingletonTest>();
+ EXPECT_TRUE(service);
+
+ auto service2 = mediautils::checkService<aidl::IServiceSingletonTest>();
+ EXPECT_TRUE(service2);
+ }
+ EXPECT_EQ(2, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ {
+ auto service = mediautils::getService<IServiceSingletonTest>();
+ EXPECT_TRUE(service);
+
+ auto service2 = mediautils::getService<aidl::IServiceSingletonTest>();
+ EXPECT_TRUE(service2);
+ }
+ EXPECT_EQ(2, sNewService);
+ EXPECT_EQ(0, sServiceDied);
+
+ // destroy the service.
+ remoteWorker->putc('b');
+ EXPECT_EQ('b', remoteWorker->getc());
+
+ sleep(1);
+
+ // We expect the died callbacks.
+ EXPECT_EQ(2, sNewService);
+ EXPECT_EQ(2, sServiceDied);
+
+ // we can also manually check whether there is a new service by
+ // requesting service notifications. This is outside of the service singleton
+ // traits.
+ auto handle1 = mediautils::requestServiceNotification<IServiceSingletonTest>(
+ [&](const sp<IServiceSingletonTest>&) { ++listenerServiceCreated; });
+ auto handle2 = mediautils::requestServiceNotification<aidl::IServiceSingletonTest>(
+ [&](const std::shared_ptr<aidl::IServiceSingletonTest>&) {
+ ++listenerServiceCreated; });
+
+ // Spawn the service again.
+ remoteWorker->putc('a');
+ EXPECT_EQ('a', remoteWorker->getc());
+
+ sleep(1); // In the background, 2 services were fetched.
+
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(2, sServiceDied);
+
+ EXPECT_EQ(2, listenerServiceCreated); // our listener picked up the service creation.
+
+ std::shared_ptr<void> handle3, handle4;
+ std::shared_ptr<aidl::IServiceSingletonTest> keepAlive; // NDK Workaround!
+ {
+ auto service = mediautils::getService<IServiceSingletonTest>();
+ EXPECT_TRUE(service);
+
+ auto service2 = mediautils::getService<aidl::IServiceSingletonTest>();
+ EXPECT_TRUE(service2);
+
+ keepAlive = service2;
+
+ // we can also request our own death notifications (outside of the service traits).
+ handle3 = mediautils::requestDeathNotification(service, [&] { ++listenerServiceDied; });
+ handle4 = mediautils::requestDeathNotification(service2, [&] { ++listenerServiceDied; });
+ }
+
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(2, sServiceDied);
+
+ // destroy the service.
+
+ remoteWorker->putc('b');
+ EXPECT_EQ('b', remoteWorker->getc());
+
+ sleep(1);
+
+ // We expect the died callbacks.
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(4, sServiceDied);
+
+ EXPECT_EQ(2, listenerServiceCreated);
+ EXPECT_EQ(2, listenerServiceDied); // NDK Workaround - without keepAlive, this is 1.
+ // the death notification is invalidated without a
+ // pointer to the binder object.
+
+ keepAlive.reset();
+
+ // Cancel the singleton cache.
+ mediautils::skipService<IServiceSingletonTest>();
+ mediautils::skipService<aidl::IServiceSingletonTest>();
+
+ // Spawn the service again.
+ remoteWorker->putc('a');
+ EXPECT_EQ('a', remoteWorker->getc());
+
+ sleep(1);
+
+ // We expect no change from the service traits (service not cached).
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(4, sServiceDied);
+ EXPECT_EQ(4, listenerServiceCreated); // our listener picks it up.
+
+ // remove service
+ remoteWorker->putc('b');
+ EXPECT_EQ('b', remoteWorker->getc());
+
+ sleep(1);
+
+ // We expect no change from the service traits (service not cached).
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(4, sServiceDied);
+ EXPECT_EQ(4, listenerServiceCreated);
+ EXPECT_EQ(2, listenerServiceDied); // binder died is associated with the actual handle.
+
+ // replace the service traits.
+ {
+ auto previous = mediautils::initService<
+ IServiceSingletonTest, TestServiceTraits2<IServiceSingletonTest>>({});
+ auto previous2 = mediautils::initService<
+ aidl::IServiceSingletonTest, TestServiceTraits2<aidl::IServiceSingletonTest>>({});
+
+ EXPECT_FALSE(previous);
+ EXPECT_FALSE(previous2);
+ }
+
+ // We expect no change with old counters.
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(4, sServiceDied);
+ EXPECT_EQ(0, sNewService2);
+ EXPECT_EQ(0, sServiceDied2);
+
+ {
+ auto service = mediautils::getService<IServiceSingletonTest>();
+ EXPECT_FALSE(service);
+
+ auto service2 = mediautils::getService<aidl::IServiceSingletonTest>();
+ EXPECT_FALSE(service2);
+ }
+
+ EXPECT_EQ(4, sNewService);
+ EXPECT_EQ(4, sServiceDied);
+ EXPECT_EQ(0, sNewService2);
+ EXPECT_EQ(0, sServiceDied2);
+
+ // Spawn the service again.
+ remoteWorker->putc('a');
+ EXPECT_EQ('a', remoteWorker->getc());
+
+ sleep(1);
+
+ EXPECT_EQ(4, sNewService); // old counters do not change.
+ EXPECT_EQ(4, sServiceDied);
+ EXPECT_EQ(2, sNewService2); // new counters change
+ EXPECT_EQ(0, sServiceDied2);
+
+ EXPECT_EQ(6, listenerServiceCreated); // listener associated with service name picks up info.
+
+ // Release the service.
+ remoteWorker->putc('b');
+ EXPECT_EQ('b', remoteWorker->getc());
+
+ sleep(1);
+
+ EXPECT_EQ(4, sNewService); // old counters do not change.
+ EXPECT_EQ(4, sServiceDied);
+ EXPECT_EQ(2, sNewService2); // new counters change
+ EXPECT_EQ(2, sServiceDied2);
+}