mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 01:55:50 +01:00
Bangle.js: Adding built-in app-loader view (available via app management icon). Only available on internet-enabled builds (it's a webview)
This commit is contained in:
parent
bdcaeae177
commit
e40bd79fbf
@ -468,6 +468,11 @@
|
||||
<activity
|
||||
android:name=".activities.AndroidPairingActivity"
|
||||
android:label="@string/title_activity_android_pairing" />
|
||||
<activity
|
||||
android:name=".devices.banglejs.AppsManagementActivity"
|
||||
android:label="@string/title_activity_appmanager"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".devices.miband.MiBandPairingActivity"
|
||||
android:label="@string/title_activity_mi_band_pairing" />
|
||||
|
@ -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";
|
||||
|
@ -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')");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;i<bytes.length;i+=mtuSize) {
|
||||
int l = bytes.length-i;
|
||||
@ -271,26 +270,69 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an object to a JSON string. see jsonToString
|
||||
private String jsonToStringInternal(Object v) {
|
||||
if (v instanceof String) {
|
||||
/* Convert a string, escaping chars we can't send over out UART connection */
|
||||
String s = (String)v;
|
||||
String json = "\"";
|
||||
for (int i=0;i<s.length();i++) {
|
||||
int ch = (int)s.charAt(i);
|
||||
if (ch<8) json += "\\"+ch;
|
||||
else if (ch==8) json += "\\b";
|
||||
else if (ch==9) json += "\\t";
|
||||
else if (ch==10) json += "\\n";
|
||||
else if (ch==11) json += "\\v";
|
||||
else if (ch==12) json += "\\f";
|
||||
else if (ch==34) json += "\\\""; // quote
|
||||
else if (ch==92) json += "\\\\"; // slash
|
||||
else if (ch<32 || ch==26 || ch==27 || ch==127 || ch==173) json += "\\x"+Integer.toHexString((ch&255)|256).substring(1);
|
||||
else json += s.charAt(i);
|
||||
}
|
||||
return json + "\"";
|
||||
} else if (v instanceof JSONArray) {
|
||||
JSONArray a = (JSONArray)v;
|
||||
String json = "[";
|
||||
for (int i=0;i<a.length();i++) {
|
||||
if (i>0) 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<String> 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<String> 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;
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -182,6 +182,8 @@
|
||||
<string name="pref_summary_transliteration">Enable this if your device has no support for your language\'s font</string>
|
||||
<string name="pref_title_banglejs_text_bitmap">Text as Bitmaps</string>
|
||||
<string name="pref_summary_banglejs_text_bitmap">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</string>
|
||||
<string name="pref_title_banglejs_webview_url">App loader URL</string>
|
||||
<string name="pref_summary_banglejs_webview_url">If you want a custom app loader put your https://…/android.html URL here. Otherwise leave blank for https://banglejs.com/apps</string>
|
||||
<string name="pref_title_rtl">Right-To-Left</string>
|
||||
<string name="pref_summary_rtl">Enable this if your device can not show right-to-left languages</string>
|
||||
<string name="pref_rtl_max_line_length">Right-To-Left Max Line Length</string>
|
||||
|
@ -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" />
|
||||
</androidx.preference.PreferenceScreen>
|
||||
<EditTextPreference
|
||||
android:defaultValue=""
|
||||
android:icon="@drawable/ic_engineering"
|
||||
android:key="banglejs_webview_url"
|
||||
android:summary="@string/pref_summary_banglejs_webview_url"
|
||||
android:title="@string/pref_title_banglejs_webview_url" />
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
Loading…
Reference in New Issue
Block a user