packages feed

android-activity-0.2.0.0: src/Android/HaskellActivity.hsc

{-# LANGUAGE ForeignFunctionInterface #-}
module Android.HaskellActivity
  ( ActivityCallbacks (..)
  , HaskellActivity (..)
  , getHaskellActivity
  , getFilesDir
  , getCacheDir
  , continueWithCallbacks
  , traceActivityCallbacks
  ) where

import Control.Exception
import Control.Monad
import Data.Default
import Debug.Trace
import Foreign.C.String
import Foreign.Marshal.Utils
import Foreign.Ptr
import Foreign.Storable

#include "HaskellActivity.h"

newtype HaskellActivity = HaskellActivity { unHaskellActivity :: Ptr HaskellActivity }

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.
continueWithCallbacks :: ActivityCallbacks -> IO ()
continueWithCallbacks ac = do
  continueWithCallbacks_ =<< new =<< activityCallbacksToPtrs ac

foreign import ccall safe "HaskellActivity_continueWithCallbacks" continueWithCallbacks_ :: Ptr ActivityCallbacksPtrs -> IO ()

data ActivityCallbacks = ActivityCallbacks
  { _activityCallbacks_onCreate :: () -> IO () -- The () input here will eventually become a representation of the Bundle that Android passes in; this placeholder is to make the change easier
  , _activityCallbacks_onStart :: IO ()
  , _activityCallbacks_onResume :: IO ()
  , _activityCallbacks_onPause :: IO ()
  , _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
  def = ActivityCallbacks
    { _activityCallbacks_onCreate = \_ -> return ()
    , _activityCallbacks_onStart = return ()
    , _activityCallbacks_onResume = return ()
    , _activityCallbacks_onPause = return ()
    , _activityCallbacks_onStop = return ()
    , _activityCallbacks_onDestroy = return ()
    , _activityCallbacks_onRestart = return ()
    , _activityCallbacks_onBackPressed = return ()
    , _activityCallbacks_onNewIntent = \_ _ -> return ()
    , _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer = \_ -> return ()
    }

traceBracket :: String -> IO a -> IO a
traceBracket s = bracket (traceIO $ s <> " entered") (\_ -> traceIO $ s <> " exited") . const

traceActivityCallbacks :: ActivityCallbacks -> ActivityCallbacks
traceActivityCallbacks ac = ActivityCallbacks
  { _activityCallbacks_onCreate = \x -> traceBracket "onCreate" $ _activityCallbacks_onCreate ac x
  , _activityCallbacks_onStart = traceBracket "onStart" $ _activityCallbacks_onStart ac
  , _activityCallbacks_onResume = traceBracket "onResume" $ _activityCallbacks_onResume ac
  , _activityCallbacks_onPause = traceBracket "onPause" $ _activityCallbacks_onPause ac
  , _activityCallbacks_onStop = traceBracket "onStop" $ _activityCallbacks_onStop ac
  , _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
activityCallbacksToPtrs ac = ActivityCallbacksPtrs
  <$> wrapIO (_activityCallbacks_onCreate ac ())
  <*> wrapIO (_activityCallbacks_onStart ac)
  <*> wrapIO (_activityCallbacks_onResume ac)
  <*> wrapIO (_activityCallbacks_onPause ac)
  <*> 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'
      )
  <*> wrapCStringIO (\token -> do
        token' <- peekCString token
        _activityCallbacks_firebaseInstanceIdServiceSendRegistrationToServer ac token'
      )

data ActivityCallbacksPtrs = ActivityCallbacksPtrs
  { _activityCallbacksPtrs_onCreate :: FunPtr (IO ())
  , _activityCallbacksPtrs_onStart :: FunPtr (IO ())
  , _activityCallbacksPtrs_onResume :: FunPtr (IO ())
  , _activityCallbacksPtrs_onPause :: FunPtr (IO ())
  , _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
  sizeOf _ = #{size ActivityCallbacks}
  alignment _ = #{alignment ActivityCallbacks}
  poke p ac = do
    #{poke ActivityCallbacks, onCreate} p $ _activityCallbacksPtrs_onCreate ac
    #{poke ActivityCallbacks, onStart} p $ _activityCallbacksPtrs_onStart ac
    #{poke ActivityCallbacks, onResume} p $ _activityCallbacksPtrs_onResume ac
    #{poke ActivityCallbacks, onPause} p $ _activityCallbacksPtrs_onPause ac
    #{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
    <*> #{peek ActivityCallbacks, onResume} p
    <*> #{peek ActivityCallbacks, onPause} p
    <*> #{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