#include #include #include #include #include #include #include #include #include "HsFFI.h" #include "Rts.h" #include "HaskellActivity.h" // From ghc/rts/RtsStartup.c extern void (*exitFn)(int); extern StgClosure ZCMain_main_closure; //TODO: Get everything above this from an existing header file if possible JavaVM* HaskellActivity_jvm = NULL; JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnCreate (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onCreate) { callbacks->onCreate(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnStart (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onStart) { callbacks->onStart(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnResume (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onResume) { callbacks->onResume(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnPause (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onPause) { callbacks->onPause(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnStop (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onStop) { callbacks->onStop(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnDestroy (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onDestroy) { callbacks->onDestroy(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnRestart (JNIEnv *env, jobject thisObj, long callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onRestart) { callbacks->onRestart(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnNewIntent (JNIEnv *env, jobject thisObj, long callbacksLong, jstring intent, jstring intentdata) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onNewIntent) { const char *cstring_intent = (*env)->GetStringUTFChars(env, intent, 0); const char *cstring_intentdata = (*env)->GetStringUTFChars(env, intentdata, 0); callbacks->onNewIntent(cstring_intent, cstring_intentdata); (*env)->ReleaseStringUTFChars(env, intent, cstring_intent); (*env)->ReleaseStringUTFChars(env, intentdata, cstring_intentdata); } } static jmp_buf mainJmpbuf; // setjmp returns 0 on its initial call, but we want to be able to return all // exit codes (0 - 255) through it. So, we offset them by this amount and then // subtract it off later. #define EXIT_CODE_OFFSET 0x10000 static void mainFinished(int exitCode) { longjmp(mainJmpbuf, exitCode + EXIT_CODE_OFFSET); } // Filled in by Java_systems_obsidian_HaskellActivity_haskellStartMain and disposed // of by haskellActivityContinueWithCallbacks static jobject haskellActivity = 0; static jobject setCallbacksQueue = 0; jobject HaskellActivity_get() { return haskellActivity; } // Continue constructing the HaskellActivity. // WARNING: This may only be invoked once per Haskell 'main' invocation void HaskellActivity_continueWithCallbacks(const ActivityCallbacks *callbacks) { assert(haskellActivity); JNIEnv *env; jint attachResult = (*HaskellActivity_jvm)->AttachCurrentThread(HaskellActivity_jvm, &env, NULL); assert(attachResult == JNI_OK); jclass cls = (*env)->GetObjectClass(env, haskellActivity); jmethodID continueWithCallbacks = (*env)->GetStaticMethodID(env, cls, "continueWithCallbacks", "(Ljava/util/concurrent/SynchronousQueue;J)V"); assert(continueWithCallbacks); (*env)->CallStaticVoidMethod(env, cls, continueWithCallbacks, setCallbacksQueue, callbacks); if((*env)->ExceptionOccurred(env)) { __android_log_write(ANDROID_LOG_DEBUG, "HaskellActivity", "continueWithCallbacks exception"); (*env)->ExceptionDescribe(env); } //TODO: Provide a better way of having access to this // (*env)->DeleteGlobalRef(env, haskellActivity); // haskellActivity = 0; (*env)->DeleteGlobalRef(env, setCallbacksQueue); setCallbacksQueue = 0; } JNIEXPORT int JNICALL Java_systems_obsidian_HaskellActivity_haskellStartMain (JNIEnv *env, jobject thisObj, jobject setCallbacksQueue_) { // Retain the HaskellActivity that we're running under haskellActivity = (*env)->NewGlobalRef(env, thisObj); setCallbacksQueue = (*env)->NewGlobalRef(env, setCallbacksQueue_); // Override Haskell's exit behavior so that it returns here instead of exiting // the program when 'main' finishes exitFn = mainFinished; int exitCode; if(exitCode = setjmp(mainJmpbuf)) { return exitCode - EXIT_CODE_OFFSET; } static int argc = 5; static char *argv[] = {"jsaddle", "+RTS", "-N2", "-I0", "-RTS"}; RtsConfig rts_opts = defaultRtsConfig; rts_opts.rts_opts_enabled = RtsOptsAll; hs_main(argc, argv, &ZCMain_main_closure, rts_opts); return 0; // Should never hit this } //////////////////////////////////////////////////////////////////////////////// // Logger //////////////////////////////////////////////////////////////////////////////// // Converts output on stdout and stderr to android log messages typedef struct loggerConfig { int pipe; const char *tag; } loggerConfig; void *loggerThread(void *cfgVoid) { assert(cfgVoid); const loggerConfig *cfg = (const loggerConfig *)cfgVoid; ssize_t rdsz; char buf[256]; __android_log_write(ANDROID_LOG_DEBUG, cfg->tag, "Logger running"); while((rdsz = read(cfg->pipe, buf, sizeof buf - 1)) > 0) { //TODO: This elides '\n's at the end of the buffer, but what should we do with ones in the middle? if(buf[rdsz - 1] == '\n') --rdsz; buf[rdsz] = 0; // Add null terminator __android_log_write(ANDROID_LOG_DEBUG, cfg->tag, buf); } } // Start the logging thread. tag will not be copied, and must continue to // be available indefinitely. // Note: if the thread is killed, the loggerConfig will be leaked void startLogger(const char *tag) { int pfd[2]; setvbuf(stdout, 0, _IOLBF, 0); setvbuf(stderr, 0, _IOLBF, 0); // Create the pipe and redirect stdout and stderr pipe(pfd); dup2(pfd[1], 1); dup2(pfd[1], 2); loggerConfig *cfg = malloc(sizeof(loggerConfig)); cfg->pipe = pfd[0]; cfg->tag = tag; pthread_t thr; // Spawn the logging thread if(pthread_create(&thr, 0, loggerThread, cfg) == -1) { __android_log_write(ANDROID_LOG_DEBUG, tag, "Could not start logger"); assert(false); } pthread_detach(thr); } //////////////////////////////////////////////////////////////////////////////// // JNI Initialization //////////////////////////////////////////////////////////////////////////////// JNIEXPORT jint JNICALL JNI_OnLoad ( JavaVM *vm, void *reserved ) { HaskellActivity_jvm = vm; startLogger("HaskellActivity"); //TODO: Use the app name return JNI_VERSION_1_6; }