servicemanager: notifications
You can now register and receive notifications for new services.
Eye towards:
- avoiding while(true) getService loops
- need it to make AIDL dynamic HALs
- need it for feature parity w/ HIDL
Bug: 136027762
Test: servicemanager_test
Change-Id: Iffbf984e87214aa9c9b7af8a5ae682e127ba40a5
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 119e4c3..b3aa342 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -28,6 +28,13 @@
ServiceManager::~ServiceManager() {
// this should only happen in tests
+ for (const auto& [name, callbacks] : mNameToCallback) {
+ CHECK(!callbacks.empty()) << name;
+ for (const auto& callback : callbacks) {
+ CHECK(callback != nullptr) << name;
+ }
+ }
+
for (const auto& [name, service] : mNameToService) {
CHECK(service.binder != nullptr) << name;
}
@@ -117,6 +124,14 @@
.dumpPriority = dumpPriority,
};
+ auto it = mNameToCallback.find(name);
+ if (it != mNameToCallback.end()) {
+ for (const sp<IServiceCallback>& cb : it->second) {
+ // permission checked in registerForNotifications
+ cb->onRegistration(name, binder);
+ }
+ }
+
return Status::ok();
}
@@ -146,6 +161,84 @@
return Status::ok();
}
+Status ServiceManager::registerForNotifications(
+ const std::string& name, const sp<IServiceCallback>& callback) {
+ auto ctx = mAccess->getCallingContext();
+
+ if (!mAccess->canFind(ctx, name)) {
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+
+ if (!isValidServiceName(name)) {
+ LOG(ERROR) << "Invalid service name: " << name;
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+ }
+
+ if (callback == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+
+ if (OK != IInterface::asBinder(callback)->linkToDeath(this)) {
+ LOG(ERROR) << "Could not linkToDeath when adding " << name;
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ }
+
+ mNameToCallback[name].push_back(callback);
+
+ if (auto it = mNameToService.find(name); it != mNameToService.end()) {
+ const sp<IBinder>& binder = it->second.binder;
+
+ // never null if an entry exists
+ CHECK(binder != nullptr) << name;
+ callback->onRegistration(name, binder);
+ }
+
+ return Status::ok();
+}
+Status ServiceManager::unregisterForNotifications(
+ const std::string& name, const sp<IServiceCallback>& callback) {
+ auto ctx = mAccess->getCallingContext();
+
+ if (!mAccess->canFind(ctx, name)) {
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+
+ bool found = false;
+
+ auto it = mNameToCallback.find(name);
+ if (it != mNameToCallback.end()) {
+ removeCallback(IInterface::asBinder(callback), &it, &found);
+ }
+
+ if (!found) {
+ LOG(ERROR) << "Trying to unregister callback, but none exists " << name;
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ }
+
+ return Status::ok();
+}
+
+void ServiceManager::removeCallback(const wp<IBinder>& who,
+ CallbackMap::iterator* it,
+ bool* found) {
+ std::vector<sp<IServiceCallback>>& listeners = (*it)->second;
+
+ for (auto lit = listeners.begin(); lit != listeners.end();) {
+ if (IInterface::asBinder(*lit) == who) {
+ if(found) *found = true;
+ lit = listeners.erase(lit);
+ } else {
+ ++lit;
+ }
+ }
+
+ if (listeners.empty()) {
+ *it = mNameToCallback.erase(*it);
+ } else {
+ it++;
+ }
+}
+
void ServiceManager::binderDied(const wp<IBinder>& who) {
for (auto it = mNameToService.begin(); it != mNameToService.end();) {
if (who == it->second.binder) {
@@ -154,6 +247,10 @@
++it;
}
}
+
+ for (auto it = mNameToCallback.begin(); it != mNameToCallback.end();) {
+ removeCallback(who, &it, nullptr /*found*/);
+ }
}
} // namespace android
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 43723c5..fcc5124 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -17,11 +17,14 @@
#pragma once
#include <android/os/BnServiceManager.h>
+#include <android/os/IServiceCallback.h>
#include "Access.h"
namespace android {
+using os::IServiceCallback;
+
class ServiceManager : public os::BnServiceManager, public IBinder::DeathRecipient {
public:
ServiceManager(std::unique_ptr<Access>&& access);
@@ -29,19 +32,35 @@
binder::Status getService(const std::string& name, sp<IBinder>* outBinder) override;
binder::Status checkService(const std::string& name, sp<IBinder>* outBinder) override;
- binder::Status addService(const std::string& name, const sp<IBinder>& binder, bool allowIsolated, int32_t dumpPriority) override;
+ binder::Status addService(const std::string& name, const sp<IBinder>& binder,
+ bool allowIsolated, int32_t dumpPriority) override;
binder::Status listServices(int32_t dumpPriority, std::vector<std::string>* outList) override;
+ binder::Status registerForNotifications(const std::string& name,
+ const sp<IServiceCallback>& callback) override;
+ binder::Status unregisterForNotifications(const std::string& name,
+ const sp<IServiceCallback>& callback) override;
void binderDied(const wp<IBinder>& who) override;
private:
struct Service {
- sp<IBinder> binder;
+ sp<IBinder> binder; // not null
bool allowIsolated;
int32_t dumpPriority;
};
- std::map<std::string, Service> mNameToService;
+ using CallbackMap = std::map<std::string, std::vector<sp<IServiceCallback>>>;
+ using ServiceMap = std::map<std::string, Service>;
+
+ // removes a callback from mNameToCallback, removing it if the vector is empty
+ // this updates iterator to the next location
+ void removeCallback(const wp<IBinder>& who,
+ CallbackMap::iterator* it,
+ bool* found);
+
+ CallbackMap mNameToCallback;
+ ServiceMap mNameToService;
+
std::unique_ptr<Access> mAccess;
};
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 91485e4..3c211d2 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -14,7 +14,10 @@
* limitations under the License.
*/
+#include <android/os/BnServiceCallback.h>
+#include <binder/Binder.h>
#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
#include <cutils/android_filesystem_config.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
@@ -24,8 +27,11 @@
using android::sp;
using android::Access;
+using android::BBinder;
using android::IBinder;
using android::ServiceManager;
+using android::binder::Status;
+using android::os::BnServiceCallback;
using android::os::IServiceManager;
using testing::_;
using testing::ElementsAre;
@@ -33,9 +39,14 @@
using testing::Return;
static sp<IBinder> getBinder() {
- // It doesn't matter what remote binder it is, we just need one so that linkToDeath will work.
- // The context manager (servicemanager) is easy to get and is in another process.
- return android::ProcessState::self()->getContextObject(nullptr);
+ class LinkableBinder : public BBinder {
+ android::status_t linkToDeath(const sp<DeathRecipient>&, void*, uint32_t) override {
+ // let SM linkToDeath
+ return android::OK;
+ }
+ };
+
+ return new LinkableBinder;
}
class MockAccess : public Access {
@@ -132,12 +143,14 @@
TEST(GetService, HappyHappy) {
auto sm = getPermissiveServiceManager();
- EXPECT_TRUE(sm->addService("foo", getBinder(), false /*allowIsolated*/,
+ sp<IBinder> service = getBinder();
+
+ EXPECT_TRUE(sm->addService("foo", service, false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
sp<IBinder> out;
EXPECT_TRUE(sm->getService("foo", &out).isOk());
- EXPECT_EQ(getBinder(), out);
+ EXPECT_EQ(service, out);
}
TEST(GetService, NonExistant) {
@@ -181,12 +194,13 @@
sp<ServiceManager> sm = new ServiceManager(std::move(access));
- EXPECT_TRUE(sm->addService("foo", getBinder(), true /*allowIsolated*/,
+ sp<IBinder> service = getBinder();
+ EXPECT_TRUE(sm->addService("foo", service, true /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
sp<IBinder> out;
EXPECT_TRUE(sm->getService("foo", &out).isOk());
- EXPECT_EQ(getBinder(), out.get());
+ EXPECT_EQ(service, out.get());
}
TEST(GetService, NotAllowedFromIsolated) {
@@ -265,3 +279,145 @@
// all there and in the right order
EXPECT_THAT(out, ElementsAre("sa"));
}
+
+class CallbackHistorian : public BnServiceCallback {
+ Status onRegistration(const std::string& name, const sp<IBinder>& binder) override {
+ registrations.push_back(name);
+ binders.push_back(binder);
+ return Status::ok();
+ }
+
+ android::status_t linkToDeath(const sp<DeathRecipient>&, void*, uint32_t) override {
+ // let SM linkToDeath
+ return android::OK;
+ }
+
+public:
+ std::vector<std::string> registrations;
+ std::vector<sp<IBinder>> binders;
+};
+
+TEST(ServiceNotifications, NoPermissionsRegister) {
+ std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
+
+ EXPECT_CALL(*access, getCallingContext()).WillOnce(Return(Access::CallingContext{}));
+ EXPECT_CALL(*access, canFind(_,_)).WillOnce(Return(false));
+
+ sp<ServiceManager> sm = new ServiceManager(std::move(access));
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(),
+ Status::EX_SECURITY);
+}
+
+TEST(ServiceNotifications, NoPermissionsUnregister) {
+ std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
+
+ EXPECT_CALL(*access, getCallingContext()).WillOnce(Return(Access::CallingContext{}));
+ EXPECT_CALL(*access, canFind(_,_)).WillOnce(Return(false));
+
+ sp<ServiceManager> sm = new ServiceManager(std::move(access));
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ // should always hit security error first
+ EXPECT_EQ(sm->unregisterForNotifications("foofoo", cb).exceptionCode(),
+ Status::EX_SECURITY);
+}
+
+TEST(ServiceNotifications, InvalidName) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ EXPECT_EQ(sm->registerForNotifications("foo@foo", cb).exceptionCode(),
+ Status::EX_ILLEGAL_ARGUMENT);
+}
+
+TEST(ServiceNotifications, NullCallback) {
+ auto sm = getPermissiveServiceManager();
+
+ EXPECT_EQ(sm->registerForNotifications("foofoo", nullptr).exceptionCode(),
+ Status::EX_NULL_POINTER);
+}
+
+TEST(ServiceNotifications, Unregister) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ EXPECT_TRUE(sm->registerForNotifications("foofoo", cb).isOk());
+ EXPECT_EQ(sm->unregisterForNotifications("foofoo", cb).exceptionCode(), 0);
+}
+
+TEST(ServiceNotifications, UnregisterWhenNoRegistrationExists) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ EXPECT_EQ(sm->unregisterForNotifications("foofoo", cb).exceptionCode(),
+ Status::EX_ILLEGAL_STATE);
+}
+
+TEST(ServiceNotifications, NoNotification) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ EXPECT_TRUE(sm->registerForNotifications("foofoo", cb).isOk());
+ EXPECT_TRUE(sm->addService("otherservice", getBinder(),
+ false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
+
+ EXPECT_THAT(cb->registrations, ElementsAre());
+ EXPECT_THAT(cb->binders, ElementsAre());
+}
+
+TEST(ServiceNotifications, GetNotification) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ sp<IBinder> service = getBinder();
+
+ EXPECT_TRUE(sm->registerForNotifications("asdfasdf", cb).isOk());
+ EXPECT_TRUE(sm->addService("asdfasdf", service,
+ false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
+
+ EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf"));
+ EXPECT_THAT(cb->binders, ElementsAre(service));
+}
+
+TEST(ServiceNotifications, GetNotificationForAlreadyRegisteredService) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ sp<IBinder> service = getBinder();
+
+ EXPECT_TRUE(sm->addService("asdfasdf", service,
+ false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
+
+ EXPECT_TRUE(sm->registerForNotifications("asdfasdf", cb).isOk());
+
+ EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf"));
+ EXPECT_THAT(cb->binders, ElementsAre(service));
+}
+
+TEST(ServiceNotifications, GetMultipleNotification) {
+ auto sm = getPermissiveServiceManager();
+
+ sp<CallbackHistorian> cb = new CallbackHistorian;
+
+ sp<IBinder> binder1 = getBinder();
+ sp<IBinder> binder2 = getBinder();
+
+ EXPECT_TRUE(sm->registerForNotifications("asdfasdf", cb).isOk());
+ EXPECT_TRUE(sm->addService("asdfasdf", binder1,
+ false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
+ EXPECT_TRUE(sm->addService("asdfasdf", binder2,
+ false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
+
+ EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf", "asdfasdf"));
+ EXPECT_THAT(cb->registrations, ElementsAre("asdfasdf", "asdfasdf"));
+}
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 027418a..86f19c5 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -145,6 +145,7 @@
name: "libbinder_aidl",
srcs: [
"aidl/android/content/pm/IPackageManagerNative.aidl",
+ "aidl/android/os/IServiceCallback.aidl",
"aidl/android/os/IServiceManager.aidl",
],
path: "aidl",
diff --git a/libs/binder/aidl/android/os/IServiceCallback.aidl b/libs/binder/aidl/android/os/IServiceCallback.aidl
new file mode 100644
index 0000000..b29dfed
--- /dev/null
+++ b/libs/binder/aidl/android/os/IServiceCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.os;
+
+/**
+ * @hide
+ */
+oneway interface IServiceCallback {
+ /**
+ * Called when a service is registered.
+ *
+ * @param name the service name that has been registered with
+ * @param binder the binder that is registered
+ */
+ void onRegistration(@utf8InCpp String name, IBinder binder);
+}
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index 50a72aa..60c2cce 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -16,6 +16,8 @@
package android.os;
+import android.os.IServiceCallback;
+
/**
* Basic interface for finding and publishing system services.
*
@@ -77,4 +79,14 @@
* Return a list of all currently running services.
*/
@utf8InCpp String[] listServices(int dumpPriority);
+
+ /**
+ * Request a callback when a service is registered.
+ */
+ void registerForNotifications(@utf8InCpp String name, IServiceCallback callback);
+
+ /**
+ * Unregisters all requests for notifications for a specific callback.
+ */
+ void unregisterForNotifications(@utf8InCpp String name, IServiceCallback callback);
}