Window Manager Service 是 Android 的重要服务,各种窗口(Activity, toast, Dialog, 系统 UI 等)都通过这个服务注册和管理。
经常玩系统隐藏 API 的都知道这个实际上是一个名为 window
的 Binder 服务,通过 ServiceManager.getService("window")
就能拿到它的 BinderProxy ,进而转为 android.view.IWindowManager 直接调用 API
但是当你在 app 的 shell 下尝试获取这个服务,却会发现根本无法找到(以下在 Termux 中测试):
对比 activity 服务:
类似的,也可以写一个 java 程序来 getService ,直接用 app_process 在普通用户下跑,得到的同样是 null ,换句话说,似乎普通应用根本没法拿到这个服务。
不过 root 或者 adb 仍然是能访问的,并且常用的 dumpsys 显然就是要获取这个服务才能进行的。
这就非常奇怪了:应用既然无法获取 window 服务,怎么和 WMS 通信呢?
经过一番源码搜索,发现 ServiceManager 中,影响 checkService 的结果取决于两个因素:
- 服务是否允许 isolated
- selinux 检查 (主动调用 selinux_check_access )
Android > 10: frameworks/native/cmds/servicemanager/ServiceManager.cpp
Android <=10: frameworks/native/cmds/servicemanager/service_manager.c
Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
*outBinder = tryGetService(name, false);
// returns ok regardless of result for legacy reasons
return Status::ok();
}
sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
auto ctx = mAccess->getCallingContext();
sp<IBinder> out;
Service* service = nullptr;
if (auto it = mNameToService.find(name); it != mNameToService.end()) {
service = &(it->second);
if (!service->allowIsolated) {
uid_t appid = multiuser_get_app_id(ctx.uid);
bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;
if (isIsolated) {
return nullptr;
}
}
out = service->binder;
}
if (!mAccess->canFind(ctx, name)) {
return nullptr;
}
if (!out && startIfNotFound) {
tryStartService(name);
}
if (out) {
// Setting this guarantee each time we hand out a binder ensures that the client-checking
// loop knows about the event even if the client immediately drops the service
service->guaranteeClient = true;
}
return out;
}
而 WMS 虽然不允许 isolated 进程访问(显然的),但好像和我们的普通应用也没什么关系。
// frameworks/base/services/java/com/android/server/SystemServer.java: startOtherServices
wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
因此问题可能就是 SELinux 导致的。尝试 setenforce 0
,果然在应用用户下就能 check 到了。
实际上每个 Binder 服务都有自己的 selinux type ,叫 ${service name}_service
,在 system/sepolicy/public/service.te
可以看到:
type activity_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
type window_service, system_api_service, system_server_service, service_manager_type;
ams 是 app_api_service ,而 wms 是 system_api_service (attribute)
反编译并搜索 sepolicy 可以发现 app 不能访问后者。(具有 find 权限才能从 servicemanager 得到这个 binder)
// system/sepolicy/private/untrusted_app_all.te 规定了 untrusted app 能访问的 service
allow untrusted_app_all app_api_service:service_manager find;
// system/sepolicy/private/priv_app.te
// priv app 就可以访问
allow priv_app system_api_service:service_manager find;
// system/sepolicy/public/shell.te
// shell 可访问绝大多数的 binder service 并 dump
# allow shell access to services
allow shell servicemanager:service_manager list;
# don't allow shell to access GateKeeper service
# TODO: why is this so broad? Tightening candidate? It needs at list:
# - dumpstate_service (so it can receive dumpstate progress updates)
allow shell {
service_manager_type
-apex_service
-dnsresolver_service
-gatekeeper_service
-incident_service
-installd_service
-iorapd_service
-mdns_service
-netd_service
-system_suspend_control_internal_service
-system_suspend_control_service
-virtual_touchpad_service
-vold_service
-default_android_service
}:service_manager find;
allow shell dumpstate:binder call;
那么 app 怎么获取 wms 的呢?实际上奥秘藏在 ServiceManager.java 和 AMS :
java 层的 ServiceManager 类有一个叫 sCache 的 Map ,存储已知的服务:
// frameworks/base/core/java/android/os/ServiceManager.java
public static void initServiceCache(Map<String, IBinder> cache) {
if (sCache.size() != 0) {
throw new IllegalStateException("setServiceCache may only be called once");
}
sCache.putAll(cache);
}
而当应用进程创建,bindApplication 的时候会调用这个方法,其中包含了从 AMS 来的服务。
// frameworks/base/core/java/android/app/ActivityThread.java:IApplicationThread
@Override
public final void bindApplication(String processName, ApplicationInfo appInfo,
/* ... */ Map services, /* ... */) {
if (services != null) {
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
}
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
// 服务从这里来
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
* lazily setup to make sure the services are running when they're asked for.
*/
private ArrayMap<String, IBinder> getCommonServicesLocked(boolean isolated) {
// Isolated processes won't get this optimization, so that we don't
// violate the rules about which services they have access to.
if (isolated) {
// ...
}
if (mAppBindArgs == null) {
mAppBindArgs = new ArrayMap<>();
// Add common services.
// IMPORTANT: Before adding services here, make sure ephemeral apps can access them too.
// Enable the check in ApplicationThread.bindApplication() to make sure.
addServiceToMap(mAppBindArgs, "package");
addServiceToMap(mAppBindArgs, "permissionmgr");
addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
// ...
其中就有 WMS 。所以,应用得到的 wms binder 其实是 AMS 喂到它的缓存里面的,这就解释了为什么应用虽然没权限从 ServiceManager 拿 wms ,但还是能用 wms 的现象。