packages feed

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 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