diff --git a/src/main/java/com/b44t/messenger/rpc/Rpc.java b/src/main/java/com/b44t/messenger/rpc/Rpc.java index 88a65fbd4..e7ae1b9e3 100644 --- a/src/main/java/com/b44t/messenger/rpc/Rpc.java +++ b/src/main/java/com/b44t/messenger/rpc/Rpc.java @@ -126,6 +126,18 @@ public class Rpc { return getResult("draft_self_report", accountId).getAsInt(); } + public void sendWebxdcRealtimeData(Integer accountId, Integer instanceMsgId, List data) throws RpcException { + getResult("send_webxdc_realtime_data", accountId, instanceMsgId, data); + } + + public void sendWebxdcRealtimeAdvertisement(Integer accountId, Integer instanceMsgId) throws RpcException { + getResult("send_webxdc_realtime_advertisement", accountId, instanceMsgId); + } + + public void leaveWebxdcRealtime(Integer accountId, Integer instanceMessageId) throws RpcException { + getResult("leave_webxdc_realtime", accountId, instanceMessageId); + } + private static class Request { private final String jsonrpc = "2.0"; public final String method; diff --git a/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java b/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java index d2756618e..b126d0c10 100644 --- a/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java @@ -37,6 +37,8 @@ import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcMsg; +import com.b44t.messenger.rpc.Rpc; +import com.b44t.messenger.rpc.RpcException; import org.json.JSONObject; import org.thoughtcrime.securesms.connect.AccountManager; @@ -51,7 +53,7 @@ import java.io.ByteArrayInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; - +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -64,6 +66,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE private ValueCallback filePathCallback; private DcContext dcContext; + private Rpc rpc; private DcMsg dcAppMsg; private String baseURL; private String sourceCodeUrl = ""; @@ -136,6 +139,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE @Override protected void onCreate(Bundle state, boolean ready) { super.onCreate(state, ready); + rpc = DcHelper.getRpc(this); Bundle b = getIntent().getExtras(); hideActionBar = b.getBoolean(EXTRA_HIDE_ACTION_BAR, false); @@ -164,6 +168,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE DcEventCenter eventCenter = DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext()); eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this); eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this); + eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this); int appMessageId = b.getInt(EXTRA_APP_MSG_ID); int accountId = b.getInt(EXTRA_ACCOUNT_ID); @@ -217,6 +222,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE @Override protected void onDestroy() { DcHelper.getEventCenter(this.getApplicationContext()).removeObservers(this); + leaveRealtimeChannel(); super.onDestroy(); } @@ -341,16 +347,29 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE return res; } + private void callJavaScriptFunction(String func) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (internetAccess) { + webView.evaluateJavascript("window." + func + ";", null); + } else { + webView.evaluateJavascript("document.getElementById('frame').contentWindow." + func + ";", null); + } + } + } + @Override public void handleEvent(@NonNull DcEvent event) { int eventId = event.getId(); if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE && event.getData1Int() == dcAppMsg.getId())) { - Log.i(TAG, "handleEvent"); - if (internetAccess) { - webView.loadUrl("javascript:window.__webxdcUpdate();"); - } else { - webView.loadUrl("javascript:document.getElementById('frame').contentWindow.__webxdcUpdate();"); + Log.i(TAG, "handling status update event"); + callJavaScriptFunction("__webxdcUpdate()"); + } else if ((eventId == DcContext.DC_EVENT_WEBXDC_REALTIME_DATA && event.getData1Int() == dcAppMsg.getId())) { + Log.i(TAG, "handling realtime data event"); + StringBuilder data = new StringBuilder(); + for (byte b : event.getData2Blob()) { + data.append(((int) b) + ","); } + callJavaScriptFunction("__webxdcRealtimeData([" + data + "])"); } else if ((eventId == DcContext.DC_EVENT_MSGS_CHANGED && event.getData2Int() == dcAppMsg.getId())) { this.dcAppMsg = this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db Util.runOnAnyBackgroundThread(() -> { @@ -428,6 +447,16 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE super.onActivityResult(reqCode, resultCode, data); } + private void leaveRealtimeChannel() { + int accountId = dcContext.getAccountId(); + int msgId = dcAppMsg.getId(); + try { + rpc.leaveWebxdcRealtime(accountId, msgId); + } catch (RpcException e) { + e.printStackTrace(); + } + } + class InternalJSApi { @JavascriptInterface public String selfAddr() { @@ -496,5 +525,36 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE return e.toString(); } } + + /** @noinspection unused*/ + @JavascriptInterface + public void sendRealtimeAdvertisement() { + int accountId = WebxdcActivity.this.dcContext.getAccountId(); + int msgId = WebxdcActivity.this.dcAppMsg.getId(); + try { + WebxdcActivity.this.rpc.sendWebxdcRealtimeAdvertisement(accountId, msgId); + } catch (RpcException e) { + e.printStackTrace(); + } + } + + /** @noinspection unused*/ + @JavascriptInterface + public void leaveRealtimeChannel() { + WebxdcActivity.this.leaveRealtimeChannel(); + } + + /** @noinspection unused*/ + @JavascriptInterface + public void sendRealtimeData(String jsonData) { + int accountId = WebxdcActivity.this.dcContext.getAccountId(); + int msgId = WebxdcActivity.this.dcAppMsg.getId(); + try { + Integer[] data = JsonUtils.fromJson(jsonData, Integer[].class); + WebxdcActivity.this.rpc.sendWebxdcRealtimeData(accountId, msgId, Arrays.asList(data)); + } catch (IOException | RpcException e) { + e.printStackTrace(); + } + } } } diff --git a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java index 09a0ae27d..3e01cbe35 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java @@ -78,6 +78,7 @@ public class DcHelper { public static final String CONFIG_SOCKS5_USER = "socks5_user"; public static final String CONFIG_SOCKS5_PASSWORD = "socks5_password"; public static final String CONFIG_VERIFIED_ONE_ON_ONE_CHATS = "verified_one_on_one_chats"; + public static final String CONFIG_WEBXDC_REALTIME_ENABLED = "webxdc_realtime_enabled"; public static DcContext getContext(@NonNull Context context) { return ApplicationContext.getInstance(context).dcContext; diff --git a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index db4360650..ed633a70a 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -8,6 +8,7 @@ import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_MVBOX_MOVE; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_ONLY_FETCH_MVBOX; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SENTBOX_WATCH; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_SHOW_EMAILS; +import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_WEBXDC_REALTIME_ENABLED; import static org.thoughtcrime.securesms.connect.DcHelper.getRpc; import android.Manifest; @@ -67,6 +68,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment CheckBoxPreference bccSelfCheckbox; CheckBoxPreference mvboxMoveCheckbox; CheckBoxPreference onlyFetchMvboxCheckbox; + CheckBoxPreference webxdcRealtimeCheckbox; CheckBoxPreference showSystemContacts; @Override @@ -126,6 +128,13 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment } })); + webxdcRealtimeCheckbox = (CheckBoxPreference) this.findPreference("pref_webxdc_realtime_enabled"); + webxdcRealtimeCheckbox.setOnPreferenceChangeListener((preference, newValue) -> { + boolean enabled = (Boolean) newValue; + dcContext.setConfigInt(CONFIG_WEBXDC_REALTIME_ENABLED, enabled? 1 : 0); + return true; + }); + showSystemContacts = (CheckBoxPreference) this.findPreference("pref_show_system_contacts"); showSystemContacts.setOnPreferenceChangeListener((preference, newValue) -> { boolean enabled = (Boolean) newValue; @@ -237,6 +246,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment bccSelfCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_BCC_SELF)); mvboxMoveCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_MVBOX_MOVE)); onlyFetchMvboxCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_ONLY_FETCH_MVBOX)); + webxdcRealtimeCheckbox.setChecked(0!=dcContext.getConfigInt(CONFIG_WEBXDC_REALTIME_ENABLED)); showSystemContacts.setChecked(0!=dcContext.getConfigInt("ui.android.show_system_contacts")); } diff --git a/src/main/res/raw/webxdc.js b/src/main/res/raw/webxdc.js index 2b99c2760..9d23bdbf5 100644 --- a/src/main/res/raw/webxdc.js +++ b/src/main/res/raw/webxdc.js @@ -1,7 +1,8 @@ window.webxdc = (() => { - let setUpdateListenerPromise = null - var update_listener = () => {}; - var last_serial = 0; + let setUpdateListenerPromise = null; + let update_listener = () => {}; + let last_serial = 0; + let realtimeChannel = null; window.__webxdcUpdate = () => { var updates = JSON.parse(InternalJSApi.getStatusUpdates(last_serial)); @@ -15,11 +16,42 @@ window.webxdc = (() => { } }; + window.__webxdcRealtimeData = (intArray) => { + if (realtimeChannel) { + realtimeChannel.__receive(Uint8Array.from(intArray)); + } + }; + + const createRealtimeChannel = () => { + let listener = null; + return { + setListener: (li) => listener = li, + leave: () => InternalJSApi.leaveRealtimeChannel(), + send: (data) => { + if ((!data) instanceof Uint8Array) { + throw new Error('realtime listener data must be a Uint8Array') + } + InternalJSApi.sendRealtimeData(JSON.stringify(Array.from(data))); + }, + __receive: (data) => { + if (listener) { + listener(data); + } + }, + }; + } + return { selfAddr: InternalJSApi.selfAddr(), selfName: InternalJSApi.selfName(), + joinRealtimeChannel: () => { + realtimeChannel = createRealtimeChannel(); + InternalJSApi.sendRealtimeAdvertisement(); + return realtimeChannel; + }, + setUpdateListener: (cb, serial) => { last_serial = typeof serial === "undefined" ? 0 : parseInt(serial); update_listener = cb diff --git a/src/main/res/xml/preferences_advanced.xml b/src/main/res/xml/preferences_advanced.xml index d826188fd..9098d943d 100644 --- a/src/main/res/xml/preferences_advanced.xml +++ b/src/main/res/xml/preferences_advanced.xml @@ -30,6 +30,12 @@ android:key="pref_location_streaming_enabled" android:title="@string/pref_on_demand_location_streaming"/> + +