diff --git a/android-activity.cabal b/android-activity.cabal
--- a/android-activity.cabal
+++ b/android-activity.cabal
@@ -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
diff --git a/cbits/HaskellActivity.c b/cbits/HaskellActivity.c
--- a/cbits/HaskellActivity.c
+++ b/cbits/HaskellActivity.c
@@ -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;
diff --git a/cbits/include/HaskellActivity.h b/cbits/include/HaskellActivity.h
--- a/cbits/include/HaskellActivity.h
+++ b/cbits/include/HaskellActivity.h
@@ -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
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -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
diff --git a/java/systems/obsidian/HaskellActivity.java b/java/systems/obsidian/HaskellActivity.java
--- a/java/systems/obsidian/HaskellActivity.java
+++ b/java/systems/obsidian/HaskellActivity.java
@@ -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;
 }
diff --git a/java/systems/obsidian/LocalFirebaseMessagingService.java b/java/systems/obsidian/LocalFirebaseMessagingService.java
new file mode 100644
--- /dev/null
+++ b/java/systems/obsidian/LocalFirebaseMessagingService.java
@@ -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);
+      }
+    }
+  }
+}
diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -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.
diff --git a/src/Android/HaskellActivity.hsc b/src/Android/HaskellActivity.hsc
--- a/src/Android/HaskellActivity.hsc
+++ b/src/Android/HaskellActivity.hsc
@@ -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
