mirror of
https://github.com/topjohnwu/libsu.git
synced 2024-11-26 21:40:42 +00:00
Refactor services module
This commit is contained in:
parent
9f75ae9598
commit
401c3c07dc
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user