Skip to content

Commit e4d8974

Browse files
committed
Daemonize: additional constructor options: surviveFrameworkRestart, exitListener
surviveFrameworkRestart (recommended: false) controls whether the daemon automatically terminates if the Android framework dies. This is commonly done during testing and by some root apps by calling stop and start in a root shell. Setting this to true doesn't guarantee your daemon will continue to function properly through a framework restart, that depends on the rest of your code - it just won't automatically terminate. exitListener is a callback for when the daemon is terminated through the RootDaemon.exit() call. It IS NOT fired when the daemon is force killed, when you call System.exit(), or an unhandled exception occurs. It IS fired when you manually call RootDaemon.exit(), when the daemon is being replaced by a newer version, and when the daemon terminates due to framework death.
1 parent c6ed651 commit e4d8974

File tree

2 files changed

+63
-16
lines changed

2 files changed

+63
-16
lines changed

librootjavadaemon/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ statements (generally after setting up logging):
6161
// If a daemon of the same version is already running, this
6262
// call will trigger process termination, and will thus
6363
// never return.
64-
RootDaemon.daemonize(BuildConfig.APPLICATION_ID, 0);
64+
RootDaemon.daemonize(BuildConfig.APPLICATION_ID, 0, false, null);
6565
6666
// ...
6767
@@ -113,14 +113,15 @@ them with your own handling.
113113
#### Termination
114114

115115
This daemon process will only terminate when explicitly told to do so,
116-
either through IPC or a Linux kill signal (or if an unhandled
117-
exception occurs). This is why in the example above we add a
116+
either through IPC, a Linux kill signal, if an unhandled
117+
exception occurs, or (if so configured) when the Android framework
118+
dies. This is why in the example above we add a
118119
```terminate()``` method to our IPC interface which calls
119120
```RootDaemon.exit()```. This way you can tell the daemon to
120121
stop running from your non-root app through IPC.
121122

122123
Note that this method will always trigger a ```RemoteException``` on the
123-
non-root end when called.
124+
non-root end when called through IPC.
124125

125126
See the [example project](../librootjavadaemon_example) for further
126127
details.

librootjavadaemon/src/main/java/eu/chainfire/librootjavadaemon/RootDaemon.java

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
* Class with utility function sto launch Java code running as a root as a daemon
3737
*
3838
* @see #getLaunchScript(Context, Class, String, String, String[], String)
39-
* @see #daemonize(String, int)
39+
* @see #daemonize(String, int, boolean, OnExitListener)
4040
* @see #run()
4141
* @see #exit()
4242
*/
@@ -122,6 +122,14 @@ public static List<String> getLaunchScript(Context context, Class<?> clazz, Stri
122122
/** Registered interfaces */
123123
private static final List<RootIPC> ipcs = new ArrayList<RootIPC>();
124124

125+
/** Called before termination */
126+
public interface OnExitListener {
127+
void onExit();
128+
}
129+
130+
/** Stored by daemonize(), called by exit() */
131+
private static volatile OnExitListener onExitListener = null;
132+
125133
/**
126134
* Makes sure there is only a single daemon running with this code parameter. This should
127135
* be one of the first calls in your process to be run as daemon, just after setting up logging
@@ -145,9 +153,11 @@ public static List<String> getLaunchScript(Context context, Class<?> clazz, Stri
145153
*
146154
* @param packageName Package name of the app. BuildConfig.APPLICATION_ID can generally be used.
147155
* @param code User-value, should be unique per daemon process
156+
* @param surviveFrameworkRestart If false (recommended), automatically terminate if the Android framework restarts
157+
* @param exitListener Callback called before the daemon exists either due to a newer daemon version being started or {@link #exit()} being called, or null
148158
*/
149159
@SuppressLint("PrivateApi")
150-
public static void daemonize(String packageName, int code) {
160+
public static void daemonize(String packageName, int code, boolean surviveFrameworkRestart, OnExitListener exitListener) {
151161
String id = packageName + "#" + String.valueOf(code) + "#daemonize";
152162

153163
File apk = new File(System.getenv("CLASSPATH"));
@@ -178,7 +188,34 @@ public static void daemonize(String packageName, int code) {
178188
}
179189

180190
// If we reach this, there either was no previous daemon, or it was outdated
181-
Logger.ep(LOG_PREFIX, "Installing service");
191+
Logger.dp(LOG_PREFIX, "Installing service");
192+
onExitListener = exitListener;
193+
194+
if (!surviveFrameworkRestart) {
195+
/* We link to Android's activity service, which lives in system_server. If the
196+
framework is restarted, for example through stop/start in a root shell, this
197+
service will die and we will be notified.
198+
199+
Obviously when setting surviveFrameworkRestart to true, things you do in
200+
your own code may still cause this process to terminate when the framework
201+
dies, we're just not doing it automatically. */
202+
203+
IBinder activityService = (IBinder)mGetService.invoke(null, Context.ACTIVITY_SERVICE);
204+
if (activityService != null) {
205+
try {
206+
activityService.linkToDeath(new IBinder.DeathRecipient() {
207+
@Override
208+
public void binderDied() {
209+
exit();
210+
}
211+
}, 0);
212+
} catch (RemoteException e) {
213+
// already dead
214+
exit();
215+
}
216+
}
217+
}
218+
182219
mAddService.invoke(null, id, new IRootDaemonIPC.Stub() {
183220
@Override
184221
public String getVersion() {
@@ -211,7 +248,7 @@ public void broadcast() {
211248
* <br>
212249
* Use this method instead of librootjava's 'new RootIPC()' constructor when running as daemon.
213250
*
214-
* @param packageName Package name of the app. Use the same value as used when calling {@link #daemonize(String, int)}.
251+
* @param packageName Package name of the app. Use the same value as used when calling {@link #daemonize(String, int, boolean, OnExitListener)}.
215252
* @param ipc Binder object to wrap and send out
216253
* @param code User-value, should be unique per Binder
217254
* @return RootIPC instance
@@ -239,7 +276,8 @@ public static RootIPC register(String packageName, IBinder ipc, int code) {
239276
* the main() implementation. The initial Binder broadcasts and the connections themselves
240277
* are handled in background threads created by the RootIPC instances created when
241278
* {@link #register(String, IBinder, int)} is called, and re-broadcasting those interfaces
242-
* is done by the internal Binder interface registered by {@link #daemonize(String, int)}.<br>
279+
* is done by the internal Binder interface registered by
280+
* {@link #daemonize(String, int, boolean, OnExitListener)}.<br>
243281
* <br>
244282
* This method never returns!
245283
*/
@@ -263,15 +301,23 @@ public static void run() {
263301
* RemoteException on the other end.
264302
*/
265303
public static void exit() {
304+
/* We do not return from the run() call but immediately exit, so if this method is called
305+
from inside a Binder interface method implementation, the process has died before the
306+
IPC call to terminate completes on the other end. This triggers a RemoteException so the
307+
other end can easily verify this process has terminated. It also prevents a
308+
race-condition between the old service dying and new service registering. Additionally it
309+
saves us from having to use another synchronizer to cope with a termination request
310+
coming in from another daemon launch before run() is actually called. */
311+
266312
Logger.dp(LOG_PREFIX, "Exiting");
267313

268-
/* We do not return from the run() call but immediately exit, so the process has died
269-
before the IPC call to terminate completes on the other end. This triggers a
270-
RemoteException so the other end can easily verify this process has terminated. It
271-
also prevents a race-condition between the old service dying and new service registering.
272-
Additionally it saves us from having to use another synchronizer to cope with a
273-
termination request coming in from another daemon launch before run() is actually
274-
called. */
314+
try {
315+
if (onExitListener != null) {
316+
onExitListener.onExit();
317+
}
318+
} catch (Exception e) {
319+
Logger.ex(e);
320+
}
275321

276322
try {
277323
/* Unlike when using RootJava.getLaunchScript(), RootDaemon.getLaunchScript() does

0 commit comments

Comments
 (0)