android-activity 0.1 → 0.2.0.0
raw patch · 8 files changed
+385/−27 lines, 8 filesdep ~basenew-uploader
Dependency ranges changed: base
Files
- android-activity.cabal +8/−2
- cbits/HaskellActivity.c +98/−13
- cbits/include/HaskellActivity.h +4/−0
- changelog.md +11/−0
- java/systems/obsidian/HaskellActivity.java +165/−7
- java/systems/obsidian/LocalFirebaseMessagingService.java +39/−0
- readme.md +5/−0
- src/Android/HaskellActivity.hsc +55/−5
android-activity.cabal view
@@ -1,5 +1,5 @@ name: android-activity-version: 0.1+version: 0.2.0.0 synopsis: Turn regular Haskell programs into Android Activities homepage: https://github.com/obsidiansystems/android-activity license: BSD3@@ -11,11 +11,14 @@ build-type: Simple extra-source-files: java/systems/obsidian/HaskellActivity.java+ java/systems/obsidian/LocalFirebaseMessagingService.java+ readme.md+ changelog.md cabal-version: >=1.10 library other-extensions: ForeignFunctionInterface- build-depends: base >=4.9 && <4.11+ build-depends: base >=4.9 && <4.15 , data-default == 0.7.* hs-source-dirs: src build-tools: hsc2hs@@ -25,3 +28,6 @@ install-includes: HaskellActivity.h exposed-modules: Android.HaskellActivity+ extra-libraries: log+ ghc-options: -Wall -fPIC+ cc-options: -fPIC
cbits/HaskellActivity.c view
@@ -6,6 +6,7 @@ #include <stdio.h> #include <android/log.h> #include <setjmp.h>+#include <string.h> #include "HsFFI.h" #include "Rts.h" #include "HaskellActivity.h"@@ -19,57 +20,66 @@ JavaVM* HaskellActivity_jvm = NULL; -JNIEXPORT void JNICALL Java_systems_obsidian_HaskellActivity_haskellOnCreate (JNIEnv *env, jobject thisObj, long callbacksLong) {+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, long callbacksLong) {+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, long callbacksLong) {+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, long callbacksLong) {+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, long callbacksLong) {+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, long callbacksLong) {+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, long callbacksLong) {+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_haskellOnNewIntent (JNIEnv *env, jobject thisObj, long callbacksLong, jstring intent, jstring intentdata) {+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);@@ -79,6 +89,24 @@ } } +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@@ -99,14 +127,68 @@ 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;- jint attachResult = (*HaskellActivity_jvm)->AttachCurrentThread(HaskellActivity_jvm, &env, NULL);- assert(attachResult == JNI_OK);+ JNIEnv *env = getJNIEnv(); jclass cls = (*env)->GetObjectClass(env, haskellActivity); jmethodID continueWithCallbacks = (*env)->GetStaticMethodID(env, cls, "continueWithCallbacks", "(Ljava/util/concurrent/SynchronousQueue;J)V");@@ -124,6 +206,9 @@ (*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_) {@@ -140,7 +225,7 @@ } static int argc = 5;- static char *argv[] = {"jsaddle", "+RTS", "-N2", "-I0", "-RTS"};+ static char *argv[] = {"HaskellActivity", "+RTS", "-N", "-I0", "-RTS"}; RtsConfig rts_opts = defaultRtsConfig; rts_opts.rts_opts_enabled = RtsOptsAll;
cbits/include/HaskellActivity.h view
@@ -11,9 +11,13 @@ void (*onStop) (); void (*onDestroy) (); void (*onRestart) ();+ void (*onBackPressed) (); void (*onNewIntent) (const char *, const char *); //TODO: Pass the whole argument and use JNI+ void (*firebaseInstanceIdService_sendRegistrationToServer) (char *); } ActivityCallbacks; extern JavaVM* HaskellActivity_jvm;++extern jobject HaskellActivity_get(); #endif
+ changelog.md view
@@ -0,0 +1,11 @@+# Changelog for android-activity++## 0.2++* Support firebase (needed for push notifications)+* Support GHC 8.10+* Flush cookies to storage on app close++## 0.1++* Turn regular haskell programs into android Activities
java/systems/obsidian/HaskellActivity.java view
@@ -1,16 +1,24 @@ package systems.obsidian; +import android.annotation.TargetApi; import android.app.Activity;-import android.content.Context;+import android.app.Notification;+import android.app.PendingIntent; import android.content.Intent;+import android.content.pm.PackageManager;+import android.Manifest;+import android.net.Uri;+import android.os.Build; import android.os.Bundle; import android.util.Log;-import android.view.ViewGroup.LayoutParams;-import android.view.Window;-import android.webkit.CookieManager;-import android.webkit.WebSettings;-import android.webkit.WebView;+import android.webkit.PermissionRequest;+import java.util.ArrayList;+import java.util.Arrays; import java.util.concurrent.SynchronousQueue;+import java.util.HashMap;+import java.util.HashSet;+import android.webkit.ValueCallback;+import android.webkit.CookieManager; public class HaskellActivity extends Activity { public native int haskellStartMain(SynchronousQueue<Long> setCallbacks);@@ -21,8 +29,11 @@ public native void haskellOnStop(long callbacks); public native void haskellOnDestroy(long callbacks); public native void haskellOnRestart(long callbacks);+ public native void haskellOnBackPressed(long callbacks); public native void haskellOnNewIntent(long callbacks, String intent, String intentdata); + public static final int REQUEST_CODE_FILE_PICKER = 51426;+ // Apparently 'long' is the right way to store a C pointer in Java // See https://stackoverflow.com/questions/337268/what-is-the-correct-way-to-store-a-native-pointer-inside-a-java-object final long callbacks;@@ -33,6 +44,7 @@ public HaskellActivity() throws InterruptedException { final SynchronousQueue<Long> setCallbacks = new SynchronousQueue<Long>();+ permissionRequests = new HashMap<Integer, PermissionRequest>(); new Thread() { public void run() { final int exitCode = haskellStartMain(setCallbacks);@@ -51,7 +63,7 @@ } // This can be called by the Haskell application to unblock the construction- // of the HaskellActivity and proced with the given callbacks+ // of the HaskellActivity and proceed with the given callbacks. // NOTE: This shouldn't be an instance method, because it must be called *before* // the constructor returns (it *causes* the constructor to return).@@ -100,6 +112,7 @@ if(callbacks != 0) { haskellOnPause(callbacks); }+ CookieManager.getInstance().flush(); } @Override@@ -108,6 +121,7 @@ if(callbacks != 0) { haskellOnStop(callbacks); }+ CookieManager.getInstance().flush(); } @Override@@ -129,10 +143,154 @@ } @Override+ public void onBackPressed() {+ if(callbacks != 0) {+ haskellOnBackPressed(callbacks);+ }+ }++ @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); if(callbacks != 0 && intent != null && intent.getData() != null && intent.getAction() != null) { haskellOnNewIntent(callbacks, intent.getAction(), intent.getDataString()); //TODO: Use a more canonical way of passing this data - i.e. pass the Intent and let the Haskell side get the data out with JNI } }++ // Proper separation of concerns is really a whole lot of work in Java, so+ // we simply handle PermissionRequests directly here - it is just sooo much+ // easier. Java makes it really hard to write good code.+ public void requestWebViewPermissions(final PermissionRequest request) {+ try {+ String[] resources = request.getResources();+ ArrayList<String> sysResourcesToRequestList = new ArrayList<String>();+ for(int i=0; i < resources.length; i++) {+ String manifestRequest = null;+ switch(resources[i]) {+ case PermissionRequest.RESOURCE_AUDIO_CAPTURE:+ manifestRequest = Manifest.permission.RECORD_AUDIO;+ break;+ case PermissionRequest.RESOURCE_VIDEO_CAPTURE:+ manifestRequest = Manifest.permission.CAMERA;+ break;+ }+ if(manifestRequest != null && checkSelfPermission(manifestRequest) != PackageManager.PERMISSION_GRANTED)+ sysResourcesToRequestList.add(manifestRequest);+ }+ String[] sysResourcesToRequest = sysResourcesToRequestList.toArray(new String[0]);+ if(sysResourcesToRequest.length > 0) {+ permissionRequests.put(nextRequestCode, request);+ requestPermissions(sysResourcesToRequest, nextRequestCode++);+ }+ else {+ runOnUiThread(new Runnable() {+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)+ @Override+ public void run() {+ request.grant(request.getResources());+ }+ });+ }+ } catch (NoSuchMethodError e) { // Compatibility for older Android versions (Android 5 and below)+ runOnUiThread(new Runnable() {+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)+ @Override+ public void run() {+ request.grant(request.getResources());+ }+ });+ }+ }++ @Override+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {+ final PermissionRequest request = permissionRequests.get(requestCode);+ permissionRequests.remove(requestCode);++ String[] requestedPermissions = request.getResources();+ final HashSet<String> grantedPermissions = new HashSet<String>(Arrays.asList(requestedPermissions));++ // We assume grantResults and permissions have same length ... obviously.+ for(int i = 0; i< permissions.length; i++) {+ if(grantResults[i] == PackageManager.PERMISSION_GRANTED) {+ String permission = null;+ switch(permissions[i]) {+ case Manifest.permission.RECORD_AUDIO:+ Log.d("HaskellActivity", "Granting RESOURCE_AUDIO_CAPTURE!");+ permission = PermissionRequest.RESOURCE_AUDIO_CAPTURE;+ break;+ case Manifest.permission.CAMERA:+ Log.d("HaskellActivity", "Granting RESOURCE_VIDEO_CAPTURE!");+ permission = PermissionRequest.RESOURCE_VIDEO_CAPTURE;+ break;+ }+ if(permission != null && grantResults[i] != PackageManager.PERMISSION_GRANTED)+ grantedPermissions.remove(permission);+ }+ }+ runOnUiThread(new Runnable() {+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)+ @Override+ public void run() {+ if(grantedPermissions.size() > 0) {+ Log.d("HaskellActivity", "Granting permissions!");+ request.grant(grantedPermissions.toArray(new String[0]));+ }+ else {+ request.deny();+ }+ }+ });+ }++ // File uploads don't work out of the box.+ // You have to start an 'Intent' from 'onShowFileChooser' by setting the callback with+ // 'setFileUploadCallback'. The callback will be handled here.+ @Override+ public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {+ if (requestCode == REQUEST_CODE_FILE_PICKER) {+ if (resultCode == Activity.RESULT_OK) {+ if (intent != null) {+ if (fileUploadCallback != null) {+ Uri[] dataUris = null;++ try {+ if (intent.getDataString() != null) {+ dataUris = new Uri[] { Uri.parse(intent.getDataString()) };+ }+ else {+ if (intent.getClipData() != null) {+ final int numSelectedFiles = intent.getClipData().getItemCount();+ dataUris = new Uri[numSelectedFiles];+ for (int i = 0; i < numSelectedFiles; i++) {+ dataUris[i] = intent.getClipData().getItemAt(i).getUri();+ }+ }+ }+ }+ catch (Exception ignored) { }++ fileUploadCallback.onReceiveValue(dataUris);+ fileUploadCallback = null;+ }+ }+ }+ else if (fileUploadCallback != null) {+ fileUploadCallback.onReceiveValue(null);+ fileUploadCallback = null;+ }+ }+ }++ // Set the file upload callback to be used by 'onActivityResult'.+ public void setFileUploadCallback(ValueCallback<Uri[]> cb) {+ if (fileUploadCallback != null) {+ fileUploadCallback.onReceiveValue(null);+ }+ fileUploadCallback = cb;+ }++ private HashMap<Integer, PermissionRequest> permissionRequests;+ private int nextRequestCode = 0;+ private ValueCallback<Uri[]> fileUploadCallback; }
+ java/systems/obsidian/LocalFirebaseMessagingService.java view
@@ -0,0 +1,39 @@+// File based on https://github.com/firebase/quickstart-android/blob/d454ecdbb664450d11812b9cfde91fca36c56a81/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/java/MyFirebaseMessagingService.java+package systems.obsidian;++import android.util.Log;+import com.google.firebase.messaging.FirebaseMessagingService;+import com.google.firebase.messaging.RemoteMessage;++public class LocalFirebaseMessagingService extends FirebaseMessagingService {++ private static final String TAG = "LocalFirebaseMessagingService";++ private native void handleDeviceToken(String token);+ private native void handleNotification(String intent, String notificationdata);++ /**+ * Called if InstanceID token is updated. This may occur if the security of+ * the previous token had been compromised. Note that this is called when the InstanceID token+ * is initially generated so this is where you would retrieve the token.+ */+ @Override+ public void onNewToken(String token) {+ Log.d(TAG, "onNewToken: Refreshed token: " + token);++ // If you want to send messages to this application instance or+ // manage this apps subscriptions on the server side, send the+ // Instance ID token to your app server.+ handleDeviceToken(token);+ }++ @Override+ public void onMessageReceived(RemoteMessage remoteMessage) {+ if (remoteMessage.getData().size() > 0) {+ String custom = remoteMessage.getData().get("custom");+ if (custom != null) {+ handleNotification("custom-foreground", custom);+ }+ }+ }+}
+ readme.md view
@@ -0,0 +1,5 @@+# android-activity for Haskell++This package lets you embed a haskell application in an android app as an [Android Activity](https://developer.android.com/reference/android/app/Activity).++See java/systems/obsidian/HaskellActivity.java for the core application. We use the [JNI](https://developer.android.com/training/articles/perf-jni) to provide access to android native functions from the Haskell application.
src/Android/HaskellActivity.hsc view
@@ -3,13 +3,15 @@ ( ActivityCallbacks (..) , HaskellActivity (..) , getHaskellActivity+ , getFilesDir+ , getCacheDir , continueWithCallbacks , traceActivityCallbacks ) where import Control.Exception+import Control.Monad import Data.Default-import Data.Monoid import Debug.Trace import Foreign.C.String import Foreign.Marshal.Utils@@ -22,6 +24,35 @@ foreign import ccall unsafe "HaskellActivity_get" getHaskellActivity :: IO HaskellActivity +foreign import ccall unsafe "HaskellActivity_getFilesDir" getFilesDirCString+ :: HaskellActivity+ -> IO CString++foreign import ccall unsafe "HaskellActivity_getCacheDir" getCacheDirCString+ :: HaskellActivity+ -> IO CString++-- | Copy a C string into Haskell returning 'Nothing' if it is NULL.+peekMaybeCString :: CString -> IO (Maybe String)+peekMaybeCString str =+ if str == nullPtr+ then return Nothing+ else Just <$> peekCString str++-- | Get the "internal" storage directory for the app. This is where data local+-- to the app should be stored. Note that 'Nothing' is returned if the activity+-- is not fully initalized. In practice this means you probably need to call+-- this inside the main widget.+getFilesDir :: HaskellActivity -> IO (Maybe FilePath)+getFilesDir = getFilesDirCString >=> peekMaybeCString++-- | Get the cache storage directory for the app. Android may delete this data+-- at any time. Note that 'Nothing' is returned if the activity is not fully+-- initalized. In practice this means you probably need to call this inside the+-- main widget.+getCacheDir :: HaskellActivity -> IO (Maybe FilePath)+getCacheDir = getCacheDirCString >=> peekMaybeCString+ -- | Allow the HaskellActivity to proceed. The given callbacks will be invoked -- at the appropriate times in the Android Activity lifecycle. -- WARNING: This should only be invoked once per application execution.@@ -39,7 +70,9 @@ , _activityCallbacks_onStop :: IO () , _activityCallbacks_onDestroy :: IO () , _activityCallbacks_onRestart :: IO ()+ , _activityCallbacks_onBackPressed :: IO () , _activityCallbacks_onNewIntent :: String -> String -> IO ()+ , _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer :: String -> IO () } instance Default ActivityCallbacks where@@ -51,7 +84,9 @@ , _activityCallbacks_onStop = return () , _activityCallbacks_onDestroy = return () , _activityCallbacks_onRestart = return ()+ , _activityCallbacks_onBackPressed = return () , _activityCallbacks_onNewIntent = \_ _ -> return ()+ , _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer = \_ -> return () } traceBracket :: String -> IO a -> IO a@@ -67,10 +102,13 @@ , _activityCallbacks_onDestroy = traceBracket "onDestroy" $ _activityCallbacks_onDestroy ac , _activityCallbacks_onRestart = traceBracket "onRestart" $ _activityCallbacks_onRestart ac , _activityCallbacks_onNewIntent = \x y -> traceBracket "onNewIntent" $ _activityCallbacks_onNewIntent ac x y+ , _activityCallbacks_onBackPressed = traceBracket "onBackPressed" $ _activityCallbacks_onBackPressed ac+ , _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer = \x ->+ traceBracket "firebaseInstanceIdServiceSendRegistrationToServer" $ _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer ac x } foreign import ccall "wrapper" wrapIO :: IO () -> IO (FunPtr (IO ()))-+foreign import ccall "wrapper" wrapCStringIO :: (CString -> IO ()) -> IO (FunPtr (CString -> IO ())) foreign import ccall "wrapper" wrapCStringCStringIO :: (CString -> CString -> IO ()) -> IO (FunPtr (CString -> CString -> IO ())) activityCallbacksToPtrs :: ActivityCallbacks -> IO ActivityCallbacksPtrs@@ -82,10 +120,16 @@ <*> wrapIO (_activityCallbacks_onStop ac) <*> wrapIO (_activityCallbacks_onDestroy ac) <*> wrapIO (_activityCallbacks_onRestart ac)+ <*> wrapIO (_activityCallbacks_onBackPressed ac) <*> wrapCStringCStringIO (\a b -> do- a' <- peekCString a- b' <- peekCString b- _activityCallbacks_onNewIntent ac a' b')+ a' <- peekCString a+ b' <- peekCString b+ _activityCallbacks_onNewIntent ac a' b'+ )+ <*> wrapCStringIO (\token -> do+ token' <- peekCString token+ _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer ac token'+ ) data ActivityCallbacksPtrs = ActivityCallbacksPtrs { _activityCallbacksPtrs_onCreate :: FunPtr (IO ())@@ -95,7 +139,9 @@ , _activityCallbacksPtrs_onStop :: FunPtr (IO ()) , _activityCallbacksPtrs_onDestroy :: FunPtr (IO ()) , _activityCallbacksPtrs_onRestart :: FunPtr (IO ())+ , _activityCallbacksPtrs_onBackPressed :: FunPtr (IO ()) , _activityCallbacksPtrs_onNewIntent :: FunPtr (CString -> CString -> IO ())+ , _activityCallbacksPtrs_firebaseInstanceIdService_sendRegistrationToServer :: FunPtr (CString -> IO ()) } instance Storable ActivityCallbacksPtrs where@@ -109,7 +155,9 @@ #{poke ActivityCallbacks, onStop} p $ _activityCallbacksPtrs_onStop ac #{poke ActivityCallbacks, onDestroy} p $ _activityCallbacksPtrs_onDestroy ac #{poke ActivityCallbacks, onRestart} p $ _activityCallbacksPtrs_onRestart ac+ #{poke ActivityCallbacks, onBackPressed} p $ _activityCallbacksPtrs_onBackPressed ac #{poke ActivityCallbacks, onNewIntent} p $ _activityCallbacksPtrs_onNewIntent ac+ #{poke ActivityCallbacks, firebaseInstanceIdService_sendRegistrationToServer} p $ _activityCallbacksPtrs_firebaseInstanceIdService_sendRegistrationToServer ac peek p = ActivityCallbacksPtrs <$> #{peek ActivityCallbacks, onCreate} p <*> #{peek ActivityCallbacks, onStart} p@@ -118,4 +166,6 @@ <*> #{peek ActivityCallbacks, onStop} p <*> #{peek ActivityCallbacks, onDestroy} p <*> #{peek ActivityCallbacks, onRestart} p+ <*> #{peek ActivityCallbacks, onBackPressed} p <*> #{peek ActivityCallbacks, onNewIntent} p+ <*> #{peek ActivityCallbacks, firebaseInstanceIdService_sendRegistrationToServer} p