diff --git a/jni/deltachat-core-rust b/jni/deltachat-core-rust index ca201b157..bf02ea310 160000 --- a/jni/deltachat-core-rust +++ b/jni/deltachat-core-rust @@ -1 +1 @@ -Subproject commit ca201b157e53465a4cd3461e8a243b00fc68eb20 +Subproject commit bf02ea3108d9deae46056dc80b389be9192133e4 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 1622cda66..4369a62d3 100644 --- a/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java @@ -38,6 +38,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; @@ -52,7 +54,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; @@ -65,6 +67,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 = ""; @@ -137,6 +140,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); @@ -161,6 +165,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); @@ -221,6 +226,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE @Override protected void onDestroy() { DcHelper.getEventCenter(this.getApplicationContext()).removeObservers(this); + leaveRealtimeChannel(); super.onDestroy(); } @@ -346,12 +352,25 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE return res; } + private void callJavaScriptFunction(String func) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript("window." + 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"); - webView.loadUrl("javascript:window.__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(() -> { @@ -429,6 +448,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 deltalab() { @@ -526,5 +555,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 32e6af5d2..8469b5453 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java @@ -81,6 +81,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 5f9fa6d2f..a787510bc 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -7,6 +7,7 @@ import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_E2EE_ENABLED; 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_SHOW_EMAILS; +import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_WEBXDC_REALTIME_ENABLED; import static org.thoughtcrime.securesms.connect.DcHelper.getRpc; import android.Manifest; @@ -63,6 +64,7 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment CheckBoxPreference bccSelfCheckbox; CheckBoxPreference mvboxMoveCheckbox; CheckBoxPreference onlyFetchMvboxCheckbox; + CheckBoxPreference webxdcRealtimeCheckbox; @Override public void onCreate(Bundle paramBundle) { @@ -114,6 +116,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; + }); + Preference manageKeys = this.findPreference("pref_manage_keys"); manageKeys.setOnPreferenceClickListener(new ManageKeysListener()); @@ -183,6 +192,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)); } @Override diff --git a/src/main/res/raw/webxdc.js b/src/main/res/raw/webxdc.js index 3a3ad156d..019238055 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,6 +16,31 @@ 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 { deltalab: InternalJSApi.deltalab(), @@ -24,6 +50,12 @@ window.webxdc = (() => { 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 b7b55b12a..9a21754e0 100644 --- a/src/main/res/xml/preferences_advanced.xml +++ b/src/main/res/xml/preferences_advanced.xml @@ -20,6 +20,12 @@ android:title="@string/videochat_instance" android:summary="@string/none"/> + +