android-activity-0.2.0.0: cbits/HaskellActivity.c
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include <jni.h>
#include <stdio.h>
#include <android/log.h>
#include <setjmp.h>
#include <string.h>
#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;
}