diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 120e6c741..0e0ab834b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -468,6 +468,11 @@
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
index 3dd558dc3..a07af38d8 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
@@ -45,6 +45,7 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_DEVICE_INTENTS = "device_intents";
public static final String PREF_BANGLEJS_TEXT_BITMAP = "banglejs_text_bitmap";
+ public static final String PREF_BANGLEJS_WEBVIEW_URL = "banglejs_webview_url";
public static final String PREF_DISCONNECT_NOTIFICATION = "disconnect_notification";
public static final String PREF_DISCONNECT_NOTIFICATION_START = "disconnect_notification_start";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
new file mode 100644
index 000000000..7e9e8105c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
@@ -0,0 +1,188 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.banglejs;
+
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_WEBVIEW_URL;
+
+public class AppsManagementActivity extends AbstractGBActivity {
+ private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class);
+
+ private WebView webView;
+ private GBDevice mGBDevice;
+ private DeviceCoordinator mCoordinator;
+ /// It seems we can get duplicate broadcasts sometimes - so this helps to avoid that
+ private int deviceRxSeq = -1;
+
+ public AppsManagementActivity() {
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_banglejs_apps_management);
+
+ Intent intent = getIntent();
+ Bundle bundle = intent.getExtras();
+ if (bundle != null) {
+ mGBDevice = bundle.getParcelable(GBDevice.EXTRA_DEVICE);
+ } else {
+ throw new IllegalArgumentException("Must provide a device when invoking this activity");
+ }
+ mCoordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
+ }
+
+ private void toast(String data) {
+ GB.toast(data, Toast.LENGTH_LONG, GB.INFO);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ webView.destroy();
+ webView = null;
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver);
+ finish();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ IntentFilter commandFilter = new IntentFilter();
+ commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+ commandFilter.addAction(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX);
+ LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, commandFilter);
+ initViews();
+ }
+
+ BroadcastReceiver deviceUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case BangleJSDeviceSupport.BANGLEJS_COMMAND_RX: {
+ String data = String.valueOf(intent.getExtras().get("DATA"));
+ int seq = intent.getIntExtra("SEQ",0);
+ LOG.info("WebView TX: " + data + "("+seq+")");
+ if (seq==deviceRxSeq) {
+ LOG.info("WebView TX DUPLICATE AND IGNORED");
+ } else {
+ deviceRxSeq = seq;
+ bangleRxData(data);
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ public class WebViewInterface {
+ Context mContext;
+
+ WebViewInterface(Context c) {
+ mContext = c;
+ }
+
+ /// Called from the WebView when data needs to be sent to the Bangle
+ @JavascriptInterface
+ public void bangleTx(String data) {
+ LOG.info("WebView RX: " + data);
+ bangleTxData(data);
+ }
+
+ }
+
+ // Called when data received from Bangle.js - push data to the WebView
+ public void bangleRxData(String data) {
+ JSONArray s = new JSONArray();
+ s.put(data);
+ String ss = s.toString();
+ final String js = "bangleRx("+ss.substring(1, ss.length()-1)+");";
+ LOG.info("WebView TX cmd: " + js);
+ if (webView!=null) webView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+ webView.evaluateJavascript(js, null);
+ } else {
+ webView.loadUrl("javascript: "+js);
+ }
+ }
+ });
+ }
+
+ // Called to send data to Bangle.js
+ public void bangleTxData(String data) {
+ Intent intent = new Intent(BangleJSDeviceSupport.BANGLEJS_COMMAND_TX);
+ intent.putExtra("DATA", data);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
+ private void initViews() {
+ //https://stackoverflow.com/questions/4325639/android-calling-javascript-functions-in-webview
+ webView = findViewById(R.id.webview);
+ webView.setWebViewClient(new WebViewClient());
+ WebSettings settings = webView.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setDatabaseEnabled(true);
+ settings.setDomStorageEnabled(true);
+ settings.setUseWideViewPort(true);
+ settings.setLoadWithOverviewMode(true);
+ String databasePath = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
+ settings.setDatabasePath(databasePath);
+ webView.addJavascriptInterface(new WebViewInterface(this), "Android");
+ webView.setWebContentsDebuggingEnabled(true); // FIXME
+
+ Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()));
+ String url = devicePrefs.getString(PREF_BANGLEJS_WEBVIEW_URL, "").trim();
+ if (url.isEmpty()) url = "https://banglejs.com/apps/android.html";
+ webView.loadUrl(url);
+
+ webView.setWebViewClient(new WebViewClient(){
+ public void onPageFinished(WebView view, String weburl){
+ //webView.loadUrl("javascript:showToast('WebView in Espruino')");
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
index ea5a39764..6a9416a97 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
@@ -34,6 +34,7 @@ import java.util.Vector;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
@@ -135,21 +136,18 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator {
return true;
}
- @Override
- public boolean supportsAppsManagement() {
- return false;
- }
-
@Override
public int getAlarmSlotCount() {
return 10;
}
@Override
- public Class extends Activity> getAppsManagementActivity() {
- return null;
- }
+ public boolean supportsAppsManagement() { return BuildConfig.INTERNET_ACCESS; }
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return BuildConfig.INTERNET_ACCESS ? AppsManagementActivity.class : null;
+ }
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
index c9cdd7b56..647f96487 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
@@ -258,9 +258,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
/// Write a string of data, and chunk it up
private void uartTx(TransactionBuilder builder, String str) {
+ byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1);
LOG.info("UART TX: " + str);
- byte[] bytes;
- bytes = str.getBytes(StandardCharsets.ISO_8859_1);
// FIXME: somehow this is still giving us UTF8 data when we put images in strings. Maybe JSON.stringify is converting to UTF-8?
for (int i=0;i0) json += ",";
+ Object o = null;
+ try {
+ o = a.get(i);
+ } catch (JSONException e) {
+ LOG.warn("jsonToString array error: " + e.getLocalizedMessage());
+ }
+ json += jsonToStringInternal(o);
+ }
+ return json+"]";
+ } else if (v instanceof JSONObject) {
+ JSONObject obj = (JSONObject)v;
+ String json = "{";
+ Iterator iter = obj.keys();
+ while (iter.hasNext()) {
+ String key = iter.next();
+ Object o = null;
+ try {
+ o = obj.get(key);
+ } catch (JSONException e) {
+ LOG.warn("jsonToString object error: " + e.getLocalizedMessage());
+ }
+ json += key+":"+jsonToStringInternal(o);
+ if (iter.hasNext()) json+=",";
+ }
+ return json+"}";
+ } // else int/double/null
+ return v.toString();
+ }
- /// Write a string of data, and chunk it up
+ /// Convert a JSON object to a JSON String (NOT 100% JSON compliant)
public String jsonToString(JSONObject jsonObj) {
- String json = jsonObj.toString();
- // toString creates '\u0000' instead of '\0'
- // FIXME: there have got to be nicer ways of handling this - maybe we just make our own JSON.toString (see below)
- json = json.replaceAll("\\\\u000([01234567])", "\\\\$1");
- json = json.replaceAll("\\\\u00([0123456789abcdef][0123456789abcdef])", "\\\\x$1");
- return json;
- /*String json = "{";
- Iterator iter = jsonObj.keys();
- while (iter.hasNext()) {
- String key = iter.next();
- Object v = jsonObj.get(key);
- if (v instanceof Integer) {
- // ...
- } else // ..
- if (iter.hasNext()) json+=",";
- }
- return json+"}";*/
+ /* jsonObj.toString() works but breaks char codes>128 (encodes as UTF8?) and also uses
+ \u0000 when just \0 would do (and so on).
+
+ So we do it manually, which can be more compact anyway.
+ This is JSON-ish, so not exactly as per JSON1 spec but good enough for Espruino.
+ */
+ return jsonToStringInternal(jsonObj);
}
/// Write a JSON object of data
@@ -327,10 +369,10 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
private void handleUartRxLine(String line) {
LOG.info("UART RX LINE: " + line);
-
+ if (line.length()==0) return;
if (">Uncaught ReferenceError: \"GB\" is not defined".equals(line))
GB.toast(getContext(), "Gadgetbridge plugin not installed on Bangle.js", Toast.LENGTH_LONG, GB.ERROR);
- else if (line.length() > 0 && line.charAt(0)=='{') {
+ else if (line.charAt(0)=='{') {
// JSON - we hope!
try {
JSONObject json = new JSONObject(line);
@@ -567,6 +609,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
receivedLine = receivedLine.substring(p+1);
handleUartRxLine(line);
}
+ // Send an intent with new data
+ Intent intent = new Intent(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX);
+ intent.putExtra("DATA", packetStr);
+ intent.putExtra("SEQ", bangleCommandSeq++);
+ LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
return false;
}
diff --git a/app/src/main/res/layout/activity_banglejs_apps_management.xml b/app/src/main/res/layout/activity_banglejs_apps_management.xml
new file mode 100644
index 000000000..08188a975
--- /dev/null
+++ b/app/src/main/res/layout/activity_banglejs_apps_management.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e0ca45df0..e7eb1ca37 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -182,6 +182,8 @@
Enable this if your device has no support for your language\'s font
Text as Bitmaps
If a word cannot be rendered with the watch\'s font, render it to a bitmap in Gadgetbridge and display the bitmap on the watch
+ App loader URL
+ If you want a custom app loader put your https://…/android.html URL here. Otherwise leave blank for https://banglejs.com/apps
Right-To-Left
Enable this if your device can not show right-to-left languages
Right-To-Left Max Line Length
diff --git a/app/src/main/res/xml/devicesettings_banglejs.xml b/app/src/main/res/xml/devicesettings_banglejs.xml
index 9270f8baf..60395238b 100644
--- a/app/src/main/res/xml/devicesettings_banglejs.xml
+++ b/app/src/main/res/xml/devicesettings_banglejs.xml
@@ -6,4 +6,10 @@
android:key="banglejs_text_bitmap"
android:summary="@string/pref_summary_banglejs_text_bitmap"
android:title="@string/pref_title_banglejs_text_bitmap" />
-
\ No newline at end of file
+
+