on develop help sign in
Android working
commit701312b1786ca9d7e5d81b0170fb095319a6d282 authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 24 Sep 2017, 17:44 (9 years ago) parenta1d69e9
7 files changed+237−25
reflex-dom/cbits/MainWidget.c+50−3 reflex-dom/cbits/include/MainWidget.h+11 reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+55−4 reflex-dom/reflex-dom.cabal+5−2 reflex-dom/src-android/Reflex/Dom/Android/MainWidget.hs−13 reflex-dom/src-android/Reflex/Dom/Android/MainWidget.hsc+106 reflex-dom/src/Reflex/Dom/Internal.hs+10−3
reflex-dom/cbits/MainWidget.c+50−3
@@ -2,8 +2,9 @@
22#include <assert.h>
33#include <android/log.h>
44#include <HaskellActivity.h>
5+#include "MainWidget.h"
56
6−void Reflex_Dom_Android_MainWidget_start(jobject activity, const char *url) {
7+jobject Reflex_Dom_Android_MainWidget_start(jobject activity, const char *url, const JSaddleCallbacks *jsaddleCallbacks) {
78 assert(activity);
89 assert(url);
910
@@ -13,14 +14,60 @@ void Reflex_Dom_Android_MainWidget_start(jobject activity, const char *url) {
1314
1415 jclass cls = (*env)->FindClass(env, "org/reflexfrp/reflexdom/MainWidget");
1516 assert(cls);
16− jmethodID startMainWidget = (*env)->GetStaticMethodID(env, cls, "startMainWidget", "(Landroid/app/Activity;Ljava/lang/String;)V");
17+ jmethodID startMainWidget = (*env)->GetStaticMethodID(env, cls, "startMainWidget", "(Landroid/app/Activity;Ljava/lang/String;JLjava/lang/String;)Ljava/lang/Object;");
1718 assert(startMainWidget);
1819
1920 jstring jurl = (*env)->NewStringUTF(env, url);
2021 assert(jurl);
21− (*env)->CallStaticVoidMethod(env, cls, startMainWidget, activity, jurl);
22+ jstring initialJS = (*env)->NewStringUTF(env, jsaddleCallbacks->jsaddleJsData);
23+ jobject result = (*env)->CallStaticObjectMethod(env, cls, startMainWidget, activity, jurl, (long)jsaddleCallbacks, initialJS);
24+ (*env)->DeleteLocalRef(env, initialJS);
2225 if((*env)->ExceptionOccurred(env)) {
2326 __android_log_write(ANDROID_LOG_DEBUG, "MainWidget", "startMainWidget exception");
2427 (*env)->ExceptionDescribe(env);
2528 }
29+ return (*env)->NewGlobalRef(env, result);
30+}
31+
32+void Reflex_Dom_Android_MainWidget_runJS(jobject jsExecutor, const char* js) {
33+ JNIEnv *env;
34+ jint attachResult = (*HaskellActivity_jvm)->AttachCurrentThread(HaskellActivity_jvm, &env, NULL);
35+ assert (attachResult == JNI_OK);
36+
37+ //TODO: Don't search for this method every time
38+ jclass cls = (*env)->GetObjectClass(env, jsExecutor);
39+ assert(cls);
40+ jmethodID evaluateJavascript = (*env)->GetMethodID(env, cls, "evaluateJavascript", "(Ljava/lang/String;)V");
41+ assert(evaluateJavascript);
42+ jstring js_str = (*env)->NewStringUTF(env, js);
43+ (*env)->CallVoidMethod(env, jsExecutor, evaluateJavascript, js_str, 0);
44+ if((*env)->ExceptionOccurred(env)) {
45+ __android_log_write(ANDROID_LOG_DEBUG, "MainWidget", "runJS exception");
46+ (*env)->ExceptionDescribe(env);
47+ }
48+ (*env)->DeleteLocalRef(env, js_str);
49+}
50+
51+JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_startProcessing (JNIEnv *env, jobject thisObj, long callbacksLong) {
52+ const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
53+ (*(callbacks->jsaddleStart))();
54+ return;
55+}
56+
57+JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processMessage (JNIEnv *env, jobject thisObj, long callbacksLong, jstring msg) {
58+ const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
59+ const char *msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
60+ (*(callbacks->jsaddleResult))(msg_str);
61+ (*env)->ReleaseStringUTFChars(env, msg, msg_str);
62+ return;
63+}
64+
65+JNIEXPORT jstring JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processSyncMessage (JNIEnv *env, jobject thisObj, long callbacksLong, jstring msg) {
66+ const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
67+ const char *msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
68+ char *next_str = (*(callbacks->jsaddleSyncResult))(msg_str);
69+ jstring next_jstr = (*env)->NewStringUTF(env,next_str);
70+ free(next_str);
71+ (*env)->ReleaseStringUTFChars(env, msg, msg_str);
72+ return next_jstr;
2673}
reflex-dom/cbits/include/MainWidget.hnew file+11
@@ -0,0 +1,11 @@
1+#ifndef MAINWIDGET_H_INCLUDED
2+#define MAINWIDGET_H_INCLUDED
3+
4+typedef struct JSaddleCallbacks {
5+ void (* jsaddleStart) ();
6+ void (* jsaddleResult) (const char *);
7+ char * (* jsaddleSyncResult) (const char *);
8+ char * jsaddleJsData;
9+} JSaddleCallbacks;
10+
11+#endif
reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+55−4
@@ -1,17 +1,19 @@
11package org.reflexfrp.reflexdom;
22
33import android.app.Activity;
4−import android.content.Context;
4+import android.os.Handler;
5+import android.util.Log;
56import android.view.ViewGroup.LayoutParams;
67import android.view.Window;
78import android.webkit.CookieManager;
9+import android.webkit.JavascriptInterface;
810import android.webkit.WebSettings;
911import android.webkit.WebView;
10−import java.util.concurrent.SynchronousQueue;
12+import android.webkit.WebViewClient;
1113
1214
1315public class MainWidget {
14− private static void startMainWidget(Activity a, String url) {
16+ private static Object startMainWidget(Activity a, String url, long jsaddleCallbacks, final String initialJS) {
1517 CookieManager.setAcceptFileSchemeCookies(true); //TODO: Can we do this just for our own WebView?
1618
1719 // Remove title and notification bars
@@ -20,7 +22,7 @@ public class MainWidget {
2022 final WebView wv = new WebView(a);
2123 wv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
2224 a.setContentView(wv);
23− WebSettings ws = wv.getSettings();
25+ final WebSettings ws = wv.getSettings();
2426 ws.setJavaScriptEnabled(true);
2527 ws.setAllowFileAccessFromFileURLs(true);
2628 ws.setAllowUniversalAccessFromFileURLs(true);
@@ -28,6 +30,55 @@ public class MainWidget {
2830 // allow video to play without user interaction
2931 wv.getSettings().setMediaPlaybackRequiresUserGesture(false);
3032
33+ wv.setWebViewClient(new WebViewClient() {
34+ @Override
35+ public void onPageFinished(WebView _view, String _url) {
36+ wv.evaluateJavascript(initialJS, null);
37+ }
38+ });
39+
40+ wv.addJavascriptInterface(new JSaddleCallbacks(jsaddleCallbacks), "jsaddle");
41+
3142 wv.loadUrl(url);
43+
44+ final Handler hnd = new Handler();
45+ return new Object() {
46+ public final void evaluateJavascript(final String js) {
47+ hnd.post(new Runnable() {
48+ @Override
49+ public void run() {
50+ wv.evaluateJavascript(js, null);
51+ }
52+ });
53+ }
54+ };
55+ }
56+
57+ private static class JSaddleCallbacks {
58+ private final long callbacks;
59+ private native void startProcessing(long callbacks);
60+ private native void processMessage(long callbacks, String msg);
61+ private native String processSyncMessage(long callbacks, String msg);
62+
63+ public JSaddleCallbacks(long _callbacks) {
64+ callbacks = _callbacks;
65+ }
66+
67+ @JavascriptInterface
68+ public boolean postReady() {
69+ startProcessing(callbacks);
70+ return true;
71+ }
72+
73+ @JavascriptInterface
74+ public boolean postMessage(final String msg) {
75+ processMessage(callbacks, msg);
76+ return true;
77+ }
78+
79+ @JavascriptInterface
80+ public String syncMessage(final String msg) {
81+ return processSyncMessage(callbacks, msg);
82+ }
3283 }
3384}
reflex-dom/reflex-dom.cabal+5−2
@@ -45,12 +45,16 @@ library
4545 hs-source-dirs: src
4646 if flag(android)
4747 hs-source-dirs: src-android
48+ other-modules: Reflex.Dom.Android.MainWidget
4849 build-depends:
50+ aeson,
4951 android-activity == 0.1.*,
5052 data-default == 0.7.*,
51− jsaddle-clib
53+ jsaddle
5254 c-sources: cbits/MainWidget.c
55+ include-dirs: cbits/include
5356 cpp-options: -DANDROID
57+ build-tools: hsc2hs
5458 build-depends:
5559 base >= 4.7 && < 4.11,
5660 bytestring == 0.10.*,
@@ -94,7 +98,6 @@ library
9498 , Reflex.Dom.Widget.Lazy
9599 , Reflex.Dom.Widget.Resize
96100 , Reflex.Dom.Xhr
97−
98101 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
99102 if flag(expose-all-unfoldings)
100103 ghc-options: -fexpose-all-unfoldings
reflex-dom/src-android/Reflex/Dom/Android/MainWidget.hsdeleted−13
@@ -1,13 +0,0 @@
1−{-# LANGUAGE ForeignFunctionInterface #-}
2−module Reflex.Dom.Android.MainWidget
3− ( startMainWidget
4− ) where
5−
6−import Android.HaskellActivity
7−import Data.ByteString
8−import Foreign.C.String
9−
10−startMainWidget :: HaskellActivity -> ByteString -> IO ()
11−startMainWidget a url = useAsCString url $ startMainWidget_ a
12−
13−foreign import ccall safe "Reflex_Dom_Android_MainWidget_start" startMainWidget_ :: HaskellActivity -> CString -> IO ()
reflex-dom/src-android/Reflex/Dom/Android/MainWidget.hscnew file+106
@@ -0,0 +1,106 @@
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+module Reflex.Dom.Android.MainWidget
4+ ( startMainWidget
5+ ) where
6+
7+import Android.HaskellActivity
8+import Control.Concurrent
9+import Control.Monad
10+import Data.Aeson
11+import Data.ByteString (ByteString)
12+import qualified Data.ByteString as BS
13+import qualified Data.ByteString.Lazy as LBS
14+import Data.IORef
15+import Data.Monoid
16+import Foreign.C.String
17+import Foreign.Marshal.Utils
18+import Foreign.Ptr
19+import Foreign.Storable
20+import Language.Javascript.JSaddle (JSM)
21+import Language.Javascript.JSaddle.Run (runJavaScript)
22+import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
23+
24+#include "MainWidget.h"
25+
26+
27+startMainWidget :: HaskellActivity -> ByteString -> JSM () -> IO ()
28+startMainWidget a url jsm = do
29+ --TODO: Find a way to eventually release this
30+ executorRef <- newIORef $ error "startMainWidget: executor not created yet"
31+ let go batch = do
32+ executor <- readIORef executorRef
33+ BS.useAsCString (LBS.toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $ \cstr -> do
34+ runJS executor cstr
35+ (processResult, processSyncResult, start) <- runJavaScript go jsm
36+ callbacks <- new <=< jsaddleCallbacksToPtrs $ JSaddleCallbacks
37+ { _jsaddleCallbacks_jsaddleStart = void $ forkIO start
38+ , _jsaddleCallbacks_jsaddleResult = \s -> do
39+ case decode $ LBS.fromStrict s of
40+ Nothing -> error $ "jsaddle message decode failed: " <> show s
41+ Just r -> processResult r
42+ , _jsaddleCallbacks_jsaddleSyncResult = \s -> do
43+ case decode $ LBS.fromStrict s of
44+ Nothing -> error $ "jsaddle message decode failed: " <> show s
45+ Just r -> LBS.toStrict . encode <$> processSyncResult r
46+ , _jsaddleCallbacks_jsaddleJsData = LBS.toStrict $ ghcjsHelpers <> "\
47+ \runJSaddleBatch = (function() {\n\
48+ \ " <> initState <> "\n\
49+ \ return function(batch) {\n\
50+ \ " <> runBatch (\a -> "jsaddle.postMessage(JSON.stringify(" <> a <> "));")
51+ (Just (\a -> "JSON.parse(jsaddle.syncMessage(JSON.stringify(" <> a <> ")))")) <> "\
52+ \ };\n\
53+ \})();\n\
54+ \jsaddle.postReady();\n"
55+ }
56+ BS.useAsCString url $ \curl -> do
57+ writeIORef executorRef =<< startMainWidget_ a curl callbacks
58+
59+newtype JSExecutor = JSExecutor { unJSExecutor :: Ptr JSExecutor }
60+
61+foreign import ccall safe "Reflex_Dom_Android_MainWidget_start" startMainWidget_ :: HaskellActivity -> CString -> Ptr JSaddleCallbacksPtrs -> IO JSExecutor
62+
63+foreign import ccall safe "Reflex_Dom_Android_MainWidget_runJS" runJS :: JSExecutor -> CString -> IO ()
64+
65+data JSaddleCallbacks = JSaddleCallbacks
66+ { _jsaddleCallbacks_jsaddleStart :: IO ()
67+ , _jsaddleCallbacks_jsaddleResult :: ByteString -> IO ()
68+ , _jsaddleCallbacks_jsaddleSyncResult :: ByteString -> IO ByteString
69+ , _jsaddleCallbacks_jsaddleJsData :: ByteString
70+ }
71+
72+data JSaddleCallbacksPtrs = JSaddleCallbacksPtrs
73+ { _jsaddleCallbacksPtrs_jsaddleStart :: !(FunPtr (IO ()))
74+ , _jsaddleCallbacksPtrs_jsaddleResult :: !(FunPtr (CString -> IO ()))
75+ , _jsaddleCallbacksPtrs_jsaddleSyncResult :: !(FunPtr (CString -> IO CString))
76+ , _jsaddleCallbacksPtrs_jsaddleJsData :: !CString
77+ }
78+
79+foreign import ccall "wrapper" wrapIO :: IO () -> IO (FunPtr (IO ()))
80+foreign import ccall "wrapper" wrapCStringIO :: (CString -> IO ()) -> IO (FunPtr (CString -> IO ()))
81+foreign import ccall "wrapper" wrapCStringIOCString :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString))
82+
83+--TODO: Get rid of the extra copy step
84+newCStringFromByteString :: ByteString -> IO CString
85+newCStringFromByteString bs = BS.useAsCString bs $ newCString <=< peekCString
86+
87+jsaddleCallbacksToPtrs :: JSaddleCallbacks -> IO JSaddleCallbacksPtrs
88+jsaddleCallbacksToPtrs jc = JSaddleCallbacksPtrs
89+ <$> wrapIO (_jsaddleCallbacks_jsaddleStart jc)
90+ <*> wrapCStringIO (\cstr -> _jsaddleCallbacks_jsaddleResult jc =<< BS.packCString cstr)
91+ <*> wrapCStringIOCString (\cstr -> newCStringFromByteString =<< _jsaddleCallbacks_jsaddleSyncResult jc =<< BS.packCString cstr)
92+ <*> newCStringFromByteString (_jsaddleCallbacks_jsaddleJsData jc)
93+
94+instance Storable JSaddleCallbacksPtrs where
95+ sizeOf _ = #{size JSaddleCallbacks}
96+ alignment _ = #{alignment JSaddleCallbacks}
97+ poke p jc = do
98+ #{poke JSaddleCallbacks, jsaddleStart} p $ _jsaddleCallbacksPtrs_jsaddleStart jc
99+ #{poke JSaddleCallbacks, jsaddleResult} p $ _jsaddleCallbacksPtrs_jsaddleResult jc
100+ #{poke JSaddleCallbacks, jsaddleSyncResult} p $ _jsaddleCallbacksPtrs_jsaddleSyncResult jc
101+ #{poke JSaddleCallbacks, jsaddleJsData} p $ _jsaddleCallbacksPtrs_jsaddleJsData jc
102+ peek p = JSaddleCallbacksPtrs
103+ <$> #{peek JSaddleCallbacks, jsaddleStart} p
104+ <*> #{peek JSaddleCallbacks, jsaddleResult} p
105+ <*> #{peek JSaddleCallbacks, jsaddleSyncResult} p
106+ <*> #{peek JSaddleCallbacks, jsaddleJsData} p
reflex-dom/src/Reflex/Dom/Internal.hs+10−3
@@ -28,11 +28,18 @@ import Control.Concurrent
2828import Data.Default
2929import Data.String
3030import Reflex.Dom.Android.MainWidget
31−run _ = do
32− continueWithCallbacks $ traceActivityCallbacks $ def
31+import System.IO
32+import Language.Javascript.JSaddle (JSM)
33+
34+run :: JSM () -> IO ()
35+run jsm = do
36+ hSetBuffering stdout LineBuffering
37+ hSetBuffering stderr LineBuffering
38+ continueWithCallbacks $ def
3339 { _activityCallbacks_onCreate = \_ -> do
3440 a <- getHaskellActivity
35− startMainWidget a $ fromString "data:,Hello%2C%20World!" -- "file:///android_asset/index.html"
41+ let startPage = fromString "file:///android_asset/index.html"
42+ startMainWidget a startPage jsm
3643 }
3744 forever $ threadDelay 1000000000
3845#else