#include #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; static ActivityCallbacks const *gCallbacks = NULL; JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnCreate (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onCreate) { callbacks->onCreate(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnStart (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onStart) { callbacks->onStart(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnResume (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onResume) { callbacks->onResume(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnPause (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onPause) { callbacks->onPause(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnStop (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onStop) { callbacks->onStop(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnDestroy (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onDestroy) { callbacks->onDestroy(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnRestart (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onRestart) { callbacks->onRestart(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnBackPressed (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const ActivityCallbacks *callbacks = (const ActivityCallbacks *)callbacksLong; if(callbacks->onBackPressed) { callbacks->onBackPressed(); } } JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnNewIntent (JNIEnv *env, jobject thisObj, jlong 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); } } JNIEXPORT void JNICALL Java_systems_obsidian_LocalFirebaseMessagingService_handleDeviceToken (JNIEnv *env, jobject thisObj, jstring token) { if(gCallbacks && gCallbacks->firebaseInstanceIdService_sendRegistrationToServer) { const char *cstring_token = (*env)->GetStringUTFChars(env, token, 0); gCallbacks->firebaseInstanceIdService_sendRegistrationToServer(cstring_token); (*env)->ReleaseStringUTFChars(env, token, cstring_token); } } JNIEXPORT void JNICALL Java_systems_obsidian_LocalFirebaseMessagingService_handleNotification (JNIEnv *env, jobject thisObj, jstring intent, jstring notificationdata) { if(gCallbacks && gCallbacks->onNewIntent) { const char *cstring_intent = (*env)->GetStringUTFChars(env, intent, 0); const char *cstring_notificationdata = (*env)->GetStringUTFChars(env, notificationdata, 0); gCallbacks->onNewIntent(cstring_intent, cstring_notificationdata); (*env)->ReleaseStringUTFChars(env, intent, cstring_intent); (*env)->ReleaseStringUTFChars(env, notificationdata, cstring_notificationdata); } } 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; } JNIEnv *getJNIEnv() { JNIEnv *env; jint attachResult = (*HaskellActivity_jvm)->AttachCurrentThread(HaskellActivity_jvm, &env, NULL); assert(attachResult == JNI_OK); return env; } // Return a copied C string which must be freed by the caller with free(). char *copyToCString(JNIEnv *env, jobject str) { char *cstr = (*env)->GetStringUTFChars(env, (jstring)str, NULL); assert(cstr); char *result = malloc(strlen(cstr) + 1); strcpy(result, cstr); (*env)->ReleaseStringUTFChars(env, str, cstr); return result; } jobject java_io_File_getPath(JNIEnv *env, jobject file) { jclass cls = (*env)->GetObjectClass(env, file); jmethodID getPath = (*env)->GetMethodID(env, cls, "getPath", "()Ljava/lang/String;"); assert(getPath); jobject result = (*env)->CallObjectMethod(env, file, getPath); assert(result); return result; } char *HaskellActivity_getFilesDir(jobject haskellActivity) { assert(haskellActivity); JNIEnv *env = getJNIEnv(); jclass haskellActivityClass = (*env)->GetObjectClass(env, haskellActivity); jmethodID getFilesDir = (*env)->GetMethodID(env, haskellActivityClass, "getFilesDir", "()Ljava/io/File;"); assert(getFilesDir); jobject filesDir = (*env)->CallObjectMethod(env, haskellActivity, getFilesDir); if (!filesDir) return NULL; return copyToCString(env, java_io_File_getPath(env, filesDir)); } char *HaskellActivity_getCacheDir(jobject haskellActivity) { assert(haskellActivity); JNIEnv *env = getJNIEnv(); jclass haskellActivityClass = (*env)->GetObjectClass(env, haskellActivity); jmethodID getCacheDir = (*env)->GetMethodID(env, haskellActivityClass, "getCacheDir", "()Ljava/io/File;"); assert(getCacheDir); jobject cacheDir = (*env)->CallObjectMethod(env, haskellActivity, getCacheDir); if (!cacheDir) return NULL; return copyToCString(env, java_io_File_getPath(env, cacheDir)); } // 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 = getJNIEnv(); 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; // We'll need to use this in some callbacks that aren't methods of HaskellActivity gCallbacks = callbacks; } 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[] = {"HaskellActivity", "+RTS", "-N", "-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; }