Skip to content

Commit

Permalink
Refactor services module
Browse files Browse the repository at this point in the history
  • Loading branch information
topjohnwu committed Sep 13, 2022
1 parent 9f75ae9 commit 401c3c0
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
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 {

Expand Down Expand Up @@ -119,22 +124,15 @@ private static void enforceMainThread() {
}

@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");
}
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;
Expand All @@ -143,8 +141,8 @@ private static void notifyDisconnection(
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() {}

Expand Down Expand Up @@ -239,33 +237,33 @@ private Shell.Task startRootProcess(ComponentName name, String action) {
}

// 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);
Expand All @@ -277,14 +275,14 @@ private Pair<ComponentName, Boolean> bindInternal(
}

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;
Expand All @@ -293,76 +291,98 @@ public Shell.Task createBindTask(Intent intent, Executor executor, ServiceConnec
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; }
}

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()));
}
}

final IRootServiceManager sm;
private class RemoteProcess extends BinderHolder {

final IRootServiceManager mgr;

RemoteProcess(IRootServiceManager s) throws RemoteException {
super(s.asBinder());
sm = s;
mgr = s;
}

@Override
Expand All @@ -372,26 +392,30 @@ protected void onBinderDied() {
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();
}
}
dropConnections(s -> s.host == this);
}
}

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();
}
}
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;
}
}

class ServiceReceiver extends BroadcastReceiver {
private class ServiceReceiver extends BroadcastReceiver {

private final Messenger m;

Expand All @@ -410,10 +434,10 @@ public void onReceive(Context context, Intent intent) {
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;
Expand All @@ -432,20 +456,11 @@ public void onReceive(Context context, Intent intent) {
}
}

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;
}
private interface BindTask {
boolean run();
}

interface BindTask {
boolean run();
private interface Predicate {
boolean eval(RemoteServiceRecord s);
}
}
Loading

0 comments on commit 401c3c0

Please sign in to comment.