Refactor services module

This commit is contained in:
topjohnwu 2022-09-13 03:51:44 -07:00
parent 9f75ae9598
commit 401c3c07dc
2 changed files with 127 additions and 110 deletions

View File

@ -61,6 +61,11 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Runs in the non-root (client) process.
*
* Starts the root process and manages connections with the remote process.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RootServiceManager implements Handler.Callback {
@ -119,7 +124,7 @@ public class RootServiceManager implements Handler.Callback {
}
@NonNull
private static Pair<ComponentName, Boolean> enforceIntent(Intent intent) {
private static ServiceKey parseIntent(Intent intent) {
ComponentName name = intent.getComponent();
if (name == null) {
throw new IllegalArgumentException("The intent does not have a component set");
@ -127,14 +132,7 @@ public class RootServiceManager implements Handler.Callback {
if (!name.getPackageName().equals(Utils.getContext().getPackageName())) {
throw new IllegalArgumentException("RootServices outside of the app are not supported");
}
return new Pair<>(name, intent.hasCategory(CATEGORY_DAEMON_MODE));
}
private static void notifyDisconnection(
Map.Entry<ServiceConnection, Pair<RemoteService, Executor>> e) {
ServiceConnection c = e.getKey();
ComponentName name = e.getValue().first.key.first;
e.getValue().second.execute(() -> c.onServiceDisconnected(name));
return new ServiceKey(name, intent.hasCategory(CATEGORY_DAEMON_MODE));
}
private RemoteProcess mRemote;
@ -143,8 +141,8 @@ public class RootServiceManager implements Handler.Callback {
private int flags = 0;
private final List<BindTask> pendingTasks = new ArrayList<>();
private final Map<Pair<ComponentName, Boolean>, RemoteService> services = new ArrayMap<>();
private final Map<ServiceConnection, Pair<RemoteService, Executor>> connections = new ArrayMap<>();
private final Map<ServiceKey, RemoteServiceRecord> services = new ArrayMap<>();
private final Map<ServiceConnection, ConnectionRecord> connections = new ArrayMap<>();
private RootServiceManager() {}
@ -239,33 +237,33 @@ public class RootServiceManager implements Handler.Callback {
}
// Returns null if binding is done synchronously, or else return key
private Pair<ComponentName, Boolean> bindInternal(
Intent intent, Executor executor, ServiceConnection conn) {
private ServiceKey bindInternal(Intent intent, Executor executor, ServiceConnection conn) {
enforceMainThread();
// Local cache
Pair<ComponentName, Boolean> key = enforceIntent(intent);
RemoteService s = services.get(key);
ServiceKey key = parseIntent(intent);
RemoteServiceRecord s = services.get(key);
if (s != null) {
connections.put(conn, new Pair<>(s, executor));
connections.put(conn, new ConnectionRecord(s, executor));
s.refCount++;
executor.execute(() -> conn.onServiceConnected(key.first, s.binder));
IBinder binder = s.binder;
executor.execute(() -> conn.onServiceConnected(key.getName(), binder));
return null;
}
RemoteProcess p = key.second ? mDaemon : mRemote;
RemoteProcess p = key.isDaemon() ? mDaemon : mRemote;
if (p == null)
return key;
try {
IBinder binder = p.sm.bind(intent);
IBinder binder = p.mgr.bind(intent);
if (binder != null) {
RemoteService r = new RemoteService(key, binder, p);
connections.put(conn, new Pair<>(r, executor));
services.put(key, r);
executor.execute(() -> conn.onServiceConnected(key.first, binder));
s = new RemoteServiceRecord(key, binder, p);
connections.put(conn, new ConnectionRecord(s, executor));
services.put(key, s);
executor.execute(() -> conn.onServiceConnected(key.getName(), binder));
} else if (Build.VERSION.SDK_INT >= 28) {
executor.execute(() -> conn.onNullBinding(key.first));
executor.execute(() -> conn.onNullBinding(key.getName()));
}
} catch (RemoteException e) {
Utils.err(TAG, e);
@ -277,14 +275,14 @@ public class RootServiceManager implements Handler.Callback {
}
public Shell.Task createBindTask(Intent intent, Executor executor, ServiceConnection conn) {
Pair<ComponentName, Boolean> key = bindInternal(intent, executor, conn);
ServiceKey key = bindInternal(intent, executor, conn);
if (key != null) {
pendingTasks.add(() -> bindInternal(intent, executor, conn) == null);
int mask = key.second ? DAEMON_EN_ROUTE : REMOTE_EN_ROUTE;
String action = key.second ? CMDLINE_START_DAEMON : CMDLINE_START_SERVICE;
int mask = key.isDaemon() ? DAEMON_EN_ROUTE : REMOTE_EN_ROUTE;
if ((flags & mask) == 0) {
flags |= mask;
return startRootProcess(key.first, action);
String action = key.isDaemon() ? CMDLINE_START_DAEMON : CMDLINE_START_SERVICE;
return startRootProcess(key.getName(), action);
}
}
return null;
@ -293,76 +291,98 @@ public class RootServiceManager implements Handler.Callback {
public void unbind(@NonNull ServiceConnection conn) {
enforceMainThread();
Pair<RemoteService, Executor> p = connections.remove(conn);
if (p != null) {
p.first.refCount--;
p.second.execute(() -> conn.onServiceDisconnected(p.first.key.first));
if (p.first.refCount == 0) {
ConnectionRecord r = connections.remove(conn);
if (r != null) {
RemoteServiceRecord s = r.getService();
s.refCount--;
if (s.refCount == 0) {
// Actually close the service
services.remove(p.first.key);
services.remove(s.key);
try {
p.first.host.sm.unbind(p.first.key.first);
s.host.mgr.unbind(s.key.getName());
} catch (RemoteException e) {
Utils.err(TAG, e);
}
}
r.disconnect(conn);
}
}
private void stopInternal(Pair<ComponentName, Boolean> key) {
RemoteService s = services.remove(key);
if (s == null)
return;
// Notify all connections
Iterator<Map.Entry<ServiceConnection, Pair<RemoteService, Executor>>> it =
private void dropConnections(Predicate predicate) {
Iterator<Map.Entry<ServiceConnection, ConnectionRecord>> it =
connections.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<ServiceConnection, Pair<RemoteService, Executor>> e = it.next();
if (e.getValue().first.equals(s)) {
notifyDisconnection(e);
Map.Entry<ServiceConnection, ConnectionRecord> e = it.next();
ConnectionRecord r = e.getValue();
if (predicate.eval(r.getService())) {
r.disconnect(e.getKey());
it.remove();
}
}
}
private void onServiceStopped(ServiceKey key) {
RemoteServiceRecord s = services.remove(key);
if (s != null)
dropConnections(s::equals);
}
public Shell.Task createStopTask(Intent intent) {
enforceMainThread();
Pair<ComponentName, Boolean> key = enforceIntent(intent);
RemoteProcess p = key.second ? mDaemon : mRemote;
ServiceKey key = parseIntent(intent);
RemoteProcess p = key.isDaemon() ? mDaemon : mRemote;
if (p == null) {
if (key.second) {
if (key.isDaemon()) {
// Start a new root process to stop daemon
return startRootProcess(key.first, CMDLINE_STOP_SERVICE);
return startRootProcess(key.getName(), CMDLINE_STOP_SERVICE);
}
return null;
}
stopInternal(key);
try {
p.sm.stop(key.first, -1);
p.mgr.stop(key.getName(), -1);
} catch (RemoteException e) {
Utils.err(TAG, e);
}
onServiceStopped(key);
return null;
}
@Override
public boolean handleMessage(@NonNull Message msg) {
if (msg.what == MSG_STOP) {
stopInternal(new Pair<>((ComponentName) msg.obj, msg.arg1 != 0));
onServiceStopped(new ServiceKey((ComponentName) msg.obj, msg.arg1 != 0));
}
return false;
}
class RemoteProcess extends BinderHolder {
private static class ServiceKey extends Pair<ComponentName, Boolean> {
ServiceKey(ComponentName name, boolean isDaemon) {
super(name, isDaemon);
}
ComponentName getName() { return first; }
boolean isDaemon() { return second; }
}
final IRootServiceManager sm;
private static class ConnectionRecord extends Pair<RemoteServiceRecord, Executor> {
ConnectionRecord(RemoteServiceRecord s, Executor e) {
super(s, e);
}
RemoteServiceRecord getService() { return first; }
void disconnect(ServiceConnection conn) {
second.execute(() -> conn.onServiceDisconnected(first.key.getName()));
}
}
private class RemoteProcess extends BinderHolder {
final IRootServiceManager mgr;
RemoteProcess(IRootServiceManager s) throws RemoteException {
super(s.asBinder());
sm = s;
mgr = s;
}
@Override
@ -372,26 +392,30 @@ public class RootServiceManager implements Handler.Callback {
if (mDaemon == this)
mDaemon = null;
Iterator<RemoteService> sit = services.values().iterator();
Iterator<RemoteServiceRecord> sit = services.values().iterator();
while (sit.hasNext()) {
if (sit.next().host == this) {
sit.remove();
}
}
Iterator<Map.Entry<ServiceConnection, Pair<RemoteService, Executor>>> it =
connections.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<ServiceConnection, Pair<RemoteService, Executor>> e = it.next();
if (e.getValue().first.host == this) {
notifyDisconnection(e);
it.remove();
}
}
dropConnections(s -> s.host == this);
}
}
class ServiceReceiver extends BroadcastReceiver {
private static class RemoteServiceRecord {
final ServiceKey key;
final IBinder binder;
final RemoteProcess host;
int refCount = 1;
RemoteServiceRecord(ServiceKey key, IBinder binder, RemoteProcess host) {
this.key = key;
this.binder = binder;
this.host = host;
}
}
private class ServiceReceiver extends BroadcastReceiver {
private final Messenger m;
@ -410,10 +434,10 @@ public class RootServiceManager implements Handler.Callback {
if (binder == null)
return;
IRootServiceManager sm = IRootServiceManager.Stub.asInterface(binder);
IRootServiceManager mgr = IRootServiceManager.Stub.asInterface(binder);
try {
sm.connect(m.getBinder());
RemoteProcess p = new RemoteProcess(sm);
mgr.connect(m.getBinder());
RemoteProcess p = new RemoteProcess(mgr);
if (intent.getBooleanExtra(INTENT_DAEMON_KEY, false)) {
mDaemon = p;
flags &= ~DAEMON_EN_ROUTE;
@ -432,20 +456,11 @@ public class RootServiceManager implements Handler.Callback {
}
}
static class RemoteService {
final Pair<ComponentName, Boolean> key;
final IBinder binder;
final RemoteProcess host;
int refCount = 1;
RemoteService(Pair<ComponentName, Boolean> key, IBinder binder, RemoteProcess host) {
this.key = key;
this.binder = binder;
this.host = host;
}
}
interface BindTask {
private interface BindTask {
boolean run();
}
private interface Predicate {
boolean eval(RemoteServiceRecord s);
}
}

View File

@ -52,6 +52,11 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* Runs in the root (server) process.
*
* Manages the lifecycle of RootServices and the root process.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RootServiceServer extends IRootServiceManager.Stub implements Runnable {
@ -66,7 +71,7 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
@SuppressWarnings("FieldCanBeLocal")
private final FileObserver observer; /* A strong reference is required */
private final Map<ComponentName, ServiceContainer> activeServices = new ArrayMap<>();
private final Map<ComponentName, ServiceRecord> services = new ArrayMap<>();
private final SparseArray<ClientProcess> clients = new SparseArray<>();
private final boolean isDaemon;
@ -128,12 +133,10 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
}
private void connectInternal(int uid, IBinder binder) {
ClientProcess c = clients.get(uid);
if (c != null)
if (clients.get(uid) != null)
return;
try {
c = new ClientProcess(binder, uid);
clients.put(c.mUid, c);
clients.put(uid, new ClientProcess(binder, uid));
UiThreadHandler.handler.removeCallbacks(this);
} catch (RemoteException e) {
Utils.err(TAG, e);
@ -197,8 +200,8 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
}
public void register(RootService service) {
ServiceContainer s = new ServiceContainer(service);
activeServices.put(service.getComponentName(), s);
ServiceRecord s = new ServiceRecord(service);
services.put(service.getComponentName(), s);
}
private IBinder bindInternal(int uid, Intent intent) throws Exception {
@ -208,7 +211,7 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
ComponentName name = intent.getComponent();
ServiceContainer s = activeServices.get(name);
ServiceRecord s = services.get(name);
if (s == null) {
Class<?> clz = context.getClassLoader().loadClass(name.getClassName());
Constructor<?> ctor = clz.getDeclaredConstructor();
@ -216,7 +219,7 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
HiddenAPIs.attachBaseContext(ctor.newInstance(), context);
// RootService should be registered after attachBaseContext
s = activeServices.get(name);
s = services.get(name);
if (s == null) {
return null;
}
@ -236,7 +239,7 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
return s.binder;
}
private void unbindInternal(ServiceContainer s, int uid, Runnable onDestroy) {
private void unbindInternal(ServiceRecord s, int uid, Runnable onDestroy) {
boolean hadUsers = !s.users.isEmpty();
s.users.remove(uid);
if (uid < 0 || s.users.isEmpty()) {
@ -266,23 +269,23 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
}
}
}
if (activeServices.isEmpty()) {
if (services.isEmpty()) {
exit("No active services");
}
}
private void unbindService(int uid, ComponentName name) {
ServiceContainer s = activeServices.get(name);
ServiceRecord s = services.get(name);
if (s == null)
return;
unbindInternal(s, uid, () -> activeServices.remove(name));
unbindInternal(s, uid, () -> services.remove(name));
}
private void unbindServices(int uid) {
Iterator<Map.Entry<ComponentName, ServiceContainer>> it =
activeServices.entrySet().iterator();
Iterator<Map.Entry<ComponentName, ServiceRecord>> it =
services.entrySet().iterator();
while (it.hasNext()) {
ServiceContainer s = it.next().getValue();
ServiceRecord s = it.next().getValue();
if (uid < 0) {
// App is updated/deleted, all clients will get killed anyways,
// no need to notify anyone.
@ -297,7 +300,7 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
System.exit(0);
}
class AppObserver extends FileObserver {
private class AppObserver extends FileObserver {
private final String name;
@ -316,34 +319,33 @@ public class RootServiceServer extends IRootServiceManager.Stub implements Runna
}
}
class ClientProcess extends BinderHolder {
private class ClientProcess extends BinderHolder {
final Messenger m;
final int mUid;
final int uid;
ClientProcess(IBinder b, int uid) throws RemoteException {
super(b);
m = new Messenger(b);
mUid = uid;
this.uid = uid;
}
@Override
protected void onBinderDied() {
Utils.log(TAG, "Client process terminated, uid=" + mUid);
clients.remove(mUid);
unbindServices(mUid);
Utils.log(TAG, "Client process terminated, uid=" + uid);
clients.remove(uid);
unbindServices(uid);
}
}
static class ServiceContainer {
private static class ServiceRecord {
final RootService service;
final Set<Integer> users = newArraySet();
Intent intent;
IBinder binder;
boolean rebind;
ServiceContainer(RootService s) {
ServiceRecord(RootService s) {
service = s;
}
}