mirror of
https://github.com/topjohnwu/libsu.git
synced 2024-11-23 12:09:42 +00:00
Support launching RootServices with a non main shell
This commit is contained in:
parent
a458697a43
commit
88f384b39c
@ -49,9 +49,9 @@ import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -73,6 +73,12 @@ public class RootServiceManager implements Handler.Callback {
|
||||
static final int MSG_STOP = 2;
|
||||
|
||||
private static final String INTENT_EXTRA_KEY = "extra.bundle";
|
||||
private static final String API_27_DEBUG =
|
||||
"-Xrunjdwp:transport=dt_android_adb,suspend=n,server=y " +
|
||||
"-Xcompiler-option --debuggable";
|
||||
private static final String API_28_DEBUG =
|
||||
"-XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y " +
|
||||
"-Xcompiler-option --debuggable";
|
||||
|
||||
private static final int REMOTE_EN_ROUTE = 1 << 0;
|
||||
private static final int DAEMON_EN_ROUTE = 1 << 1;
|
||||
@ -131,7 +137,7 @@ public class RootServiceManager implements Handler.Callback {
|
||||
|
||||
private RootServiceManager() {}
|
||||
|
||||
private Runnable createStartRootProcessTask(ComponentName name, String action) {
|
||||
private Shell.Task startRootProcess(ComponentName name, String action) {
|
||||
Context context = Utils.getContext();
|
||||
boolean[] debug = new boolean[1];
|
||||
if (filterAction == null) {
|
||||
@ -179,32 +185,36 @@ public class RootServiceManager implements Handler.Callback {
|
||||
// Reference of the params to start jdwp:
|
||||
// https://developer.android.com/ndk/guides/wrap-script#debugging_when_using_wrapsh
|
||||
if (Build.VERSION.SDK_INT == 27) {
|
||||
debugParams = "-Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable";
|
||||
debugParams = API_27_DEBUG;
|
||||
} else {
|
||||
debugParams = "-XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable";
|
||||
debugParams = API_28_DEBUG;
|
||||
}
|
||||
}
|
||||
|
||||
String cmd = String.format(Locale.ROOT,
|
||||
"(%s CLASSPATH=%s /proc/%d/exe %s /system/bin --nice-name=%s:root " +
|
||||
"com.topjohnwu.superuser.internal.RootServerMain %s %d %s %s)&",
|
||||
"com.topjohnwu.superuser.internal.RootServerMain %s %d %s %s >/dev/null 2>&1)&",
|
||||
logParams, mainJar, Process.myPid(), debugParams, context.getPackageName(),
|
||||
name.flattenToString().replace("$", "\\$"), // args[0]
|
||||
Process.myUid(), // args[1]
|
||||
filterAction, // args[2]
|
||||
action); // args[3]
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
// Dump main.jar as trampoline
|
||||
try (InputStream in = context.getResources().getAssets().open("main.jar");
|
||||
OutputStream out = new FileOutputStream(mainJar)) {
|
||||
Utils.pump(in, out);
|
||||
}
|
||||
Shell.su(cmd).exec();
|
||||
} catch (IOException e) {
|
||||
Utils.err(TAG, e);
|
||||
return (stdin, stdout, stderr) -> {
|
||||
// Dump main.jar as trampoline
|
||||
try (InputStream in = context.getResources().getAssets().open("main.jar");
|
||||
OutputStream out = new FileOutputStream(mainJar)) {
|
||||
Utils.pump(in, out);
|
||||
}
|
||||
Utils.log(TAG, cmd);
|
||||
// Write command to stdin
|
||||
byte[] bytes = cmd.getBytes(StandardCharsets.UTF_8);
|
||||
stdin.write(bytes);
|
||||
stdin.write('\n');
|
||||
stdin.flush();
|
||||
// Since all output for the command is redirected to /dev/null and
|
||||
// the command runs in the background, we don't need to wait and
|
||||
// can just return.
|
||||
};
|
||||
}
|
||||
|
||||
@ -246,7 +256,7 @@ public class RootServiceManager implements Handler.Callback {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Runnable createBindTask(Intent intent, Executor executor, ServiceConnection conn) {
|
||||
public Shell.Task createBindTask(Intent intent, Executor executor, ServiceConnection conn) {
|
||||
Pair<ComponentName, Boolean> key = bindInternal(intent, executor, conn);
|
||||
if (key != null) {
|
||||
pendingTasks.add(() -> bindInternal(intent, executor, conn) == null);
|
||||
@ -254,7 +264,7 @@ public class RootServiceManager implements Handler.Callback {
|
||||
String action = key.second ? CMDLINE_START_DAEMON : CMDLINE_START_SERVICE;
|
||||
if ((flags & mask) == 0) {
|
||||
flags |= mask;
|
||||
return createStartRootProcessTask(key.first, action);
|
||||
return startRootProcess(key.first, action);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -296,7 +306,7 @@ public class RootServiceManager implements Handler.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(Intent intent) {
|
||||
public Shell.Task createStopTask(Intent intent) {
|
||||
enforceMainThread();
|
||||
|
||||
Pair<ComponentName, Boolean> key = enforceIntent(intent);
|
||||
@ -304,10 +314,9 @@ public class RootServiceManager implements Handler.Callback {
|
||||
if (p == null) {
|
||||
if (key.second) {
|
||||
// Start a new root process to stop daemon
|
||||
Runnable r = createStartRootProcessTask(key.first, CMDLINE_STOP_SERVICE);
|
||||
Shell.EXECUTOR.execute(r);
|
||||
return startRootProcess(key.first, CMDLINE_STOP_SERVICE);
|
||||
}
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
stopInternal(key);
|
||||
@ -316,6 +325,7 @@ public class RootServiceManager implements Handler.Callback {
|
||||
} catch (RemoteException e) {
|
||||
Utils.err(TAG, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,6 +36,7 @@ import com.topjohnwu.superuser.internal.RootServiceServer;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
import com.topjohnwu.superuser.internal.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@ -85,13 +86,13 @@ public abstract class RootService extends ContextWrapper {
|
||||
* <p>
|
||||
* Add this category in the intents passed to {@link #bind(Intent, ServiceConnection)},
|
||||
* {@link #bind(Intent, Executor, ServiceConnection)}, or
|
||||
* {@link #createBindTask(Intent, Executor, ServiceConnection)}
|
||||
* {@link #bindOrTask(Intent, Executor, ServiceConnection)}
|
||||
* to have the service launched in "Daemon Mode".
|
||||
*/
|
||||
public static final String CATEGORY_DAEMON_MODE = "com.topjohnwu.superuser.DAEMON_MODE";
|
||||
|
||||
/**
|
||||
* Connect to a root service, creating it if needed.
|
||||
* Bind to a root service, launching a new root process if needed.
|
||||
* @param intent identifies the service to connect to.
|
||||
* @param executor callbacks on ServiceConnection will be called on this executor.
|
||||
* @param conn receives information as the service is started and stopped.
|
||||
@ -102,14 +103,14 @@ public abstract class RootService extends ContextWrapper {
|
||||
@NonNull Intent intent,
|
||||
@NonNull Executor executor,
|
||||
@NonNull ServiceConnection conn) {
|
||||
Runnable r = createBindTask(intent, executor, conn);
|
||||
if (r != null) {
|
||||
Shell.EXECUTOR.execute(r);
|
||||
Shell.Task task = bindOrTask(intent, executor, conn);
|
||||
if (task != null) {
|
||||
Shell.EXECUTOR.execute(asRunnable(task));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a root service, creating it if needed.
|
||||
* Bind to a root service, launching a new root process if needed.
|
||||
* @param intent identifies the service to connect to.
|
||||
* @param conn receives information as the service is started and stopped.
|
||||
* @see Context#bindService(Intent, ServiceConnection, int)
|
||||
@ -120,20 +121,19 @@ public abstract class RootService extends ContextWrapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a root service, creating it if needed.
|
||||
* Bind to a root service, creating a task to launch a new root process if needed.
|
||||
* <p>
|
||||
* This method is useful if you want to precisely manage which background thread and the
|
||||
* timing to do I/O operations and execute root commands for creating a new root process.
|
||||
* <p>
|
||||
* Binding will NOT happen if the developer does not run the returned {@link Runnable}.
|
||||
* @return a {@link Runnable} instance on which a new root process will be launched upon
|
||||
* calling {@link Runnable#run()}. If there is no need for creating a new root process,
|
||||
* {@code null} is returned.
|
||||
* If the application is already connected to a root process, binding will happen immediately
|
||||
* and this method will return {@code null}. Or else this method returns a {@link Shell.Task}
|
||||
* that has to be executed to launch the root process. Binding will only happen after the
|
||||
* developer has executed the returned task with {@link Shell#execTask(Shell.Task)}.
|
||||
* @return the task to launch a root process. If there is no need to launch a new root
|
||||
* process, {@code null} is returned.
|
||||
* @see #bind(Intent, Executor, ServiceConnection)
|
||||
*/
|
||||
@MainThread
|
||||
@Nullable
|
||||
public static Runnable createBindTask(
|
||||
public static Shell.Task bindOrTask(
|
||||
@NonNull Intent intent,
|
||||
@NonNull Executor executor,
|
||||
@NonNull ServiceConnection conn) {
|
||||
@ -141,8 +141,9 @@ public abstract class RootService extends ContextWrapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from a root service.
|
||||
* @param conn the connection interface previously supplied to {@link #bind(Intent, ServiceConnection)}
|
||||
* Unbind from a root service.
|
||||
* @param conn the connection interface previously supplied to
|
||||
* {@link #bind(Intent, ServiceConnection)}
|
||||
* @see Context#unbindService(ServiceConnection)
|
||||
*/
|
||||
@MainThread
|
||||
@ -151,18 +152,43 @@ public abstract class RootService extends ContextWrapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Force stop a root service.
|
||||
* Force stop a root service, launching a new root process if needed.
|
||||
* <p>
|
||||
* Since root services are bound only, unlike {@link Context#stopService(Intent)}, this
|
||||
* method is used to immediately stop a root service regardless of its state.
|
||||
* Only use this method to stop a daemon root service; for normal root services please use
|
||||
* {@link #unbind(ServiceConnection)} instead as this method could potentially end up starting
|
||||
* an additional root process to make sure daemon services are stopped.
|
||||
* This method is used to immediately stop a root service regardless of its state.
|
||||
* ONLY use this method to stop a daemon root service; for normal root services, please use
|
||||
* {@link #unbind(ServiceConnection)} instead as this method has to potentially launch
|
||||
* an additional root process to ensure daemon services are stopped.
|
||||
* @param intent identifies the service to stop.
|
||||
*/
|
||||
@MainThread
|
||||
public static void stop(@NonNull Intent intent) {
|
||||
RootServiceManager.getInstance().stop(intent);
|
||||
Shell.Task task = stopOrTask(intent);
|
||||
if (task != null) {
|
||||
Shell.EXECUTOR.execute(asRunnable(task));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force stop a root service, creating a task to launch a new root process if needed.
|
||||
* <p>
|
||||
* This method returns a {@link Shell.Task} that has to be executed to launch a
|
||||
* root process if necessary, or else {@code null} will be returned.
|
||||
* @see #stop(Intent)
|
||||
*/
|
||||
@MainThread
|
||||
@Nullable
|
||||
public static Shell.Task stopOrTask(@NonNull Intent intent) {
|
||||
return RootServiceManager.getInstance().createStopTask(intent);
|
||||
}
|
||||
|
||||
private static Runnable asRunnable(Shell.Task task) {
|
||||
return () -> {
|
||||
try {
|
||||
Shell.getShell().execTask(task);
|
||||
} catch (IOException e) {
|
||||
Utils.err(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public RootService() {
|
||||
@ -180,7 +206,7 @@ public abstract class RootService extends ContextWrapper {
|
||||
/**
|
||||
* Return the component name that will be used for service lookup.
|
||||
* <p>
|
||||
* Overriding this method is only for very unusual situations when a different
|
||||
* Overriding this method is only for very unusual use cases when a different
|
||||
* component name other than the actual class name is desired.
|
||||
* @return the desired component name
|
||||
*/
|
||||
@ -222,11 +248,25 @@ public abstract class RootService extends ContextWrapper {
|
||||
public void onDestroy() {}
|
||||
|
||||
/**
|
||||
* Force stop this root service process.
|
||||
* <p>
|
||||
* This is the same as calling {@link #stop(Intent)} for this particular service.
|
||||
* Force stop this root service.
|
||||
*/
|
||||
public final void stopSelf() {
|
||||
RootServiceServer.getInstance(this).selfStop(getComponentName());
|
||||
}
|
||||
|
||||
// Deprecated APIs
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #bindOrTask(Intent, Executor, ServiceConnection)}
|
||||
*/
|
||||
@MainThread
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public static Runnable createBindTask(
|
||||
@NonNull Intent intent,
|
||||
@NonNull Executor executor,
|
||||
@NonNull ServiceConnection conn) {
|
||||
Shell.Task task = bindOrTask(intent, executor, conn);
|
||||
return task == null ? null : asRunnable(task);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user