diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml
index c3a6ef34..d6693e56 100644
--- a/src/android/AndroidManifest.xml
+++ b/src/android/AndroidManifest.xml
@@ -20,5 +20,10 @@
+
diff --git a/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java b/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java
index 1c7b1cb4..a117460f 100644
--- a/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java
+++ b/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java
@@ -3,15 +3,18 @@ package com.unprompted.tildefriends;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
+import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
-import android.os.CountDownTimer;
import android.os.Environment;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.StrictMode;
-import android.os.SystemClock;
-import android.os.strictmode.Violation;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
@@ -29,19 +32,13 @@ import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
-import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.OutputStream;
import java.io.FileReader;
-import java.io.InputStream;
-import java.lang.Process;
-import java.lang.Thread;
+import java.io.OutputStream;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
@@ -51,12 +48,13 @@ import java.nio.file.WatchService;
import java.util.concurrent.TimeUnit;
public class TildeFriendsActivity extends Activity {
+ static TildeFriendsActivity s_activity;
TildeFriendsWebView web_view;
String base_url;
String port_file_path;
- Process process;
Thread thread;
Thread server_thread;
+ ServiceConnection service_connection;
private ValueCallback upload_message;
private final static int FILECHOOSER_RESULT = 1;
@@ -68,10 +66,12 @@ public class TildeFriendsActivity extends Activity {
Log.w("tildefriends", "system.loadLibrary() completed.");
}
- static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path);
+ public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path);
+ public static native int tf_sandbox_main(int pipe_fd);
@Override
protected void onCreate(Bundle savedInstanceState) {
+ s_activity = this;
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.penaltyLog()
@@ -274,13 +274,8 @@ public class TildeFriendsActivity extends Activity {
@Override
protected void onDestroy()
{
- if (process != null) {
- Log.w("tildefriends", "Killing process.");
- process.destroyForcibly();
- Log.w("tildefriends", "Process killed.");
- process = null;
- }
super.onDestroy();
+ s_activity = null;
}
@Override
@@ -382,4 +377,49 @@ public class TildeFriendsActivity extends Activity {
web_view.setVisibility(View.VISIBLE);
text_view.setVisibility(View.GONE);
}
+
+ public static void start_sandbox(int pipe_fd) {
+ Log.w("tildefriends", "starting service with fd: " + pipe_fd);
+ Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
+ s_activity.service_connection = new ServiceConnection() {
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.w("tildefriends", "onBindingDied");
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.w("tildefriends", "onNullBinding");
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ Log.w("tildefriends", "onServiceConnected");
+ Parcel data = Parcel.obtain();
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(pipe_fd);
+ data.writeParcelable(pfd, 0);
+ try {
+ binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY);
+ } catch (RemoteException e) {
+ Log.w("tildefriends", "RemoteException");
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.w("tildefriends", "onServiceDisconnected");
+ }
+ };
+ s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE);
+ }
+
+ public static void stop_sandbox() {
+ Log.w("tildefriends", "stop_sandbox");
+ if (s_activity.service_connection != null) {
+ s_activity.unbindService(s_activity.service_connection);
+ s_activity.service_connection = null;
+ }
+ }
}
diff --git a/src/android/com/unprompted/tildefriends/TildeFriendsSandboxService.java b/src/android/com/unprompted/tildefriends/TildeFriendsSandboxService.java
new file mode 100644
index 00000000..4626f705
--- /dev/null
+++ b/src/android/com/unprompted/tildefriends/TildeFriendsSandboxService.java
@@ -0,0 +1,59 @@
+package com.unprompted.tildefriends;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+public class TildeFriendsSandboxService extends Service {
+ public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION;
+
+ Thread thread;
+
+ public int onStartCommand(Intent intent, int flags, int start_id) {
+ Log.w("tildefriends", "TildeFriendsSandboxService: onStartCommand");
+ return super.onStartCommand(intent, flags, start_id);
+ }
+
+ public void onDestroy() {
+ Log.w("tildefriends", "TildeFriendsSandboxService: onDestroy");
+ super.onDestroy();
+ }
+
+ private void start_thread(int pipe_fd) {
+ thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Log.w("tildefriends", "Calling tf_sandbox_main.");
+ int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd);
+ Log.w("tildefriends", "tf_sandbox_main returned " + result + ".");
+ }
+ });
+ thread.start();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
+ if (code == START_CALL) {
+ ParcelFileDescriptor pfd = data.readParcelable(ParcelFileDescriptor.class.getClassLoader(), ParcelFileDescriptor.class);
+ if (pfd != null) {
+ Log.w("tildefriends", "fd is " + pfd.getFd());
+ start_thread(pfd.detachFd());
+ try {
+ pfd.close();
+ } catch (java.io.IOException e) {
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+}
diff --git a/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java b/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java
index e56bee09..340de874 100644
--- a/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java
+++ b/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java
@@ -2,7 +2,6 @@ package com.unprompted.tildefriends;
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
public class TildeFriendsWebView extends android.webkit.WebView {
boolean overscrolledY = false;
diff --git a/src/main.c b/src/main.c
index e834383b..37b203d1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -607,14 +607,16 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
static int _tf_command_sandbox(const char* file, int argc, char* argv[])
{
bool show_usage = false;
+ int fd = STDIN_FILENO;
while (!show_usage)
{
static const struct option k_options[] = {
+ { "fd", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
- int c = getopt_long(argc, argv, "h", k_options, NULL);
+ int c = getopt_long(argc, argv, "f:h", k_options, NULL);
if (c == -1)
{
break;
@@ -626,6 +628,10 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
default:
show_usage = true;
break;
+ case 'f':
+ tf_printf("got -f %s\n", optarg);
+ fd = atoi(optarg);
+ break;
}
}
@@ -634,6 +640,7 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
tf_printf("\nUsage: %s sandbox [options]\n\n", file);
tf_printf("options:\n");
tf_printf(" -h, --help Show this usage information.\n");
+ tf_printf(" -f, --fd File descriptor with which to communicate with parent process.\n");
return EXIT_FAILURE;
}
@@ -641,7 +648,7 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
prctl(PR_SET_PDEATHSIG, SIGHUP);
#endif
tf_task_t* task = tf_task_create();
- tf_task_configure_from_fd(task, STDIN_FILENO);
+ tf_task_configure_from_fd(task, fd);
_shed_privileges();
/* The caller will trigger tf_task_activate with a message. */
tf_task_run(task);
@@ -689,10 +696,6 @@ static void _startup(int argc, char* argv[])
}
}
-#if defined(__ANDROID__)
- setenv("UV_USE_IO_URING", "0", 1);
-#endif
-
tf_mem_startup(tracking);
g_backtrace_state = backtrace_create_state(argv[0], 0, _backtrace_error, NULL);
@@ -720,7 +723,7 @@ static void _startup(int argc, char* argv[])
{
if (
#if !defined(_WIN32)
- signal(SIGSYS, _error_handler) == SIG_ERR ||
+ signal(SIGSYS, _error_handler) == SIG_ERR || signal(SIGABRT, _error_handler) == SIG_ERR ||
#endif
signal(SIGSEGV, _error_handler) == SIG_ERR)
{
@@ -730,8 +733,28 @@ static void _startup(int argc, char* argv[])
}
#if defined(__ANDROID__)
+static JNIEnv* s_jni_env;
+
+static void _tf_service_start(int pipe_fd)
+{
+ tf_printf("_tf_service_start\n");
+ jclass c = (*s_jni_env)->FindClass(s_jni_env, "com/unprompted/tildefriends/TildeFriendsActivity");
+ jmethodID start_sandbox = (*s_jni_env)->GetStaticMethodID(s_jni_env, c, "start_sandbox", "(I)V");
+ (*s_jni_env)->CallStaticVoidMethod(s_jni_env, c, start_sandbox, pipe_fd);
+}
+
+static void _tf_service_stop()
+{
+ tf_printf("_tf_service_stop\n");
+ jclass c = (*s_jni_env)->FindClass(s_jni_env, "com/unprompted/tildefriends/TildeFriendsActivity");
+ jmethodID stop_sandbox = (*s_jni_env)->GetStaticMethodID(s_jni_env, c, "stop_sandbox", "()V");
+ (*s_jni_env)->CallStaticVoidMethod(s_jni_env, c, stop_sandbox);
+}
+
static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir, jstring apk_path, jstring out_port_file_path)
{
+ s_jni_env = env;
+
tf_printf("This is tf_server_main main.\n");
_startup(0, (char*[]) { NULL });
tf_printf("That was startup.\n");
@@ -754,24 +777,51 @@ static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir,
char* port_file_arg = alloca(port_file_arg_length);
snprintf(port_file_arg, port_file_arg_length, "out_http_port_file=%s", out_port_file);
- const char* args[] =
- {
+ const char* args[] = {
+ "run",
"-z",
apk,
"-a",
port_file_arg,
"-p",
"0",
- "-o", /* HACK! FIXME! */
};
+ tf_task_set_android_service_callbacks(_tf_service_start, _tf_service_stop);
result = _tf_command_run(apk, _countof(args), (char**)args);
+ tf_task_set_android_service_callbacks(NULL, NULL);
(*env)->ReleaseStringUTFChars(env, files_dir, files);
(*env)->ReleaseStringUTFChars(env, apk_path, apk);
(*env)->ReleaseStringUTFChars(env, out_port_file_path, out_port_file);
tf_printf("tf_server_main finished with %d.", result);
+
+ s_jni_env = NULL;
+ return result;
+}
+
+static jint _tf_sandbox_main(JNIEnv* env, jobject this_object, int pipe_fd)
+{
+ s_jni_env = env;
+
+ tf_printf("This is tf_sandbox_main main (fd=%d).\n", pipe_fd);
+ _startup(0, (char*[]) { NULL });
+ tf_printf("That was startup.\n");
+
+ char fd[32] = { 0 };
+ snprintf(fd, sizeof(fd), "%d", pipe_fd);
+ const char* args[] = {
+ "sandbox",
+ "-f",
+ fd,
+ };
+
+ int result = _tf_command_sandbox(NULL, _countof(args), (char**)args);
+
+ tf_printf("tf_sandbox_main finished with %d.", result);
+
+ s_jni_env = NULL;
return result;
}
@@ -796,6 +846,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
tf_printf("Registering method.\n");
static const JNINativeMethod methods[] = {
{ "tf_server_main", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", _tf_server_main },
+ { "tf_sandbox_main", "(I)I", _tf_sandbox_main },
};
int result = (*env)->RegisterNatives(env, c, methods, (int)_countof(methods));
if (result != JNI_OK)
diff --git a/src/task.c b/src/task.c
index dbf32710..b0d54f95 100644
--- a/src/task.c
+++ b/src/task.c
@@ -50,6 +50,9 @@
static JSClassID _import_class_id;
static int _count;
+static tf_android_start_service_t* s_android_start_service;
+static tf_android_stop_service_t* s_android_stop_service;
+
extern struct backtrace_state* g_backtrace_state;
typedef struct _export_record_t export_record_t;
@@ -2160,3 +2163,19 @@ static JSValue _tf_task_pokeSandbox(JSContext* context, JSValueConst this_val, i
#endif
return JS_NewInt32(context, WEXITSTATUS(result));
}
+
+void tf_task_set_android_service_callbacks(tf_android_start_service_t* start_service, tf_android_stop_service_t* stop_service)
+{
+ s_android_start_service = start_service;
+ s_android_stop_service = stop_service;
+}
+
+tf_android_start_service_t* tf_task_get_android_start_service()
+{
+ return s_android_start_service;
+}
+
+tf_android_stop_service_t* tf_task_get_android_stop_service()
+{
+ return s_android_stop_service;
+}
diff --git a/src/task.h b/src/task.h
index 61a2eb09..7d81d2c6 100644
--- a/src/task.h
+++ b/src/task.h
@@ -333,4 +333,35 @@ char* tf_task_get_debug(tf_task_t* task);
*/
char* tf_task_get_hitches(tf_task_t* task);
+/**
+** A callback used to start an Android service.
+** @param pipe_fd A file descriptor with which to communicate with the invoking
+** task.
+*/
+typedef void(tf_android_start_service_t)(int pipe_fd);
+
+/**
+** A callback used to stop an Android service.
+*/
+typedef void(tf_android_stop_service_t)();
+
+/**
+** Set Android service callbacks.
+** @param start_service Start service callback.
+** @param stop_service Stop service callback.
+*/
+void tf_task_set_android_service_callbacks(tf_android_start_service_t* start_service, tf_android_stop_service_t* stop_service);
+
+/**
+** Get the callback registered for starting an Android service.
+** @return the callback.
+*/
+tf_android_start_service_t* tf_task_get_android_start_service();
+
+/**
+** Get the callback registered for stopping an Android service.
+** @return the callback.
+*/
+tf_android_stop_service_t* tf_task_get_android_stop_service();
+
/** @} */
diff --git a/src/taskstub.js.c b/src/taskstub.js.c
index 5fdb3414..e2071e12 100644
--- a/src/taskstub.js.c
+++ b/src/taskstub.js.c
@@ -154,41 +154,74 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
}
else
{
- uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
- memset(pipe, 0, sizeof(*pipe));
- if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0)
+ tf_android_start_service_t* start_service = tf_task_get_android_start_service();
+ if (start_service)
{
- tf_printf("uv_pipe_init failed\n");
- }
+ uv_os_sock_t fds[2];
+ int socketpair_result = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0);
+ if (socketpair_result)
+ {
+ tf_printf("uv_socketpair: %s\n", uv_strerror(socketpair_result));
+ }
- uv_stdio_container_t io[3];
- io[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE;
- io[0].data.stream = (uv_stream_t*)pipe;
- io[1].flags = UV_INHERIT_FD;
- io[1].data.fd = STDOUT_FILENO;
- io[2].flags = UV_INHERIT_FD;
- io[2].data.fd = STDERR_FILENO;
+ uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
+ *pipe = (uv_pipe_t) { 0 };
+ if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0)
+ {
+ tf_printf("uv_pipe_init failed\n");
+ }
- uv_process_options_t options = { 0 };
- options.args = command_argv;
- options.exit_cb = _taskstub_on_process_exit;
- options.stdio = io;
- options.stdio_count = sizeof(io) / sizeof(*io);
- options.file = command_argv[0];
- options.env = (char*[]) { "ASAN_OPTIONS=detect_leaks=0", NULL };
+ int pipe_result = uv_pipe_open(pipe, fds[0]);
+ if (pipe_result)
+ {
+ tf_printf("uv_pipe_open: %s\n", uv_strerror(pipe_result));
+ }
- stub->_process.data = stub;
- int spawn_result = uv_spawn(tf_task_get_loop(parent), &stub->_process, &options);
- if (spawn_result == 0)
- {
+ start_service(fds[1]);
+
+ stub->_process.data = stub;
tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub);
tf_packetstream_start(stub->_stream);
result = taskObject;
}
else
{
- tf_printf("uv_spawn failed: %s\n", uv_strerror(spawn_result));
- JS_FreeValue(context, taskObject);
+ uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
+ memset(pipe, 0, sizeof(*pipe));
+ if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0)
+ {
+ tf_printf("uv_pipe_init failed\n");
+ }
+
+ uv_stdio_container_t io[3];
+ io[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE;
+ io[0].data.stream = (uv_stream_t*)pipe;
+ io[1].flags = UV_INHERIT_FD;
+ io[1].data.fd = STDOUT_FILENO;
+ io[2].flags = UV_INHERIT_FD;
+ io[2].data.fd = STDERR_FILENO;
+
+ uv_process_options_t options = { 0 };
+ options.args = command_argv;
+ options.exit_cb = _taskstub_on_process_exit;
+ options.stdio = io;
+ options.stdio_count = sizeof(io) / sizeof(*io);
+ options.file = command_argv[0];
+ options.env = (char*[]) { "ASAN_OPTIONS=detect_leaks=0", NULL };
+
+ stub->_process.data = stub;
+ int spawn_result = uv_spawn(tf_task_get_loop(parent), &stub->_process, &options);
+ if (spawn_result == 0)
+ {
+ tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub);
+ tf_packetstream_start(stub->_stream);
+ result = taskObject;
+ }
+ else
+ {
+ tf_printf("uv_spawn failed: %s\n", uv_strerror(spawn_result));
+ JS_FreeValue(context, taskObject);
+ }
}
}
return JS_DupValue(context, result);
@@ -443,7 +476,15 @@ JSValue tf_taskstub_kill(tf_taskstub_t* stub)
JSValue result = JS_UNDEFINED;
if (!tf_task_get_one_proc(stub->_owner))
{
- uv_process_kill(&stub->_process, SIGKILL);
+ tf_android_stop_service_t* stop_service = tf_task_get_android_stop_service();
+ if (stop_service)
+ {
+ stop_service();
+ }
+ else
+ {
+ uv_process_kill(&stub->_process, SIGKILL);
+ }
}
else
{