From eddbde313b041e66f4b2a9c593ea78eddb607238 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 10 Feb 2022 14:19:32 +0000 Subject: [PATCH 01/39] Bangle.js restrict icon size even if it's already a bitmap --- .../banglejs/BangleJSDeviceSupport.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) 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 51357d254..eb0754e23 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 @@ -624,23 +624,34 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { /** Convert a drawable to a bitmap, for use with bitmapToEspruino */ public static Bitmap drawableToBitmap(Drawable drawable) { + final int maxWidth = 32; + final int maxHeight = 32; + /* Return bitmap directly but only if it's small enough. It could be + we have a bitmap but it's just too big to send direct to the bangle */ if (drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; - if (bitmapDrawable.getBitmap() != null) { - return bitmapDrawable.getBitmap(); - } + Bitmap bmp = bitmapDrawable.getBitmap(); + if (bmp != null && bmp.getWidth()<=maxWidth && bmp.getHeight()<=maxHeight) + return bmp; } - int w = 24; - int h = 24; + /* Otherwise render this to a bitmap ourselves.. work out size */ + int w = maxWidth; + int h = maxHeight; if (drawable.getIntrinsicWidth() > 0 && drawable.getIntrinsicHeight() > 0) { w = drawable.getIntrinsicWidth(); h = drawable.getIntrinsicHeight(); - // don't allocate anything too big - if (w>24) w=24; - if (h>24) h=24; + // don't allocate anything too big, but keep the ratio + if (w>maxWidth) { + h = h * maxWidth / w; + w = maxWidth; + } + if (h>maxHeight) { + w = w * maxHeight / h; + h = maxHeight; + } } + /* render */ Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel - Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); From 2a53219a14accdf5c78e02cc3af57f3940baf008 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 10 Feb 2022 15:49:28 +0000 Subject: [PATCH 02/39] Bangle.js build flavor --- app/build.gradle | 17 +++++ .../res/drawable/ic_launcher_foreground.xml | 72 +++++++++++++++++++ app/src/banglejs/res/values/strings.xml | 8 +++ 3 files changed, 97 insertions(+) create mode 100644 app/src/banglejs/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/banglejs/res/values/strings.xml diff --git a/app/build.gradle b/app/build.gradle index c20245f8c..80db15028 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,6 +61,7 @@ android { vectorDrawables.useSupportLibrary = true multiDexEnabled true buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\"" + buildConfigField "boolean", "INTERNET_ACCESS", "false" resValue "string", "pebble_content_provider", "com.getpebble.android.provider" } signingConfigs { @@ -122,6 +123,22 @@ android { } + flavorDimensions "device_type" + productFlavors { + main { + // the default build product flavor + dimension "device_type" + //applicationIdSuffix "" + //versionNameSuffix "" + } + banglejs { + dimension "device_type" + applicationIdSuffix ".banglejs" + versionNameSuffix "-banglejs" + buildConfigField "boolean", "INTERNET_ACCESS", "true" + } + } + lintOptions { abortOnError ABORT_ON_CHECK_FAILURE lintConfig file("${project.rootDir}/config/lint/lint.xml") diff --git a/app/src/banglejs/res/drawable/ic_launcher_foreground.xml b/app/src/banglejs/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..92a455296 --- /dev/null +++ b/app/src/banglejs/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/banglejs/res/values/strings.xml b/app/src/banglejs/res/values/strings.xml new file mode 100644 index 000000000..5bc9037a5 --- /dev/null +++ b/app/src/banglejs/res/values/strings.xml @@ -0,0 +1,8 @@ + + + Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + About Bangle.js Gadgetbridge + Android companion app for Bangle.js built on top of the Gadgetbridge project + Bangle.js running + From c597adf27fb4628ca523c40a99e78aa5a613b21e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 10 Feb 2022 16:49:17 +0000 Subject: [PATCH 03/39] Adding internet connectivity with 'http' Gadgetbridge event FIXME - still needs a setting to enable that is off by default --- app/build.gradle | 1 + app/src/banglejs/AndroidManifest.xml | 5 +++ .../activities/ControlCenterv2.java | 7 ++++ .../banglejs/BangleJSDeviceSupport.java | 40 +++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 app/src/banglejs/AndroidManifest.xml diff --git a/app/build.gradle b/app/build.gradle index 80db15028..159126200 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -213,6 +213,7 @@ dependencies { implementation 'com.github.wax911:android-emojify:0.1.7' implementation 'com.google.protobuf:protobuf-lite:3.0.1' implementation "androidx.multidex:multidex:2.0.1" + implementation 'com.android.volley:volley:1.2.1' } preBuild.dependsOn(":GBDaoGenerator:genSources") diff --git a/app/src/banglejs/AndroidManifest.xml b/app/src/banglejs/AndroidManifest.xml new file mode 100644 index 000000000..8478e0e63 --- /dev/null +++ b/app/src/banglejs/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 2b2e82205..e63c4d23e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -65,6 +65,7 @@ import java.util.Set; import de.cketti.library.changelog.ChangeLog; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapterv2; import nodomain.freeyourgadget.gadgetbridge.database.DBAccess; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; @@ -438,6 +439,12 @@ public class ControlCenterv2 extends AppCompatActivity } } + if (BuildConfig.INTERNET_ACCESS) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) { + wantedPermissions.add(Manifest.permission.INTERNET); + } + } + if (!wantedPermissions.isEmpty()) { Prefs prefs = GBApplication.getPrefs(); // If this is not the first run, we can rely on 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 eb0754e23..542fa5fbc 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 @@ -31,6 +31,13 @@ import android.widget.Toast; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.android.volley.Request; +import com.android.volley.Response; +import com.android.volley.RequestQueue; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.StringRequest; +import com.android.volley.toolbox.Volley; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -267,6 +274,39 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } } break; + case "http": { + // FIXME: This should be behind a default-off option in Gadgetbridge settings + RequestQueue queue = Volley.newRequestQueue(getContext()); + String url = json.getString("url"); + // Request a string response from the provided URL. + StringRequest stringRequest = new StringRequest(Request.Method.GET, url, + new Response.Listener() { + @Override + public void onResponse(String response) { + JSONObject o = new JSONObject(); + try { + o.put("t", "http"); + o.put("resp", response); + } catch (JSONException e) { + GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + uartTxJSON("http", o); + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + JSONObject o = new JSONObject(); + try { + o.put("t", "http"); + o.put("err", error.toString()); + } catch (JSONException e) { + GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + uartTxJSON("http", o); + } + }); + queue.add(stringRequest); + } break; } } From cc9d9a16e2034491b20996c98cef92c7e7abefa5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 23 Mar 2022 11:15:01 +0000 Subject: [PATCH 04/39] rename application ID to com.espruino.gadgetbridge.banglejs --- README.md | 1 + app/build.gradle | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 3eb5905e6..f5037d308 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ vendor's servers. - T-Rex [**\[!\]**](#special-pairing-procedures) - Verge Lite [**\[!\]**](#special-pairing-procedures) - [X ](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-5) [**\[!\]**](#special-pairing-procedures) +* Bangle.js - BFH-16 - [Casio](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Casio) - Casio GB-X6900B diff --git a/app/build.gradle b/app/build.gradle index 159126200..5a34e5732 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,6 +133,7 @@ android { } banglejs { dimension "device_type" + applicationId "com.espruino.gadgetbridge" applicationIdSuffix ".banglejs" versionNameSuffix "-banglejs" buildConfigField "boolean", "INTERNET_ACCESS", "true" From f26f3b0a15bc1d5b0f5706e66f35d57a8c456245 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 23 Mar 2022 13:08:12 +0000 Subject: [PATCH 05/39] readme link tweaks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5037d308..a264f89d8 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ vendor's servers. - T-Rex [**\[!\]**](#special-pairing-procedures) - Verge Lite [**\[!\]**](#special-pairing-procedures) - [X ](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-5) [**\[!\]**](#special-pairing-procedures) -* Bangle.js +- [Bangle.js](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Bangle.js) - BFH-16 - [Casio](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Casio) - Casio GB-X6900B From 1b412af3c79883594d9d24f212bb60f0470711a6 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 23 Mar 2022 15:35:09 +0000 Subject: [PATCH 06/39] Bangle.js: Add ability to filter HTTP request by xpath, and extra logging if unknown JSON --- .../banglejs/BangleJSDeviceSupport.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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 e79089800..c245a6351 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 @@ -43,8 +43,10 @@ import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; import java.io.IOException; +import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Calendar; @@ -85,6 +87,9 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.database.DBHelper.*; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; + public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class); private BluetoothGattCharacteristic rxCharacteristic = null; @@ -168,6 +173,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { // JSON - we hope! try { JSONObject json = new JSONObject(line); + LOG.info("UART RX JSON parsed successfully"); handleUartRxJSON(json); } catch (JSONException e) { GB.toast(getContext(), "Malformed JSON from Bangle.js: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); @@ -176,7 +182,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } private void handleUartRxJSON(JSONObject json) throws JSONException { - switch (json.getString("t")) { + String packetType = json.getString("t"); + switch (packetType) { case "info": GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.INFO); break; @@ -281,12 +288,28 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { // FIXME: This should be behind a default-off option in Gadgetbridge settings RequestQueue queue = Volley.newRequestQueue(getContext()); String url = json.getString("url"); + String _xmlPath = ""; + try { _xmlPath = json.getString("xpath"); } catch (JSONException e) {} + final String xmlPath = _xmlPath; // Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { JSONObject o = new JSONObject(); + if (xmlPath.length()!=0) { + try { + InputSource inputXML = new InputSource( new StringReader( response ) ); + XPath xPath = XPathFactory.newInstance().newXPath(); + response = xPath.evaluate(xmlPath, inputXML); + } catch (Exception error) { + try { + o.put("err", error.toString()); + } catch (JSONException e) { + GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + } try { o.put("t", "http"); o.put("resp", response); @@ -310,6 +333,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { }); queue.add(stringRequest); } break; + default : { + LOG.info("UART RX JSON packet type '"+packetType+"' not understood."); + } } } From 2f11b875e31607ec429827610d6d1419e72d56c2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 23 Mar 2022 16:20:50 +0000 Subject: [PATCH 07/39] Bangle.js extra debug info --- .../service/devices/banglejs/BangleJSDeviceSupport.java | 3 +++ 1 file changed, 3 insertions(+) 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 c245a6351..a03cf9446 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 @@ -176,8 +176,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info("UART RX JSON parsed successfully"); handleUartRxJSON(json); } catch (JSONException e) { + LOG.info("UART RX JSON parse failure: "+ e.getLocalizedMessage()); GB.toast(getContext(), "Malformed JSON from Bangle.js: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); } + } else { + LOG.info("UART RX line started with "+(int)line.charAt(0)+" - ignoring"); } } From 9b5a7424d69a8085531bd15359056261789937de Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 23 Mar 2022 17:01:59 +0000 Subject: [PATCH 08/39] Initial test - loads app manager in a window, js works. Still need to add API for comms --- app/src/main/AndroidManifest.xml | 5 + .../banglejs/AppsManagementActivity.java | 96 +++++++++++++++++++ .../devices/banglejs/BangleJSCoordinator.java | 14 ++- .../activity_banglejs_apps_management.xml | 16 ++++ 4 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java create mode 100644 app/src/main/res/layout/activity_banglejs_apps_management.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index edf7b965e..92c682e79 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -467,6 +467,11 @@ + 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..0b4cbd00b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java @@ -0,0 +1,96 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.banglejs; + +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.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 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.devices.qhybrid.QHybridSupport; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class AppsManagementActivity extends AbstractGBActivity { + private WebView webView; + private GBDevice mGBDevice; + private DeviceCoordinator mCoordinator; + + @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); + + initViews(); + } + + private void toast(String data) { + GB.toast(data, Toast.LENGTH_LONG, GB.INFO); + } + + @Override + protected void onPause() { + super.onPause(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver); + finish(); + } + + @Override + protected void onResume() { + super.onResume(); + LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)); + } + + BroadcastReceiver deviceUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + + } + }; + + private void initViews() { + //https://stackoverflow.com/questions/4325639/android-calling-javascript-functions-in-webview + webView = findViewById(R.id.webview); + webView.setWebViewClient(new WebViewClient()); + webView.getSettings().setJavaScriptEnabled(true); + webView.loadUrl("https://banglejs.com/apps/"); + + 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 ad052bf1e..38a1a4b0b 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 @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Collections; 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; @@ -132,21 +133,18 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator { return true; } - @Override - public boolean supportsAppsManagement() { - return false; - } - @Override public int getAlarmSlotCount() { return 10; } @Override - public Class getAppsManagementActivity() { - return null; - } + public boolean supportsAppsManagement() { return true; } + @Override + public Class getAppsManagementActivity() { + return AppsManagementActivity.class; + } @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) { 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 From 8d2b2491db5dd95c3b1a30b5fa26b4f88bf0bc6c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 24 Mar 2022 11:31:53 +0000 Subject: [PATCH 09/39] two-way comms test --- .../banglejs/AppsManagementActivity.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) 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 index 0b4cbd00b..9b4711147 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java @@ -9,6 +9,7 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.webkit.JavascriptInterface; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.AdapterView; @@ -23,6 +24,9 @@ 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 nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; @@ -30,11 +34,13 @@ 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.devices.banglejs.BangleJSDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; public class AppsManagementActivity extends AbstractGBActivity { + private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class); private WebView webView; private GBDevice mGBDevice; private DeviceCoordinator mCoordinator; @@ -80,12 +86,49 @@ public class AppsManagementActivity extends AbstractGBActivity { } }; + + public class WebViewInterface { + Context mContext; + + WebViewInterface(Context c) { + mContext = c; + } + + @JavascriptInterface + public void bangleTx(String data) { + LOG.info("WebView RX: " + data); + bangleRx("Hello world"); + } + + } + + // Called when data received from Bangle.js + public void bangleRx(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); + 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); + } + } + }); + } + private void initViews() { //https://stackoverflow.com/questions/4325639/android-calling-javascript-functions-in-webview webView = findViewById(R.id.webview); webView.setWebViewClient(new WebViewClient()); webView.getSettings().setJavaScriptEnabled(true); - webView.loadUrl("https://banglejs.com/apps/"); + webView.addJavascriptInterface(new WebViewInterface(this), "Android"); + webView.loadUrl("https://www.pur3.co.uk/tmp/android.html"); webView.setWebViewClient(new WebViewClient(){ public void onPageFinished(WebView view, String weburl){ From 86f738d9471e6ff953b70f87466dd0a7dcd27105 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 24 Mar 2022 13:03:28 +0000 Subject: [PATCH 10/39] Actual working app loader - albeit slow --- .../banglejs/AppsManagementActivity.java | 57 ++++++++++++++++--- .../banglejs/BangleJSDeviceSupport.java | 37 ++++++++++++ 2 files changed, 85 insertions(+), 9 deletions(-) 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 index 9b4711147..75cbbd010 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java @@ -10,6 +10,7 @@ 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; @@ -28,12 +29,15 @@ 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; @@ -41,10 +45,31 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class AppsManagementActivity extends AbstractGBActivity { private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class); + private final BroadcastReceiver commandReceiver; + private WebView webView; private GBDevice mGBDevice; private DeviceCoordinator mCoordinator; + public AppsManagementActivity() { + IntentFilter commandFilter = new IntentFilter(); + commandFilter.addAction(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX); + commandReceiver = 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")); + LOG.info("WebView TX: " + data); + bangleRxData(data); + break; + } + } + } + }; + LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -86,7 +111,6 @@ public class AppsManagementActivity extends AbstractGBActivity { } }; - public class WebViewInterface { Context mContext; @@ -94,16 +118,17 @@ public class AppsManagementActivity extends AbstractGBActivity { 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); - bangleRx("Hello world"); + bangleTxData(data); } } - // Called when data received from Bangle.js - public void bangleRx(String 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(); @@ -114,25 +139,39 @@ public class AppsManagementActivity extends AbstractGBActivity { public void run() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, null); - } - else { + } 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()); - webView.getSettings().setJavaScriptEnabled(true); + 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.loadUrl("https://www.pur3.co.uk/tmp/android.html"); + webView.setWebContentsDebuggingEnabled(true); // FIXME + webView.loadUrl("https://banglejs.com/apps/android.html"); webView.setWebViewClient(new WebViewClient(){ public void onPageFinished(WebView view, String weburl){ - webView.loadUrl("javascript:showToast('WebView in Espruino')"); + //webView.loadUrl("javascript:showToast('WebView in Espruino')"); } }); } 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 a03cf9446..d3f6b4ac1 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 @@ -18,8 +18,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -51,6 +53,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; import java.util.SimpleTimeZone; import java.util.UUID; import java.lang.reflect.Field; @@ -66,6 +70,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicContr import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration; import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -81,6 +86,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -92,6 +98,8 @@ import javax.xml.xpath.XPathFactory; public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class); + private final BroadcastReceiver commandReceiver; + private BluetoothGattCharacteristic rxCharacteristic = null; private BluetoothGattCharacteristic txCharacteristic = null; private int mtuSize = 20; @@ -101,9 +109,34 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private boolean realtimeStep = false; private int realtimeHRMInterval = 30*60; + public static final String BANGLEJS_COMMAND_TX = "banglejs_command_tx"; + public static final String BANGLEJS_COMMAND_RX = "banglejs_command_rx"; + public BangleJSDeviceSupport() { super(LOG); addSupportedService(BangleJSConstants.UUID_SERVICE_NORDIC_UART); + + IntentFilter commandFilter = new IntentFilter(); + commandFilter.addAction(BANGLEJS_COMMAND_TX); + commandReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case BANGLEJS_COMMAND_TX: { + String data = String.valueOf(intent.getExtras().get("DATA")); + try { + TransactionBuilder builder = performInitialized("TX"); + uartTx(builder, data); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Error in TX: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + break; + } + } + } + }; + LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter); } @Override @@ -362,6 +395,10 @@ 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); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } return false; } From 2413f077415e3d267d2bae76f47df5a01306b768 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 24 Mar 2022 14:09:47 +0000 Subject: [PATCH 11/39] Ensure app manager is only available in Bangle.js builds (where internet access allowed) --- .../devices/banglejs/BangleJSCoordinator.java | 5 +- .../banglejs/BangleJSDeviceSupport.java | 94 +++++++++++-------- 2 files changed, 56 insertions(+), 43 deletions(-) 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 38a1a4b0b..91b4e8f96 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 @@ -30,6 +30,7 @@ import androidx.annotation.NonNull; import java.util.Collection; import java.util.Collections; +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; @@ -139,11 +140,11 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator { } @Override - public boolean supportsAppsManagement() { return true; } + public boolean supportsAppsManagement() { return BuildConfig.INTERNET_ACCESS; } @Override public Class getAppsManagementActivity() { - return AppsManagementActivity.class; + return BuildConfig.INTERNET_ACCESS ? AppsManagementActivity.class : null; } @Override 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 d3f6b4ac1..8e0d8a7a5 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 @@ -59,6 +59,7 @@ import java.util.SimpleTimeZone; import java.util.UUID; import java.lang.reflect.Field; +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; @@ -186,7 +187,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } } - /// Write a string of data, and chunk it up + /// Write a JSON object of data private void uartTxJSON(String taskName, JSONObject json) { try { TransactionBuilder builder = performInitialized(taskName); @@ -197,6 +198,19 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } } + /// Write JSON object of the form {t:taskName, err:message} + private void uartTxJSONError(String taskName, String message) { + JSONObject o = new JSONObject(); + try { + o.put("t", taskName); + o.put("err", message); + } catch (JSONException e) { + GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + uartTxJSON(taskName, o); + } + + private void handleUartRxLine(String line) { LOG.info("UART RX LINE: " + line); @@ -322,52 +336,50 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } break; case "http": { // FIXME: This should be behind a default-off option in Gadgetbridge settings - RequestQueue queue = Volley.newRequestQueue(getContext()); - String url = json.getString("url"); - String _xmlPath = ""; - try { _xmlPath = json.getString("xpath"); } catch (JSONException e) {} - final String xmlPath = _xmlPath; - // Request a string response from the provided URL. - StringRequest stringRequest = new StringRequest(Request.Method.GET, url, - new Response.Listener() { - @Override - public void onResponse(String response) { - JSONObject o = new JSONObject(); - if (xmlPath.length()!=0) { - try { - InputSource inputXML = new InputSource( new StringReader( response ) ); - XPath xPath = XPathFactory.newInstance().newXPath(); - response = xPath.evaluate(xmlPath, inputXML); - } catch (Exception error) { + if (BuildConfig.INTERNET_ACCESS) { + RequestQueue queue = Volley.newRequestQueue(getContext()); + String url = json.getString("url"); + String _xmlPath = ""; + try { + _xmlPath = json.getString("xpath"); + } catch (JSONException e) { + } + final String xmlPath = _xmlPath; + // Request a string response from the provided URL. + StringRequest stringRequest = new StringRequest(Request.Method.GET, url, + new Response.Listener() { + @Override + public void onResponse(String response) { + JSONObject o = new JSONObject(); + if (xmlPath.length() != 0) { + try { + InputSource inputXML = new InputSource(new StringReader(response)); + XPath xPath = XPathFactory.newInstance().newXPath(); + response = xPath.evaluate(xmlPath, inputXML); + } catch (Exception error) { + uartTxJSONError("http", error.toString()); + return; + } + } try { - o.put("err", error.toString()); + o.put("t", "http"); + o.put("resp", response); } catch (JSONException e) { GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); } - } + uartTxJSON("http", o); } - try { - o.put("t", "http"); - o.put("resp", response); - } catch (JSONException e) { - GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); - } - uartTxJSON("http", o); - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - JSONObject o = new JSONObject(); - try { - o.put("t", "http"); - o.put("err", error.toString()); - } catch (JSONException e) { - GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + JSONObject o = new JSONObject(); + uartTxJSONError("http", error.toString()); } - uartTxJSON("http", o); - } - }); - queue.add(stringRequest); + }); + queue.add(stringRequest); + } else { + uartTxJSONError("http", "Internet access not enabled"); + } } break; default : { LOG.info("UART RX JSON packet type '"+packetType+"' not understood."); From 8836d1a71c4912d8e051ffd442b331f336591773 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Mar 2022 08:24:44 +0000 Subject: [PATCH 12/39] Fix duplicate BroadcastReceiver/webview issue --- .../banglejs/AppsManagementActivity.java | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) 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 index 75cbbd010..3a918b5b9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java @@ -45,29 +45,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class AppsManagementActivity extends AbstractGBActivity { private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class); - private final BroadcastReceiver commandReceiver; private WebView webView; private GBDevice mGBDevice; private DeviceCoordinator mCoordinator; public AppsManagementActivity() { - IntentFilter commandFilter = new IntentFilter(); - commandFilter.addAction(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX); - commandReceiver = 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")); - LOG.info("WebView TX: " + data); - bangleRxData(data); - break; - } - } - } - }; - LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter); } @Override @@ -83,8 +66,6 @@ public class AppsManagementActivity extends AbstractGBActivity { throw new IllegalArgumentException("Must provide a device when invoking this activity"); } mCoordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); - - initViews(); } private void toast(String data) { @@ -94,6 +75,8 @@ public class AppsManagementActivity extends AbstractGBActivity { @Override protected void onPause() { super.onPause(); + webView.destroy(); + webView = null; LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver); finish(); } @@ -101,13 +84,25 @@ public class AppsManagementActivity extends AbstractGBActivity { @Override protected void onResume() { super.onResume(); - LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)); + 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")); + LOG.info("WebView TX: " + data); + bangleRxData(data); + break; + } + } } }; @@ -134,7 +129,7 @@ public class AppsManagementActivity extends AbstractGBActivity { String ss = s.toString(); final String js = "bangleRx("+ss.substring(1, ss.length()-1)+");"; LOG.info("WebView TX cmd: " + js); - webView.post(new Runnable() { + if (webView!=null) webView.post(new Runnable() { @Override public void run() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { From 04a8d242c7ad306b2c182a2a98bf1f97eff4d469 Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 2 Apr 2022 16:49:09 +0200 Subject: [PATCH 13/39] provide app strings based on variants via code --- app/src/banglejs/res/values/strings.xml | 8 ------ .../gadgetbridge/GBApplication.java | 21 ++++++++++++++++ .../activities/AboutActivity.java | 13 ++++++++++ .../activities/AbstractGBActivity.java | 2 ++ .../activities/ControlCenterv2.java | 1 + .../service/DeviceCommunicationService.java | 2 +- app/src/main/res/layout/activity_about.xml | 2 +- app/src/main/res/values/strings.xml | 25 ++++++++++++++++--- app/src/nightly/res/values/strings.xml | 2 +- app/src/nopebble/res/values/strings.xml | 2 +- 10 files changed, 63 insertions(+), 15 deletions(-) delete mode 100644 app/src/banglejs/res/values/strings.xml diff --git a/app/src/banglejs/res/values/strings.xml b/app/src/banglejs/res/values/strings.xml deleted file mode 100644 index e965e9764..000000000 --- a/app/src/banglejs/res/values/strings.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - Bangle.js Gadgetbridge - Bangle.js Gadgetbridge - About Bangle.js Gadgetbridge - Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. - Bangle.js running - diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 6282ebcac..7556e2468 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -1218,4 +1218,25 @@ public class GBApplication extends Application { public void setAutoExportScheduledTimestamp(long autoExportScheduledTimestamp) { this.autoExportScheduledTimestamp = autoExportScheduledTimestamp; } + /** + * Provides either a string resource based on current variant + * (main_debug, main_nightly...): "about_description_main_debug" + * or just a universal string resource for "about_description" + * + * @param resString + * @return string + */ + public String getStringResourceByVariantName(String resString) { + String packageName = getPackageName(); + String variation = BuildConfig.FLAVOR + "_" + BuildConfig.BUILD_TYPE; + int resIdVariation = getResources().getIdentifier(resString + "_" + variation, "string", packageName); + int resIdNormal = getResources().getIdentifier(resString, "string", packageName); + + if (resIdVariation == 0) { + //LOG.warn("Missing variation string: " + aString + "_" + variation); + return getString(resIdNormal); + } else { + return getString(resIdVariation); + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java index 56a7ae3b9..18ba47446 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java @@ -20,17 +20,30 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.os.Bundle; import android.text.method.LinkMovementMethod; import android.widget.TextView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import nodomain.freeyourgadget.gadgetbridge.BuildConfig; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; public class AboutActivity extends AbstractGBActivity { + private static final Logger LOG = LoggerFactory.getLogger(ConfigureAlarms.class); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_about); TextView about_version = findViewById(R.id.about_version); + TextView about_title = findViewById(R.id.about_title); + TextView about_description = findViewById(R.id.about_description); + + setTitle(GBApplication.app().getStringResourceByVariantName("about_activity_title")); + about_title.setText(GBApplication.app().getStringResourceByVariantName("about_activity_title")); + about_description.setText(GBApplication.app().getStringResourceByVariantName("about_description")); + TextView about_hash = findViewById(R.id.about_hash); String versionName = BuildConfig.VERSION_NAME; String versionHASH = BuildConfig.GIT_HASH_SHORT; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java index 8d0ea2d51..ba96aaa15 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java @@ -28,6 +28,8 @@ import java.util.Locale; import androidx.appcompat.app.AppCompatActivity; import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index e63c4d23e..893813238 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -151,6 +151,7 @@ public class ControlCenterv2 extends AppCompatActivity setContentView(R.layout.activity_controlcenterv2); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + setTitle(GBApplication.app().getStringResourceByVariantName("title_activity_controlcenter")); DrawerLayout drawer = findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index a9f7ddfa6..260f8e803 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -666,7 +666,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private void start() { if (!mStarted) { GB.createNotificationChannels(this); - startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this)); + startForeground(GB.NOTIFICATION_ID, GB.createNotification(GBApplication.app().getStringResourceByVariantName("gadgetbridge_running"), this)); mStarted = true; } } diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index a0817758e..769ce31ee 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -44,7 +44,7 @@ android:paddingRight="@dimen/about_margin"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d65c4df6..5b8247005 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,6 +2,28 @@ Gadgetbridge Gadgetbridge + About Gadgetbridge + Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. + Gadgetbridge running + + Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + About Bangle.js Gadgetbridge + Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. + Bangle.js running + + Gadgetbridge (Nightly) + Gadgetbridge Nightly + About Gadgetbridge Nightly + Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. It cannot be installed if you already have either the Gadgetbridge or the Pebble app installed, due to a conflict in the Pebble provider. + Nightly GB running + + Gadgetbridge (Nightly, No Pebble provider) + Gadgetbridge Nightly No Pebble + About Gadgetbridge Nightly No Pebble + Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. This version has the Pebble provider renamed to prevent conflicts, so some Pebble related integrations will not work, but it can be installed alongside existing Gadgetbridge installation. + Nightly NoPebble GB running + Settings Debug Quit @@ -368,7 +390,6 @@ Tap connected device for vibration Tap a device to connect Cannot connect. Bluetooth address invalid? - Gadgetbridge running Installing binary %1$d/%2$d Installation failed Installed @@ -1096,11 +1117,9 @@ About Version %s Commit %s - About Gadgetbridge GPX Receiver Gadgetbridge GPX file(s) received: Some file(s) already exist. Overwrite? - Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Core Team (in order of first code contribution) Contributors Andreas Shimokawa\nCarsten Pfeiffer\nDaniele Gobbetti diff --git a/app/src/nightly/res/values/strings.xml b/app/src/nightly/res/values/strings.xml index c4e5885c9..e9a80b725 100644 --- a/app/src/nightly/res/values/strings.xml +++ b/app/src/nightly/res/values/strings.xml @@ -1,4 +1,4 @@ - Gadgetbridge (Nightly) + diff --git a/app/src/nopebble/res/values/strings.xml b/app/src/nopebble/res/values/strings.xml index 090f10a24..e9a80b725 100644 --- a/app/src/nopebble/res/values/strings.xml +++ b/app/src/nopebble/res/values/strings.xml @@ -1,4 +1,4 @@ - Gadgetbridge (Nightly, No Pebble provider) + From 38b9a8f80ec10ee5daea0423fe84fdb295f93d11 Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 2 Apr 2022 19:35:10 +0200 Subject: [PATCH 14/39] add logging --- .../nodomain/freeyourgadget/gadgetbridge/GBApplication.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 7556e2468..070b0b74f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -1233,7 +1233,8 @@ public class GBApplication extends Application { int resIdNormal = getResources().getIdentifier(resString, "string", packageName); if (resIdVariation == 0) { - //LOG.warn("Missing variation string: " + aString + "_" + variation); + // Since this class must not log to slf4j, we use plain android.util.Log + Log.i(TAG, "Missing variation string: " + resString + "_" + variation); return getString(resIdNormal); } else { return getString(resIdVariation); From ffeba878acaa0907b1a0b882b2fcbeb7ff7b5434 Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 2 Apr 2022 19:35:39 +0200 Subject: [PATCH 15/39] make Bangle strings working --- app/src/main/res/values/strings.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b8247005..4b5d55d2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,11 +6,17 @@ Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Gadgetbridge running - Bangle.js Gadgetbridge - Bangle.js Gadgetbridge - About Bangle.js Gadgetbridge - Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. - Bangle.js running + Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + About Bangle.js Gadgetbridge + Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. + Bangle.js running + + Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + About Bangle.js Gadgetbridge + Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. + Bangle.js running Gadgetbridge (Nightly) Gadgetbridge Nightly From dfd659301709939a9086404baed93a165224970d Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 3 Apr 2022 12:10:10 +0200 Subject: [PATCH 16/39] modify app_name in gradle --- app/build.gradle | 4 ++++ app/src/main/AndroidManifest.xml | 3 ++- app/src/main/res/values/strings.xml | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b7264498e..c4e4d1d23 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,6 +62,7 @@ android { buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\"" buildConfigField "boolean", "INTERNET_ACCESS", "false" resValue "string", "pebble_content_provider", "com.getpebble.android.provider" + resValue "string", "app_name", "@string/application_name" } signingConfigs { nightly { @@ -91,6 +92,7 @@ android { } proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" resValue "string", "pebble_content_provider", "com.getpebble.android.provider" + resValue "string", "app_name", "@string/application_name_main_nightly" debuggable true } nopebble { @@ -104,6 +106,7 @@ android { } proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" resValue "string", "pebble_content_provider", "com.getpebble.android.nopebble.provider" + resValue "string", "app_name", "@string/application_name_main_nopebble" debuggable true } @@ -140,6 +143,7 @@ android { buildConfigField "boolean", "INTERNET_ACCESS", "true" // Disable pebble provider to allow Bangle.js Gadgetbridge to coexist with Gadgetbridge resValue "string", "pebble_content_provider", "com.getpebble.android.nopebble.provider" + resValue "string", "app_name", "@string/application_name_banglejs_main" } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index edf7b965e..a2078b549 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,7 +69,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:requestLegacyExternalStorage="true" - android:theme="@style/GadgetbridgeTheme"> + android:theme="@style/GadgetbridgeTheme" + tools:replace="android:label"> - Gadgetbridge + Gadgetbridge Gadgetbridge About Gadgetbridge Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Gadgetbridge running - Bangle.js Gadgetbridge + Bangle.js Gadgetbridge Bangle.js Gadgetbridge About Bangle.js Gadgetbridge Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. Bangle.js running - Bangle.js Gadgetbridge + Bangle.js Gadgetbridge Bangle.js Gadgetbridge About Bangle.js Gadgetbridge Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. Bangle.js running - Gadgetbridge (Nightly) + Gadgetbridge (Nightly) Gadgetbridge Nightly About Gadgetbridge Nightly Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. It cannot be installed if you already have either the Gadgetbridge or the Pebble app installed, due to a conflict in the Pebble provider. Nightly GB running - Gadgetbridge (Nightly, No Pebble provider) + Gadgetbridge (Nightly, No Pebble provider) Gadgetbridge Nightly No Pebble About Gadgetbridge Nightly No Pebble Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. Nightly releases of Gadgetbridge. This version has the Pebble provider renamed to prevent conflicts, so some Pebble related integrations will not work, but it can be installed alongside existing Gadgetbridge installation. From 1516033955ad8ecd3a950230c617de388f4991bc Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 23 Apr 2022 07:29:54 +0200 Subject: [PATCH 17/39] make string resources to be customazibe via gradle only, no code --- app/build.gradle | 18 ++++++++++++++- .../gadgetbridge/GBApplication.java | 22 ------------------- .../activities/AboutActivity.java | 13 ----------- .../activities/AbstractGBActivity.java | 2 -- .../activities/ControlCenterv2.java | 1 - .../service/DeviceCommunicationService.java | 2 +- app/src/main/res/values/strings.xml | 10 ++++----- 7 files changed, 23 insertions(+), 45 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c4e4d1d23..85eeb0121 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,7 +62,11 @@ android { buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\"" buildConfigField "boolean", "INTERNET_ACCESS", "false" resValue "string", "pebble_content_provider", "com.getpebble.android.provider" - resValue "string", "app_name", "@string/application_name" + resValue "string", "app_name", "@string/application_name_generic" + resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_generic" + resValue "string", "about_activity_title", "@string/about_activity_title_generic" + resValue "string", "about_description", "@string/about_description_generic" + resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_generic" } signingConfigs { nightly { @@ -93,6 +97,10 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" resValue "string", "pebble_content_provider", "com.getpebble.android.provider" resValue "string", "app_name", "@string/application_name_main_nightly" + resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_main_nightly" + resValue "string", "about_activity_title", "@string/about_activity_title_main_nightly" + resValue "string", "about_description", "@string/about_description_main_nightly" + resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_main_nightly" debuggable true } nopebble { @@ -107,6 +115,10 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" resValue "string", "pebble_content_provider", "com.getpebble.android.nopebble.provider" resValue "string", "app_name", "@string/application_name_main_nopebble" + resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_main_nopebble" + resValue "string", "about_activity_title", "@string/about_activity_title_main_nopebble" + resValue "string", "about_description", "@string/about_description_main_nopebble" + resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_main_nopebble" debuggable true } @@ -144,6 +156,10 @@ android { // Disable pebble provider to allow Bangle.js Gadgetbridge to coexist with Gadgetbridge resValue "string", "pebble_content_provider", "com.getpebble.android.nopebble.provider" resValue "string", "app_name", "@string/application_name_banglejs_main" + resValue "string", "title_activity_controlcenter", "@string/title_activity_controlcenter_banglejs_main" + resValue "string", "about_activity_title", "@string/about_activity_title_banglejs_main" + resValue "string", "about_description", "@string/about_description_banglejs_main" + resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_banglejs_main" } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 070b0b74f..6282ebcac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -1218,26 +1218,4 @@ public class GBApplication extends Application { public void setAutoExportScheduledTimestamp(long autoExportScheduledTimestamp) { this.autoExportScheduledTimestamp = autoExportScheduledTimestamp; } - /** - * Provides either a string resource based on current variant - * (main_debug, main_nightly...): "about_description_main_debug" - * or just a universal string resource for "about_description" - * - * @param resString - * @return string - */ - public String getStringResourceByVariantName(String resString) { - String packageName = getPackageName(); - String variation = BuildConfig.FLAVOR + "_" + BuildConfig.BUILD_TYPE; - int resIdVariation = getResources().getIdentifier(resString + "_" + variation, "string", packageName); - int resIdNormal = getResources().getIdentifier(resString, "string", packageName); - - if (resIdVariation == 0) { - // Since this class must not log to slf4j, we use plain android.util.Log - Log.i(TAG, "Missing variation string: " + resString + "_" + variation); - return getString(resIdNormal); - } else { - return getString(resIdVariation); - } - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java index 18ba47446..56a7ae3b9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutActivity.java @@ -20,30 +20,17 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.os.Bundle; import android.text.method.LinkMovementMethod; import android.widget.TextView; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import nodomain.freeyourgadget.gadgetbridge.BuildConfig; -import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; public class AboutActivity extends AbstractGBActivity { - private static final Logger LOG = LoggerFactory.getLogger(ConfigureAlarms.class); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_about); TextView about_version = findViewById(R.id.about_version); - TextView about_title = findViewById(R.id.about_title); - TextView about_description = findViewById(R.id.about_description); - - setTitle(GBApplication.app().getStringResourceByVariantName("about_activity_title")); - about_title.setText(GBApplication.app().getStringResourceByVariantName("about_activity_title")); - about_description.setText(GBApplication.app().getStringResourceByVariantName("about_description")); - TextView about_hash = findViewById(R.id.about_hash); String versionName = BuildConfig.VERSION_NAME; String versionHASH = BuildConfig.GIT_HASH_SHORT; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java index ba96aaa15..8d0ea2d51 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBActivity.java @@ -28,8 +28,6 @@ import java.util.Locale; import androidx.appcompat.app.AppCompatActivity; import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 893813238..e63c4d23e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -151,7 +151,6 @@ public class ControlCenterv2 extends AppCompatActivity setContentView(R.layout.activity_controlcenterv2); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - setTitle(GBApplication.app().getStringResourceByVariantName("title_activity_controlcenter")); DrawerLayout drawer = findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 260f8e803..a9f7ddfa6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -666,7 +666,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private void start() { if (!mStarted) { GB.createNotificationChannels(this); - startForeground(GB.NOTIFICATION_ID, GB.createNotification(GBApplication.app().getStringResourceByVariantName("gadgetbridge_running"), this)); + startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this)); mStarted = true; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9bebbd325..b4c78e916 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,10 +1,10 @@ - Gadgetbridge - Gadgetbridge - About Gadgetbridge - Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. - Gadgetbridge running + Gadgetbridge + Gadgetbridge + About Gadgetbridge + Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. + Gadgetbridge running Bangle.js Gadgetbridge Bangle.js Gadgetbridge From 8a2a03ecd34b8c7134706fb780367d65ebf6d879 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Apr 2022 15:29:05 +0100 Subject: [PATCH 18/39] Update strings for app_name/etc to '_generic' values to allow differently named apps - https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/2627 --- app/src/main/res/values-bg/strings.xml | 6 +++--- app/src/main/res/values-ca/strings.xml | 10 +++++----- app/src/main/res/values-cs/strings.xml | 10 +++++----- app/src/main/res/values-de/strings.xml | 10 +++++----- app/src/main/res/values-el/strings.xml | 10 +++++----- app/src/main/res/values-en-rGB/strings.xml | 10 +++++----- app/src/main/res/values-es/strings.xml | 10 +++++----- app/src/main/res/values-et/strings.xml | 10 +++++----- app/src/main/res/values-fa/strings.xml | 4 ++-- app/src/main/res/values-fi/strings.xml | 6 +++--- app/src/main/res/values-fr-rCA/strings.xml | 6 +++--- app/src/main/res/values-fr/strings.xml | 10 +++++----- app/src/main/res/values-gl/strings.xml | 6 +++--- app/src/main/res/values-he/strings.xml | 10 +++++----- app/src/main/res/values-hr/strings.xml | 10 +++++----- app/src/main/res/values-hu/strings.xml | 6 +++--- app/src/main/res/values-id/strings.xml | 6 +++--- app/src/main/res/values-it/strings.xml | 10 +++++----- app/src/main/res/values-ja/strings.xml | 8 ++++---- app/src/main/res/values-ka/strings.xml | 4 ++-- app/src/main/res/values-ko/strings.xml | 6 +++--- app/src/main/res/values-lt/strings.xml | 6 +++--- app/src/main/res/values-ml/strings.xml | 4 ++-- app/src/main/res/values-my/strings.xml | 4 ++-- app/src/main/res/values-nb-rNO/strings.xml | 10 +++++----- app/src/main/res/values-nl/strings.xml | 10 +++++----- app/src/main/res/values-pl/strings.xml | 10 +++++----- app/src/main/res/values-pt-rBR/strings.xml | 10 +++++----- app/src/main/res/values-pt/strings.xml | 10 +++++----- app/src/main/res/values-ro/strings.xml | 4 ++-- app/src/main/res/values-ru/strings.xml | 10 +++++----- app/src/main/res/values-sk/strings.xml | 6 +++--- app/src/main/res/values-sv/strings.xml | 4 ++-- app/src/main/res/values-tr/strings.xml | 10 +++++----- app/src/main/res/values-uk/strings.xml | 10 +++++----- app/src/main/res/values-vi/strings.xml | 6 +++--- app/src/main/res/values-zh-rCN/strings.xml | 10 +++++----- app/src/main/res/values-zh-rTW/strings.xml | 10 +++++----- 38 files changed, 151 insertions(+), 151 deletions(-) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index e74f26fca..2976c5371 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -17,8 +17,8 @@ Обхват на Графиката Обхвата е Месец Обхвата е Седмица - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Настройки Дебъгване Изход @@ -228,7 +228,7 @@ Докоснете свързаното устройство за вибрации Докоснете устройство за свързване Не може да се установи връзка. Грешен Bluetooth адрес\? - Gadgetbridge работи + Gadgetbridge работи Инсталиране на двоичен файл %1$d/%2$d Инсталацията пропадна Инсталирано diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index dfd01fa43..242f20e44 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Configuració Depuració Surt @@ -174,7 +174,7 @@ Toca l\'aparell per fer-lo vibrar Toca un aparell per connectar-hi No es pot connectar. Potser la direcció Bluetooth és incorrecta\? - El Gadgetbridge està funcionant + El Gadgetbridge està funcionant S\'està instal·lant el binari %1$d/%2$d La instal·lació ha fallat Instal·lat @@ -908,7 +908,7 @@ Support d\'aparell addicional Col·laboradors Equip principal (en ordre de primera contribució de codi) - Substitut lliure, copyleft i sense núvol per a les aplicacions Android de codi tancat dels fabricants dels aparells. + Substitut lliure, copyleft i sense núvol per a les aplicacions Android de codi tancat dels fabricants dels aparells. No molestis SpO2 PAI @@ -995,7 +995,7 @@ ES REQUEREIX CLAU La ubicació ha d\'estar activada Enllaços - Quant a Gadgetbridge + Quant a Gadgetbridge Versió %s Quant a Última notificació diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 507730638..4b382a539 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Nastavení Ladění Ukončit @@ -154,7 +154,7 @@ Dotkněte se zařízení pro vibrace Dotkněte se zařízení pro připojení Nelze připojit. Je BT adresa v pořádku? - Gadgetbridge běží + Gadgetbridge běží Instaluji soubor %1$d/%2$d Instalace selhala Instalace úspěšná @@ -915,8 +915,8 @@ Podpora dalších zařízení Přispěvatelé Centrální tým (v pořadí příspěvku prvního kódu) - Svobodná a lokální náhrada za uzavřenou aplikaci vašich hodinek/náramku. - O aplikaci Gadgetbridge + Svobodná a lokální náhrada za uzavřenou aplikaci vašich hodinek/náramku. + O aplikaci Gadgetbridge O aplikaci Světový Čas Použito s poskytovatelem počasí LineageOS. Ostatní verze Androidu vyžadují \"Weather Notification\" app. Více informací naleznete na Gadgetbridge wiki. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c7a592dad..bf3117e66 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Einstellungen Debug Beenden @@ -161,7 +161,7 @@ Tippe auf das verbundene Gerät, um es vibrieren zu lassen Tippe auf ein Gerät, um eine Verbindung herzustellen Keine Verbindung möglich. Bluetooth-Adresse ungültig\? - Gadgetbridge läuft + Gadgetbridge läuft Binärdatei %1$d/%2$d wird installiert Installation fehlgeschlagen Installiert @@ -923,8 +923,8 @@ Unterstützung zusätzlicher Geräte Beitragende Kernteam (Reihenfolge der ersten Mitwirkung am Code) - Cloudloser freier Ersatz für proprietäre Android-Gadget-Apps der Hersteller. - Über Gadgetbridge + Cloudloser freier Ersatz für proprietäre Android-Gadget-Apps der Hersteller. + Über Gadgetbridge Über Wird für den LineageOS Wetterdienst genutzt, andere Android-Versionen müssen eine Anwendung wie Weather notification nutzen. Mehr Informationen gibt es im Gadgetbride-Wiki. Weltzeituhr diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index cd82ac718..e4c50f513 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Ρυθμίσεις Αποσφαλμάτωση Έξοδος @@ -174,7 +174,7 @@ Πατήστε τη συνδεδεμένη συσκευή για δόνηση Πατήστε μία συσκευή για να συνδεθεί Δεν μπορεί να γίνει σύνδεση. Μήπως δεν είναι έγκυρη η διεύθυνση Bluetooth; - Το Gadgetbridge εκτελείται + Το Gadgetbridge εκτελείται Γίνεται εγκατάσταση των βιβλιοθηκών %1$d/%2$d Η εγκατάσταση απέτυχε Εγκατεστημένο @@ -901,8 +901,8 @@ Πρόσθετη υποστήριξη συσκευών Συντελεστές Βασική ομάδα (κατά σειρά μεγαλύτερης συνεισφοράς κώδικα) - Μια δωρεάν, ελεύθερου κώδικα και χωρίς πρόσβαση στο διαδίκτυο εφαρμογή Android για να αντικαταστήσει τις υπάρχουσες εφαρμογές κλειστού κώδικα των κατασκευαστών του smartwatch σας. - Σχετικά με το Gadgetbridge + Μια δωρεάν, ελεύθερου κώδικα και χωρίς πρόσβαση στο διαδίκτυο εφαρμογή Android για να αντικαταστήσει τις υπάρχουσες εφαρμογές κλειστού κώδικα των κατασκευαστών του smartwatch σας. + Σχετικά με το Gadgetbridge Σχετικά Παγκόσμιο Ρολόι Amazfit T-Rex diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 401143723..953d8f9e2 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -684,7 +684,7 @@ Installed Installation failed Installing binary %1$d/%2$d - Gadgetbridge running + Gadgetbridge running Cannot connect. Bluetooth address invalid\? Tap a device to connect Tap connected device for vibration @@ -853,8 +853,8 @@ Quit Debug Settings - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Save raw activity files Workout Event Reminder @@ -1124,7 +1124,7 @@ Statistics Upper Button short Upper Button double - About Gadgetbridge + About Gadgetbridge GPX file(s) received: Some file(s) already exist. Overwrite\? Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money… ✊ @@ -1276,7 +1276,7 @@ Temperature Version %s About - Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. + Cloudless copylefted libre replacement for closed source Android gadget apps from vendors. 30 days Time period Already bonded diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3cba4fce6..f0f86007f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Configuración Depuración Salir @@ -161,7 +161,7 @@ Toque el dispositivo conectado para la vibración Toca un dispositivo para conectarlo No se puede conectar. ¿Dirección BT incorrecta? - Gadgetbridge funcionando + Gadgetbridge funcionando Instalando binario %1$d/%2$d Instalación fallida Instalado @@ -1093,8 +1093,8 @@ Soporte de dispositivos adicionales Colaboradores Equipo principal (por orden de primera contribución de código) - Sustitución sin nube y con copyleft de las aplicaciones de código cerrado para Android de los proveedores. - Acerca de Gadgetbridge + Sustitución sin nube y con copyleft de las aplicaciones de código cerrado para Android de los proveedores. + Acerca de Gadgetbridge Versión %s Acerca de Última notificación diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index c1dcfc880..d271cdd78 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Seaded Silumine Välju @@ -243,7 +243,7 @@ Puuduta ühendatud seadet värina tekitamiseks Puuduta seadet, et ühendada Ei saa ühendust. Vigane bluetooth aadress\? - Gadgetbridge töötab + Gadgetbridge töötab Binaarfaili %1$d/%2$d paigaldamine Paigaldamine nurjus Paigaldatud @@ -746,8 +746,8 @@ Täiendavate seadmete tugi Kaasaaitajad Tuumiktiim (järjestatud esimese koodirea kuupäeva järgi) - Tasuta ja pilvevaba asendus teie kantavate nutiseadmete tootjate rakendustele. - Gadgetbridge\'ist + Tasuta ja pilvevaba asendus teie kantavate nutiseadmete tootjate rakendustele. + Gadgetbridge\'ist Rakendusest Alumine nupp Keskmine nupp diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 6c9da732f..2fcd9211d 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,7 +1,7 @@ - گجت‌بریج - گجت‌بریج + گجت‌بریج + گجت‌بریج تنظیمات اشکال‌زدایی خروج diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index f2c7af838..820f5adff 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -3,8 +3,8 @@ Asetukset Synkronoi Löydä kadonnut laite - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Lopeta Lahjoita Ota ruutukaappaus @@ -447,7 +447,7 @@ Askeleiden historia Kokonaisaskeleet Tietoja - Tietoja Gadgetbridgestä + Tietoja Gadgetbridgestä Alkaen Päättyen Vastaamatta jääneen puhelun ilmoitus diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index b93cfdd6c..d588a524f 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -129,8 +129,8 @@ Calories Vibration Long - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge %d heure %d heures @@ -697,7 +697,7 @@ Installé Échec de l\'installation Installation du binaire %1$d/%2$d - Gadgetbridge est en fonctionnement + Gadgetbridge est en fonctionnement Tapez sur le périphérique pour le connecter Cliquez sur connecter pour envoyer une vibration Cliquez sur l\'appareil pour ouvrir le gestionnaire d’activité diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6e3c5c682..1eeb6039f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Paramètres Déboguer Quitter @@ -161,7 +161,7 @@ Cliquez sur connecter pour envoyer une vibration Tapez sur le périphérique pour le connecter Connexion impossible. L’adresse Bluetooth est-elle valide ? - Gadgetbridge est en fonctionnement + Gadgetbridge est en fonctionnement Installation du binaire %1$d/%2$d Échec de l\'installation Installé @@ -1017,8 +1017,8 @@ Temps de sommeil préféré en heures Support d\'appareil supplémentaire Contributeurs Équipe principal (dans l\'ordre de la première contribution au code) - Un remplaçant libre et sans cloud aux applications Android propriétaires des fabricants de vos bracelets. - A propos de Gadgetbridge + Un remplaçant libre et sans cloud aux applications Android propriétaires des fabricants de vos bracelets. + A propos de Gadgetbridge A propos Horloge mondiale Stress diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 2947c1049..ae765d3aa 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Axustes Depuración Saír @@ -143,7 +143,7 @@ Toque nun dispositivo conectado para facelo Vibrar Toque nun dispositivo para conectar Non foi posíbel conectar. Enderezo Bluetooth inválido? - Gadgetbridge en execución + Gadgetbridge en execución Instalando binario %1$d/%2$d Fallou a instalación Instalado diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 9fe7ad229..b6c614675 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge הגדרות ניפוי שגיאות יציאה @@ -147,7 +147,7 @@ נקישה על ההתקן המחובר לרטט יש לגעת בהתקן כדי להתחבר לא ניתן להתחבר. כתובת ה־Bluetooth שגויה? - Gadgetbridge פעיל + Gadgetbridge פעיל התקנת הבינרי %1$d/%2$d ההתקנה נכשלה ההתקנה הצליחה @@ -913,8 +913,8 @@ תמיכה במכשירים נוספים תורמים צוות הליבה (מסודרים לפי מועד תרומת הקוד הראשונה) - חלופה חופשית ונטולת ענן ליישומוני ה־Android לחפיצים עם הקוד הסגור של היצרנים. - על אודות Gadgetbridge + חלופה חופשית ונטולת ענן ליישומוני ה־Android לחפיצים עם הקוד הסגור של היצרנים. + על אודות Gadgetbridge על אודות משמש לספק מזג האוויר של LineageOS, גרסאות אחרות של Android צריכות להשתמש ביישומונים כמו „התראות מזג אוויר”. אפשר למצוא מידע נוסף בוויקי של Gadgetbridge. שעון עולמי diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 711a0edb3..09e10ad4f 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1,6 +1,6 @@ - Gadgetbridge + Gadgetbridge Muško Žensko Ostalo @@ -28,7 +28,7 @@ Postavi pseudonim Izlaz Debug - Gadgetbridge + Gadgetbridge Stvarno izbrisati staru bazu podataka aktivnosti\? Podaci aktivnosti koji nisu uvezeni će se izgubiti. Izbrisati staru bazu podataka aktivnosti\? Podaci izbrisani. @@ -1268,8 +1268,8 @@ Dodaj widget Postavljanje alarma za %1$02d:%2$02d Verzija %s - O Gadgetbridge - Zamjena za libre bez oblaka bez kopiranja za zatvorene aplikacije za Android gadgete od dobavljača. + O Gadgetbridge + Zamjena za libre bez oblaka bez kopiranja za zatvorene aplikacije za Android gadgete od dobavljača. Osnovni tim (po redoslijedu doprinosa prvog koda) Konfiguracija izgleda sata Promjena pozadinske slike @@ -1358,7 +1358,7 @@ \n \n \n Ovaj firmware je za HW reviziju: %s - Gadgetbridge je pokrenut + Gadgetbridge je pokrenut Dodirnite uređaj za povezivanje Test Nije povezano diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index d9cf5a1f6..36b660a49 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Beállítások Hibakeresés Kilépés @@ -157,7 +157,7 @@ Koppints az eszközre a rezgetéshez. Koppints az eszközre a csatlakozáshoz Nem lehet csatlakozni. Rossz Bluetooth cím? - A Gadgetbridge fut + A Gadgetbridge fut %1$d/%2$d bináris telepítése Sikertelen telepítés Telepítve diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index dd7f06e40..2f9a12290 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Pengaturan Keluar Donasi @@ -175,7 +175,7 @@ Kalori Penerima GPX Gadgetbridge Lokasi harus diaktifkan - Tentang Gadgetbridge + Tentang Gadgetbridge Versi %s Tentang Tautan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f6ab2f1c9..d7877fe9e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Impostazioni Debug Esci @@ -158,7 +158,7 @@ Tocca il dispositivo connesso per la vibrazione tocca il dispositivo a cui connettersi Impossibile connettersi. Indirizzo Bluetooth non valido? - Gadgetbridge in esecuzione + Gadgetbridge in esecuzione Installazione del binario %1$d/%2$d Installazione fallita Installazione conclusa con successo @@ -884,8 +884,8 @@ Supporto a dispositivi aggiuntivi Contributori Team principale (ordinati in base al primo contributo di codice) - Un\'alternativa gratuita, libera e senza cloud alle applicazioni Android a sorgente chiusa del tuo dispositivo. - Su Gadgetbridge + Un\'alternativa gratuita, libera e senza cloud alle applicazioni Android a sorgente chiusa del tuo dispositivo. + Su Gadgetbridge Informazioni Ultima notifica Salva i dati raw (grezzi) delle attività diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 90f5245de..2e8deca6e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1,7 +1,7 @@ - ガジェットブリッジ - ガジェットブリッジ + ガジェットブリッジ + ガジェットブリッジ 設定 デバッグ 終了 @@ -161,7 +161,7 @@ バイブレーションの接続されたデバイスをタップ 接続するデバイスをタップ 接続できません。 Bluetoothアドレスが無効ですか\? - ガジェットブリッジは実行中 + ガジェットブリッジは実行中 バイナリーのインストール中 %1$d/%2$d インストールに失敗しました インストールに成功しました @@ -708,7 +708,7 @@ 距離 キーが必要です 貢献者 - Gadgetbridgeについて + Gadgetbridgeについて について 最終通知 通勤 diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index b55601dd1..43984c98e 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge პარამეტრები შეწყვეტა დონაცია diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 35e2abd47..7afd8b2d9 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1,7 +1,7 @@ - 가젯브릿지 - 가젯브릿지 + 가젯브릿지 + 가젯브릿지 설정 디버그 종료 @@ -77,7 +77,7 @@ 연결된 기기를 선택해 앱 관리자 실행 기기를 선택해 연결 연결할 수 없습니다. 블루투스 주소가 올바르지 않은 것 같습니다\? - 가젯브릿지 실행중 + 가젯브릿지 실행중 바이너리 설치중: %1$d/%2$d 설치 실패 설치됨 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index f466c9a57..3180dafc8 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Nustatymai Šalinti riktus Išeiti @@ -167,7 +167,7 @@ Bluetooth nepalaikomas. Bluetooth išjungtas. Bagstelėkite įrenginį, kad prisijungtumėte - Gadgetbridge paleistas + Gadgetbridge paleistas Instaluota Įrenginio paieška Stabdyti skenavimą diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 692c26523..7e0dca922 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -78,8 +78,8 @@ പുറത്തു കടക്കുക തെറ്റുകൾ തിരുത്തുക ക്രമീകരണങ്ങൾ - ഗാഡ്ജക്‌റ്റ് ബ്രിഡ്ജ് - ഗാഡ്ജക്‌റ്റ് ബ്രിഡ്ജ് + ഗാഡ്ജക്‌റ്റ് ബ്രിഡ്ജ് + ഗാഡ്ജക്‌റ്റ് ബ്രിഡ്ജ് ബാൻഡിൽ വൈബ്രേഷന്റെ കുറഞ്ഞ തീവ്രത പ്രവർത്തനക്ഷമമാക്കുക പവർ സേവിംഗ് മോഡ് ഹൃദയമിടിപ്പിന്റെ ആനുകാലിക യാന്ത്രിക അളവ് ഓഫുചെയ്യുന്നത് പ്രവർത്തന സമയം വർദ്ധിപ്പിക്കുന്നു ഇൻസ്റ്റാൾ ചെയ്ത അലാറത്തിന് മുമ്പുള്ള ഇടവേളയാണ് സ്മാർട്ട് അലാറം ഇടവേള. ഈ ഇടവേള ഉപകരണം ഉപയോക്താവിനെ ഉണർത്താൻ ഉറക്കത്തിന്റെ ഭാരം കുറഞ്ഞ ഘട്ടം കണ്ടെത്താൻ ശ്രമിക്കുന്നു diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 4caf4bc91..6b15aa624 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge ခ်ိန္ညွိရန္ ျပစ္ခ်က္ ထြက္မည္ diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 89b097f23..f035c80c7 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -39,8 +39,8 @@ Svartelistede kalendere Fastvare-/program-installerer Du er i ferd med å installere %s. - Gadgetbru - Gadgetbru + Gadgetbru + Gadgetbru Dette vil slette enheten og all tilknyttet data! Du er i ferd med å installere %s-fastvaren på din Amazfit Bip. \n @@ -165,7 +165,7 @@ Trykk på tilkoblet enhet for vibrasjon Trykk på en enhet for å koble til Kan ikke koble til. Ugyldig Blåtannsadresse? - Gadgetbro kjører + Gadgetbro kjører Installerer binærfil %1$d/%2$d Installasjon mislyktes Installert @@ -902,7 +902,7 @@ Klarte ikke å starte bakgrunnstjeneste Ytterligere enhetsstøtte Bidragsytere - Om Gadgetbridge + Om Gadgetbridge Om Verdensklokke Stress @@ -1034,7 +1034,7 @@ Aktiver lav vibrasjonsintensitet på båndet Strømsparingsmodus slår av periodisk automatisk måling av hjertefrekvensen og øker dermed arbeidstiden Smart alarmintervall er intervall før installert alarm. I dette intervallet prøver enheten å oppdage den letteste søvnfasen for å våkne brukeren - Skyfri, gemenhetslig fri erstatning for den lukkede motsatsen for dine Android-slaveenheter. + Skyfri, gemenhetslig fri erstatning for den lukkede motsatsen for dine Android-slaveenheter. Sjekk og forespør tilganger selv om de ikke trengs umiddelbart. Skru av dette kun hvis enhetene dine ikke støtter noen av disse funksjonene. Å ikke innvilge tilganger kan forårsake problemer! Skrur på slaveenhets-API (funker kun på Android 8 og høyere, og tilknytningen må settes opp på ny med Gadgetbro), noe som øker påliteligheten hvis tjenester må startes på ny i bakgrunnen Advarsel: Kunne ikke sjekke versjonsinfo! Du bør ikke fortsette og versjonsnavnet «%s» ble sett. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 7acb91769..0867fe1a0 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Instellingen Debug Sluit af @@ -185,7 +185,7 @@ Tap op het verbonden apparaat voor vibratie Tap op een apparaat om te verbinden Kan niet verbinden. Is het Bluetooth adres ongeldig? - Gadgetbridge loopt + Gadgetbridge loopt Binary installeren %1$d/%2$d Installatie gefaald Geïnstalleerd @@ -914,8 +914,8 @@ Extra apparaatondersteuning Bijdragers Kernteam (in volgorde van eerste codebijdrage) - Cloudloze vrije vervanging voor de gesloten Android-applicaties van gadgetfabrikanten. - Over Gadgetbridge + Cloudloze vrije vervanging voor de gesloten Android-applicaties van gadgetfabrikanten. + Over Gadgetbridge Over Gebruikt voor de LineageOS-weeraanbieder, andere Android versies moeten een app zoals \"Weather notification\" gebruiken. Meer informatie is te vinden in de Gadgetbridge wiki. Wereld Klok diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index bed0b2fe6..00bd6f24f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Ustawienia Debuguj Wyjdź @@ -108,7 +108,7 @@ Kliknij połączone urządzenie, aby uruchomić menadżer aplikacji Dotknij urządzenie aby połączyć Nie można połączyć. Nieprawidłowy adres Bluetooth\? - Gadgetbridge działa + Gadgetbridge działa Instalowanie binarki %1$d/%2$d Instalacja nie powiodła się Zainstalowano @@ -913,7 +913,7 @@ Ostatnie powiadomienie Ustaw własną nazwę Współtwórcy - O Gadgetbridge + O Gadgetbridge Amazfit T-Rex Styl grzbietowy Styl dowolny @@ -1031,7 +1031,7 @@ Linki Dziękujemy wszystkim nie notowanym na liście współtwórcom za udostępnienie kodu, tłumaczenia, wsparcie, pomysły, motywację, zgłoszenia błędów, pieniądze… ✊ Zespół główny (w kolejności od pierwszego wkładu w kod aplikacji) - Wolny od chmury zamiennik dla aplikacji o zamkniętym kodzie źródłowym dostarczanych przez producentów gadżetów dla systemu Android. + Wolny od chmury zamiennik dla aplikacji o zamkniętym kodzie źródłowym dostarczanych przez producentów gadżetów dla systemu Android. Edytuj etykietę Ping-pong Krykiet diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a411025d3..d42561908 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Configurações Depurar Sair @@ -148,7 +148,7 @@ Toque no dispositivo conectado para vibração Toque num dispositivo para ligar Não foi possível conectar. Endereço de Bluetooth inválido\? - Gadgetbridge em execução + Gadgetbridge em execução Instalando binário %1$d/%2$d Instalação falhou Instalado @@ -920,8 +920,8 @@ Suporte a dispositivos adicionais Contribuidores Equipe Core (na ordem da primeira contribuição de código) - Um substituto livre, protegido por copyleft, sem acesso a nuvem para os aplicativos Android de código fechado dos fornecedores de gadgets. - Sobre o Gadgetbridge + Um substituto livre, protegido por copyleft, sem acesso a nuvem para os aplicativos Android de código fechado dos fornecedores de gadgets. + Sobre o Gadgetbridge Sobre Usado para o provedor do clima do LineageOS, outras versões do Android precisam usar um aplicativo como o \"Notificação de clima\". Encontrará mais informações no wiki do Gadgetbridge. Relógio mundial diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 74efd9cd1..d26dbd7aa 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Definições Depurar Sair @@ -156,7 +156,7 @@ Toque no aparelho ligado para o fazer vibrar Toque num aparelho para ligar Não foi possível conectar. Endereço de Bluetooth inválido\? - Gadgetbridge a executar + Gadgetbridge a executar Instalando binário %1$d/%2$d A instalação falhou Instalado @@ -573,8 +573,8 @@ Suporte a aparelhos adicionais Contribuidores Equipa Core (na ordem da primeira contribuição de código) - Um substituto livre, protegido por copyleft, sem acesso a nuvem para as apps Android de código fechado dos fornecedores de gadgets. - Sobre o Gadgetbridge + Um substituto livre, protegido por copyleft, sem acesso a nuvem para as apps Android de código fechado dos fornecedores de gadgets. + Sobre o Gadgetbridge Sobre Está prestes a instalar o firmware %s no seu Amazit T-Rex. \n diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index a05411b86..a12113c8a 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Setari Depaneaza Iesire diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a6c394052..1d606ba1d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Настройки Отладка Выйти @@ -157,7 +157,7 @@ Коснитесь подключённого устройства для Вибрации Коснитесь устройства для соединения Не удалось соединиться. Неверный адрес Bluetooth\? - Gadgetbridge запущен + Gadgetbridge запущен установка бинарного файла %1$d/%2$d Установка не удалась Установлено @@ -848,8 +848,8 @@ Дополнительная поддержка устройств Соавторы Основная команда (в порядке поступления первого кода) - \"Безоблачная\" свободная альтернатива закрытым приложениям от производителей гаджетов. - О Gadgetbridge + \"Безоблачная\" свободная альтернатива закрытым приложениям от производителей гаджетов. + О Gadgetbridge О программе Последнее уведомление Сохранять необработанные файлы активности diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 8cff0a970..ec6aba113 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Nastavenia Ladenie Ukončiť @@ -180,7 +180,7 @@ Dotknite sa pripojeného zariadenia pre vibrácie Dotknite sa pripojeného zariadenia pre pripojenie Nie je možné pripojiť. Je BT adresa v poriadku? - Gadgetbridge je spustený + Gadgetbridge je spustený Instalujem binárny súbor %1$d/%2$d Inštalácia zlyhala Inštalácia bola úspešná diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 6a8f73ae5..47dea915a 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Inställningar Avsluta Donera diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3a2ab4b7b..9bafbe118 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -88,7 +88,7 @@ \nSürüm %2$s, %3$s tarafından \n Yukarı taşı - Gadgetbridge + Gadgetbridge Kur Pebble uygulama mağazasında ara Önbellekteki uygulamalar @@ -314,7 +314,7 @@ Dosya kurulamıyor, aygıt hazır değil. Aygıta bağlantı: %1$s Bu ürün yazılımı bu aygıtla uyumlu değil - Gadgetbridge çalışıyor + Gadgetbridge çalışıyor Almanca Kalp ritmi Yatay @@ -754,7 +754,7 @@ Uygulama Yöneticisi Kara Listeye Alınan Takvimler Etkinlik ve Uyku - Gadgetbridge + Gadgetbridge Veri yönetimi Hata Ayıklama Aygıta özel ayarlar @@ -937,8 +937,8 @@ Ek aygıt desteği Katkıda bulunanlar Çekirdek Ekip (ilk kod katkıda bulunma sırasına göre) - Satıcıların kapalı kaynaklı Android aygıt uygulamalarının yerine bulut gerektirmeyen copyleft özgür alternatif. - Gadgetbridge hakkında + Satıcıların kapalı kaynaklı Android aygıt uygulamalarının yerine bulut gerektirmeyen copyleft özgür alternatif. + Gadgetbridge hakkında Hakkında LineageOS hava durumu sağlayıcısı için kullanılır, diğer Android sürümleri \"Weather notification\" gibi bir uygulama kullanmalıdır. Gadgetbridge wiki sayfasında daha fazla bilgi bulabilirsiniz. Dünya Saati diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 1b8d399aa..814a11ce0 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Налаштування Зневадження Вийти @@ -98,7 +98,7 @@ Bluetooth не підтримується. Bluetooth вимкнуто. Не вдалося з\'єднатися. Неправильна Bluetooth адреса\? - Gadgetbridge запущено + Gadgetbridge запущено Встановлення двійкового файлу %1$d/%2$d Невдале встановлення Встановлено @@ -1022,8 +1022,8 @@ Підтримка додаткових пристроїв Співавтори Основна команда (впорядковано за датою допомоги) - Незалежний від хмари, вільний замінник закритих застосунків від інших виробників. - Про Gadgetbridge + Незалежний від хмари, вільний замінник закритих застосунків від інших виробників. + Про Gadgetbridge Про застосунок Останнє сповіщення Світовий час diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 3e852602c..fbd33c430 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Cài đặt Dò lỗi Thoát @@ -49,7 +49,7 @@ Không hỗ trợ Bluetooth. Đã tắt Bluetooth. Không thể kết nối. Địa chỉ Bluetooth không hợp lệ\? - Gadgetbridge đang chạy + Gadgetbridge đang chạy Đang cài tệp nhị phân %1$d/%2$d Cài đặt thất bại Đã cài đặt diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ea6d5dcdf..3746eaf0d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,7 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge 设置 调试 退出 @@ -166,7 +166,7 @@ 点按以震动已连接的设备 点按一个设备以连接 无法连接。 蓝牙地址无效? - Gadgetbridge 正在运行 + Gadgetbridge 正在运行 正在安装二进制文件 %1$d/%2$d 安装失败 安装成功 @@ -917,8 +917,8 @@ 额外设备支持 贡献者 核心成员 (以第一次提交代码排序) - 用于替代原厂手环应用的脱机无版权自由应用。 - 关于 Gadgetbridge + 用于替代原厂手环应用的脱机无版权自由应用。 + 关于 Gadgetbridge 关于 由LineageOS天气服务提供,其他版本的Android系统需要使用诸如“Weather notification”。更多信息请访问 Gadgetbridge 的Wiki。 世界时钟 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 1c761d590..132683fe5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -26,8 +26,8 @@ 刪除並清除緩存 重新安裝 nodomain.freeyourgadget.gadgetbridge.ButtonPressed - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge 連接中… 截取設備的屏幕截圖 校正設備 @@ -524,7 +524,7 @@ 配對您的小米手環 配對裝置 初始化完成 - Gadgetbridge 正在執行 + Gadgetbridge 正在執行 點擊一個裝置以進行連線 藍芽不支援。 涵蓋活動與未活動時消耗的卡路里 @@ -583,8 +583,8 @@ 額外裝置支援 貢獻者 核心團隊(按第一次程式碼貢獻順序排列) - 用來取代廠商提供的閉源 Android 小裝置應用程式的離線且版權自由的替代品。 - 關於 Gadgetbridge + 用來取代廠商提供的閉源 Android 小裝置應用程式的離線且版權自由的替代品。 + 關於 Gadgetbridge 關於 運動活動 游泳 From e786354c8d07d10bc3ea48499275faaccefd04a8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 10:22:29 +0100 Subject: [PATCH 19/39] Add option for enabling/disabling internet access, and add ability to send Intents direct from Bangle.js --- .../DeviceSettingsPreferenceConst.java | 3 ++ .../devices/banglejs/BangleJSCoordinator.java | 14 +++++-- .../banglejs/BangleJSDeviceSupport.java | 41 ++++++++++++++++--- app/src/main/res/values/strings.xml | 4 ++ .../res/xml/devicesettings_device_intents.xml | 9 ++++ .../devicesettings_device_internet_access.xml | 9 ++++ 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/xml/devicesettings_device_intents.xml create mode 100644 app/src/main/res/xml/devicesettings_device_internet_access.xml 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 7e461dfd1..6e0910df5 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 @@ -41,6 +41,9 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_VIBRATION_STRENGH_PERCENTAGE = "vibration_strength"; public static final String PREF_RELAX_FIRMWARE_CHECKS = "relax_firmware_checks"; + public static final String PREF_DEVICE_INTERNET_ACCESS = "device_internet_access"; + public static final String PREF_DEVICE_INTENTS = "device_intents"; + public static final String PREF_DISCONNECT_NOTIFICATION = "disconnect_notification"; public static final String PREF_DISCONNECT_NOTIFICATION_START = "disconnect_notification_start"; public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end"; 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 91b4e8f96..6764cf422 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 @@ -29,6 +29,7 @@ import androidx.annotation.NonNull; import java.util.Collection; import java.util.Collections; +import java.util.Vector; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.R; @@ -167,9 +168,16 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator { } public int[] getSupportedDeviceSpecificSettings(GBDevice device) { - return new int[]{ - R.xml.devicesettings_transliteration - }; + Vector settings = new Vector(); + settings.add(R.xml.devicesettings_transliteration); + settings.add(R.xml.devicesettings_high_mtu); + if (BuildConfig.INTERNET_ACCESS) + settings.add(R.xml.devicesettings_device_internet_access); + settings.add(R.xml.devicesettings_device_intents); + // must be a better way of doing this? + int[] settingsInt = new int[settings.size()]; + for (int i=0; i iter = extra.keys(); + while (iter.hasNext()) { + String key = iter.next(); + in.putExtra(key, extra.getString(key)); + } + } + LOG.info("Sending intent " + action); + this.getContext().getApplicationContext().sendBroadcast(in); + } else { + uartTxJSONError("intent", "Android Intents not enabled, check Gadgetbridge Device Settings"); + } + } default : { LOG.info("UART RX JSON packet type '"+packetType+"' not understood."); } @@ -396,7 +427,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { if (BangleJSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX.equals(characteristic.getUuid())) { byte[] chars = characteristic.getValue(); // check to see if we get more data - if so, increase out MTU for sending - if (chars.length > mtuSize) + if (allowHighMTU && chars.length > mtuSize) mtuSize = chars.length; String packetStr = new String(chars); LOG.info("RX: " + packetStr); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4c78e916..e35a6b21c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -264,6 +264,10 @@ Enable this if your device has a custom font firmware for emoji support Allow high MTU Increases transfer speed, but might not work on some Android devices. + Allow Internet Access + Allow apps on this device to access the internet + Allow Intents + Allow apps on this device to send Android Intents Enables calendar alerts, even when disconnected Sync calendar events Relax firmware checks diff --git a/app/src/main/res/xml/devicesettings_device_intents.xml b/app/src/main/res/xml/devicesettings_device_intents.xml new file mode 100644 index 000000000..d30169584 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_device_intents.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_device_internet_access.xml b/app/src/main/res/xml/devicesettings_device_internet_access.xml new file mode 100644 index 000000000..a84b985b8 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_device_internet_access.xml @@ -0,0 +1,9 @@ + + + + From b89467c4bb99545773e498fb12a1fffeb19ca8a4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 13:22:24 +0100 Subject: [PATCH 20/39] When we need permissions, we now pop up a dialog asking nicely and explaining why. Also instructions, since it may be unclear for new users. --- .../activities/ControlCenterv2.java | 74 +++++++++++++++---- app/src/main/res/values/strings.xml | 2 + 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index e63c4d23e..9bd0768c1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -19,9 +19,12 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.Manifest; import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.Dialog; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; @@ -44,6 +47,7 @@ import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; +import androidx.fragment.app.DialogFragment; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -243,12 +247,26 @@ public class ControlCenterv2 extends AppCompatActivity Set set = NotificationManagerCompat.getEnabledListenerPackages(this); if (pesterWithPermissions) { if (!set.contains(this.getPackageName())) { // If notification listener access hasn't been granted - Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); - startActivity(enableIntent); + // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) + // When accepted, we open the Activity for Notification access + DialogFragment dialog = new NotifyListenerPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "PermissionsDialogFragment"); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + /* In order to be able to set ringer mode to silent in GB's PhoneCallReceiver + the permission to access notifications is needed above Android M + ACCESS_NOTIFICATION_POLICY is also needed in the manifest */ + if (pesterWithPermissions) { + if (!((NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted()) { + // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) + // When accepted, we open the Activity for Notification access + DialogFragment dialog = new NotifyPolicyPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "PermissionsDialogFragment"); + } + } + // Check all the other permissions that we need to for Android M + later checkAndRequestPermissions(); } @@ -472,18 +490,6 @@ public class ControlCenterv2 extends AppCompatActivity } } - /* In order to be able to set ringer mode to silent in GB's PhoneCallReceiver - the permission to access notifications is needed above Android M - ACCESS_NOTIFICATION_POLICY is also needed in the manifest */ - if (pesterWithPermissions) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!((NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted()) { - GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); - startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)); - } - } - } - // HACK: On Lineage we have to do this so that the permission dialog pops up if (fakeStateListener == null) { fakeStateListener = new PhoneStateListener(); @@ -533,4 +539,44 @@ public class ControlCenterv2 extends AppCompatActivity } } + + /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity + public static class NotifyPolicyPermissionsDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_notification_policy_access, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)); + } + }); + return builder.create(); + } + } + + /// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity + public static class NotifyListenerPermissionsDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_notification_listener, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); + startActivity(enableIntent); + } + }); + return builder.create(); + } + } + } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e35a6b21c..358377974 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1138,6 +1138,8 @@ Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money… ✊ Links All these permissions are required and instability might occur if not granted + %1$s needs access to Notifications in order to display them on your watch.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Notification Access\', then tap \'Back\' to return to %1$s + %1$s needs access to Do Not Disturb settings in order to honour them on your watch.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Do Not Disturb\', then tap \'Back\' to return to %1$s CAUTION: Error when checking version information! You should not continue! Saw version name \"%s\" Location must be enabled CompanionDevice Pairing From fab1d100a0e7afab795f2bba290c5a51f3d9fa44 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 14:51:01 +0100 Subject: [PATCH 21/39] Bangle.js: add support for rendering to a text that can't be displayed into a bitmap and sending the bitmap over --- .../DeviceSettingsPreferenceConst.java | 2 + .../devices/banglejs/BangleJSCoordinator.java | 1 + .../banglejs/BangleJSDeviceSupport.java | 93 ++++++++++++++++--- app/src/main/res/values/strings.xml | 2 + .../main/res/xml/devicesettings_banglejs.xml | 9 ++ 5 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/xml/devicesettings_banglejs.xml 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 6e0910df5..867a10083 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 @@ -44,6 +44,8 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_DEVICE_INTERNET_ACCESS = "device_internet_access"; 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_DISCONNECT_NOTIFICATION = "disconnect_notification"; public static final String PREF_DISCONNECT_NOTIFICATION_START = "disconnect_notification_start"; public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end"; 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 6764cf422..109d6c0a0 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 @@ -169,6 +169,7 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator { public int[] getSupportedDeviceSpecificSettings(GBDevice device) { Vector settings = new Vector(); + settings.add(R.xml.devicesettings_banglejs); settings.add(R.xml.devicesettings_transliteration); settings.add(R.xml.devicesettings_high_mtu); if (BuildConfig.INTERNET_ACCESS) 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 0d5273d32..54285891b 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 @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -96,6 +97,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTENTS; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_TEXT_BITMAP; import static nodomain.freeyourgadget.gadgetbridge.database.DBHelper.*; import javax.xml.xpath.XPath; @@ -195,11 +197,31 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } } + 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(\\d\\d)", "\\\\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+"}";*/ + } + /// Write a JSON object of data private void uartTxJSON(String taskName, JSONObject json) { try { TransactionBuilder builder = performInitialized(taskName); - uartTx(builder, "\u0010GB("+json.toString()+")\n"); + uartTx(builder, "\u0010GB("+jsonToString(json)+")\n"); builder.queue(getQueue()); } catch (IOException e) { GB.toast(getContext(), "Error in "+taskName+": " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); @@ -464,6 +486,29 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { return true; } + + + public String renderUnicodeAsImage(String txt) { + if (txt==null) return null; + // If we're not doing conversion, pass this right back + Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); + if (!devicePrefs.getBoolean(PREF_BANGLEJS_TEXT_BITMAP, false)) + return txt; + // Otherwise split up and check each word + String words[] = txt.split(" "); + for (int i=0;i255) isRenderable = false; + } + if (!isRenderable) + words[i] = "\0"+bitmapToEspruinoString(textToBitmap(words[i])); + } + return String.join(" ", words); + } + @Override public void onNotification(NotificationSpec notificationSpec) { try { @@ -471,10 +516,10 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { o.put("t", "notify"); o.put("id", notificationSpec.getId()); o.put("src", notificationSpec.sourceName); - o.put("title", notificationSpec.title); - o.put("subject", notificationSpec.subject); - o.put("body", notificationSpec.body); - o.put("sender", notificationSpec.sender); + o.put("title", renderUnicodeAsImage(notificationSpec.title)); + o.put("subject", renderUnicodeAsImage(notificationSpec.subject)); + o.put("body", renderUnicodeAsImage(notificationSpec.body)); + o.put("sender", renderUnicodeAsImage(notificationSpec.sender)); o.put("tel", notificationSpec.phoneNumber); uartTxJSON("onNotification", o); } catch (JSONException e) { @@ -543,7 +588,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { cmdName = field.getName().substring(5).toLowerCase(); } catch (IllegalAccessException e) {} o.put("cmd", cmdName); - o.put("name", callSpec.name); + o.put("name", renderUnicodeAsImage(callSpec.name)); o.put("number", callSpec.number); uartTxJSON("onSetCallState", o); } catch (JSONException e) { @@ -580,9 +625,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { try { JSONObject o = new JSONObject(); o.put("t", "musicinfo"); - o.put("artist", musicSpec.artist); - o.put("album", musicSpec.album); - o.put("track", musicSpec.track); + o.put("artist", renderUnicodeAsImage(musicSpec.artist)); + o.put("album", renderUnicodeAsImage(musicSpec.album)); + o.put("track", renderUnicodeAsImage(musicSpec.track)); o.put("dur", musicSpec.duration); o.put("c", musicSpec.trackCount); o.put("n", musicSpec.trackNr); @@ -747,9 +792,23 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } } + public Bitmap textToBitmap(String text) { + Paint paint = new Paint(0); // Paint.ANTI_ALIAS_FLAG not wanted as 1bpp + paint.setTextSize(18); + paint.setColor(0xFFFFFFFF); + paint.setTextAlign(Paint.Align.LEFT); + float baseline = -paint.ascent(); // ascent() is negative + int width = (int) (paint.measureText(text) + 0.5f); // round + int height = (int) (baseline + paint.descent() + 0.5f); + Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(image); + canvas.drawText(text, 0, baseline, paint); + return image; + } + /** Convert an Android bitmap to a base64 string for use in Espruino. * Currently only 1bpp, no scaling */ - public static String bitmapToEspruino(Bitmap bitmap) { + public static byte[] bitmapToEspruinoArray(Bitmap bitmap) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); byte bmp[] = new byte[((height * width + 7) >> 3) + 3]; @@ -772,7 +831,19 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { if (cn > 0) bmp[n++] = (byte)c; //LOG.info("BMP: " + width + "x"+height+" n "+n); // Convert to base64 - return Base64.encodeToString(bmp, Base64.DEFAULT).replaceAll("\n",""); + return bmp; + } + + /** Convert an Android bitmap to a base64 string for use in Espruino. + * Currently only 1bpp, no scaling */ + public static String bitmapToEspruinoString(Bitmap bitmap) { + return new String(bitmapToEspruinoArray(bitmap), StandardCharsets.ISO_8859_1); + } + + /** Convert an Android bitmap to a base64 string for use in Espruino. + * Currently only 1bpp, no scaling */ + public static String bitmapToEspruinoBase64(Bitmap bitmap) { + return Base64.encodeToString(bitmapToEspruinoArray(bitmap), Base64.DEFAULT).replaceAll("\n",""); } /** Convert a drawable to a bitmap, for use with bitmapToEspruino */ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 358377974..10fd724c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -180,6 +180,8 @@ Allow notifications from selected apps Transliteration 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 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 new file mode 100644 index 000000000..9270f8baf --- /dev/null +++ b/app/src/main/res/xml/devicesettings_banglejs.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file From f9a49e00394e6bafc44683ef9c7403454c02d843 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 15:21:57 +0100 Subject: [PATCH 22/39] improve JSON-format output --- .../service/devices/banglejs/BangleJSDeviceSupport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 54285891b..5782c1306 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 @@ -188,6 +188,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { 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;imtuSize) l=mtuSize; @@ -202,7 +203,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { // 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(\\d\\d)", "\\\\x$1"); + json = json.replaceAll("\\\\u00([0123456789abcdef][0123456789abcdef])", "\\\\x$1"); return json; /*String json = "{"; Iterator iter = jsonObj.keys(); From 031898f538df607b3ec8faec7e24dda1eb2229e2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 21:03:20 +0100 Subject: [PATCH 23/39] Add ability to receive intents to com.banglejs.uart.tx (from apps like tasker) and send them to Bangle.js --- .../banglejs/BangleJSDeviceSupport.java | 72 +++++++++++++++---- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 60 insertions(+), 14 deletions(-) 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 5782c1306..c2929f25b 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 @@ -105,7 +105,6 @@ import javax.xml.xpath.XPathFactory; public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class); - private final BroadcastReceiver commandReceiver; private BluetoothGattCharacteristic rxCharacteristic = null; private BluetoothGattCharacteristic txCharacteristic = null; @@ -117,36 +116,83 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private boolean realtimeStep = false; private int realtimeHRMInterval = 30*60; + // Local Intents - for app manager communication public static final String BANGLEJS_COMMAND_TX = "banglejs_command_tx"; public static final String BANGLEJS_COMMAND_RX = "banglejs_command_rx"; + // Global Intents + private static final String BANGLE_ACTION_UART_TX = "com.banglejs.uart.tx"; public BangleJSDeviceSupport() { super(LOG); addSupportedService(BangleJSConstants.UUID_SERVICE_NORDIC_UART); + registerLocalIntents(); + registerGlobalIntents(); + } + + private void registerLocalIntents() { IntentFilter commandFilter = new IntentFilter(); commandFilter.addAction(BANGLEJS_COMMAND_TX); - commandReceiver = new BroadcastReceiver() { + BroadcastReceiver commandReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case BANGLEJS_COMMAND_TX: { - String data = String.valueOf(intent.getExtras().get("DATA")); - try { - TransactionBuilder builder = performInitialized("TX"); - uartTx(builder, data); - builder.queue(getQueue()); - } catch (IOException e) { - GB.toast(getContext(), "Error in TX: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + switch (intent.getAction()) { + case BANGLEJS_COMMAND_TX: { + String data = String.valueOf(intent.getExtras().get("DATA")); + try { + TransactionBuilder builder = performInitialized("TX"); + uartTx(builder, data); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Error in TX: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + break; } - break; } } - } }; LocalBroadcastManager.getInstance(GBApplication.getContext()).registerReceiver(commandReceiver, commandFilter); } + private void registerGlobalIntents() { + IntentFilter commandFilter = new IntentFilter(); + commandFilter.addAction(BANGLE_ACTION_UART_TX); + BroadcastReceiver commandReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case BANGLE_ACTION_UART_TX: { + /* In Tasker: + Action: com.banglejs.uart.tx + Cat: None + Extra: line:Terminal.println(%avariable) + Target: Broadcast Receiver + + Variable: Number, Configure on Import, NOT structured, Value set, Nothing Exported, NOT Same as value + */ + Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); + if (!devicePrefs.getBoolean(PREF_DEVICE_INTENTS, false)) return; + String data = intent.getStringExtra("line"); + if (data==null) { + GB.toast(getContext(), "UART TX Intent, but no 'line' supplied", Toast.LENGTH_LONG, GB.ERROR); + return; + } + if (!data.endsWith("\n")) data += "\n"; + try { + TransactionBuilder builder = performInitialized("TX"); + uartTx(builder, data); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Error in TX: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + break; + } + } + } + }; + GBApplication.getContext().registerReceiver(commandReceiver, commandFilter); + } + @Override protected TransactionBuilder initializeDevice(TransactionBuilder builder) { LOG.info("Initializing"); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a0f99796e..e4c53899b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,7 +269,7 @@ Allow Internet Access Allow apps on this device to access the internet Allow Intents - Allow apps on this device to send Android Intents + Allow Bangle.js watch apps to send Android Intents, and allow other apps on Android (like Tasker) to send data to Bangle.js with the com.banglejs.uart.tx Intent. Enables calendar alerts, even when disconnected Sync calendar events Relax firmware checks From c101ac74df1112d8a57f3e7e39c59216c00871c6 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 19 May 2022 14:52:27 +0100 Subject: [PATCH 24/39] Add even more prompts (specifically for location) - thanks Google Play :/ --- .../activities/ControlCenterv2.java | 48 +++++++++++++++---- app/src/main/res/values/strings.xml | 5 +- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 9bd0768c1..e11ce8ae6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -89,6 +89,8 @@ public class ControlCenterv2 extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, GBActivity { public static final int MENU_REFRESH_CODE = 1; + public static final String ACTION_REQUEST_PERMISSIONS + = "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions"; private static PhoneStateListener fakeStateListener; //needed for KK compatibility @@ -123,6 +125,9 @@ public class ControlCenterv2 extends AppCompatActivity case DeviceService.ACTION_REALTIME_SAMPLES: handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE)); break; + case ACTION_REQUEST_PERMISSIONS: + checkAndRequestPermissions(false); + break; } } }; @@ -234,6 +239,7 @@ public class ControlCenterv2 extends AppCompatActivity filterLocal.addAction(GBApplication.ACTION_NEW_DATA); filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED); filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES); + filterLocal.addAction(ACTION_REQUEST_PERMISSIONS); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); refreshPairedDevices(); @@ -250,7 +256,7 @@ public class ControlCenterv2 extends AppCompatActivity // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) // When accepted, we open the Activity for Notification access DialogFragment dialog = new NotifyListenerPermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "PermissionsDialogFragment"); + dialog.show(getSupportFragmentManager(), "NotifyListenerPermissionsDialogFragment"); } } @@ -263,11 +269,11 @@ public class ControlCenterv2 extends AppCompatActivity // Put up a dialog explaining why we need permissions (Polite, but also Play Store policy) // When accepted, we open the Activity for Notification access DialogFragment dialog = new NotifyPolicyPermissionsDialogFragment(); - dialog.show(getSupportFragmentManager(), "PermissionsDialogFragment"); + dialog.show(getSupportFragmentManager(), "NotifyPolicyPermissionsDialogFragment"); } } // Check all the other permissions that we need to for Android M + later - checkAndRequestPermissions(); + checkAndRequestPermissions(true); } ChangeLog cl = createChangeLog(); @@ -405,7 +411,7 @@ public class ControlCenterv2 extends AppCompatActivity } @TargetApi(Build.VERSION_CODES.M) - private void checkAndRequestPermissions() { + private void checkAndRequestPermissions(boolean showDialogFirst) { List wantedPermissions = new ArrayList<>(); if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_DENIED) @@ -478,15 +484,20 @@ public class ControlCenterv2 extends AppCompatActivity } } wantedPermissions.removeAll(shouldNotAsk); - } else { + } else if (!showDialogFirst) { // Permissions have not been asked yet, but now will be prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply(); } if (!wantedPermissions.isEmpty()) { - GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); - ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); - GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); + if (showDialogFirst) { + // Show a dialog - thus will then call checkAndRequestPermissions(false) + DialogFragment dialog = new LocationPermissionsDialogFragment(); + dialog.show(getSupportFragmentManager(), "LocationPermissionsDialogFragment"); + } else { + GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR); + ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); + } } } @@ -579,4 +590,25 @@ public class ControlCenterv2 extends AppCompatActivity } } + /// Called from checkAndRequestPermissions - this puts up a dialog explaining we need permissions, and then calls checkAndRequestPermissions (via an intent) when 'ok' pressed + public static class LocationPermissionsDialogFragment extends DialogFragment { + ControlCenterv2 controlCenter; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + Context context = getContext(); + builder.setMessage(context.getString(R.string.permission_location, + getContext().getString(R.string.app_name), + getContext().getString(R.string.ok))) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } + }); + return builder.create(); + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4c53899b..bfc343204 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1185,8 +1185,9 @@ Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money… ✊ Links All these permissions are required and instability might occur if not granted - %1$s needs access to Notifications in order to display them on your watch.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Notification Access\', then tap \'Back\' to return to %1$s - %1$s needs access to Do Not Disturb settings in order to honour them on your watch.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Do Not Disturb\', then tap \'Back\' to return to %1$s + %1$s needs access to Notifications in order to display them on your watch when your phone\'s screen is off.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Notification Access\', then tap \'Back\' to return to %1$s + %1$s needs access to Do Not Disturb settings in order to honour them on your watch when your phone\'s screen is off.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Do Not Disturb\', then tap \'Back\' to return to %1$s + %1$s needs access to your location in the background to allow it to stay connected to your watch even when your screen is off.\n\nPlease tap \'%2$s\' to agree. CAUTION: Error when checking version information! You should not continue! Saw version name \"%s\" Location must be enabled CompanionDevice Pairing From 5211540e033de8c1ae10b78cdbbacf499950820b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 23 May 2022 15:38:06 +0100 Subject: [PATCH 25/39] Improve JSON stringification for Bangle.js - much more compact, and now handles non-ASCII chars correctly --- .../banglejs/BangleJSDeviceSupport.java | 89 ++++++++++++++----- 1 file changed, 66 insertions(+), 23 deletions(-) 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 8ed215b49..e6fcefb90 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 @@ -231,9 +231,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 iter = jsonObj.keys(); - while (iter.hasNext()) { - String key = iter.next(); - Object v = jsonObj.get(key); - if (v instanceof Integer) { - // ... - } else // .. - if (iter.hasNext()) json+=","; + /// 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;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(); } - return json+"}";*/ + } + + /// Convert a JSON object to a JSON String (NOT 100% JSON compliant) + public String jsonToString(JSONObject jsonObj) { + /* 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 @@ -291,10 +334,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); From 57b53f28be945c717bbc496f1af629ec0e8d33d9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 23 May 2022 16:01:06 +0100 Subject: [PATCH 26/39] Now split words for image conversion on punctuation, not just space. Also fix accidental typos --- .../banglejs/BangleJSDeviceSupport.java | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) 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 e6fcefb90..96482f5a9 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 @@ -262,7 +262,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { 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); } - json += "\""; + return json + "\""; } else if (v instanceof JSONArray) { JSONArray a = (JSONArray)v; String json = "["; @@ -274,7 +274,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } catch (JSONException e) { LOG.warn("jsonToString array error: " + e.getLocalizedMessage()); } - json += jsonToStringInternal(o)); + json += jsonToStringInternal(o); } return json+"]"; } else if (v instanceof JSONObject) { @@ -293,9 +293,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { if (iter.hasNext()) json+=","; } return json+"}"; - } else { // int/double/null - return v.toString(); - } + } // else int/double/null + return v.toString(); } /// Convert a JSON object to a JSON String (NOT 100% JSON compliant) @@ -577,8 +576,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { return true; } - - public String renderUnicodeAsImage(String txt) { if (txt==null) return null; // If we're not doing conversion, pass this right back @@ -586,18 +583,31 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { if (!devicePrefs.getBoolean(PREF_BANGLEJS_TEXT_BITMAP, false)) return txt; // Otherwise split up and check each word - String words[] = txt.split(" "); - for (int i=0;i=0) { + // word split + if (needsTranslate) { // convert word + result += "\0"+bitmapToEspruinoString(textToBitmap(word))+ch; + } else { // or just copy across + result += word+ch; + } + word = ""; + needsTranslate = false; + } else { // TODO: better check? - if (c<0 || c>255) isRenderable = false; + if (ch<0 || ch>255) needsTranslate = true; + word += ch; } - if (!isRenderable) - words[i] = "\0"+bitmapToEspruinoString(textToBitmap(words[i])); } - return String.join(" ", words); + if (needsTranslate) { // convert word + result += "\0"+bitmapToEspruinoString(textToBitmap(word)); + } else { // or just copy across + result += word; + } + return result; } @Override From 961cdd437f1b0cee1097fe65fcd57ced27efe4ea Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 23 May 2022 17:08:10 +0100 Subject: [PATCH 27/39] Make app loader page more reliable, and allow a custom app loader URL to be specified --- .../DeviceSettingsPreferenceConst.java | 1 + .../banglejs/AppsManagementActivity.java | 23 +++++++++++++++---- .../banglejs/BangleJSDeviceSupport.java | 2 ++ app/src/main/res/values/strings.xml | 2 ++ .../main/res/xml/devicesettings_banglejs.xml | 6 +++++ 5 files changed, 30 insertions(+), 4 deletions(-) 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 495a92f7c..d2ff3f607 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 index 3a918b5b9..7e9e8105c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java @@ -1,5 +1,7 @@ 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; @@ -42,6 +44,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDev 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); @@ -49,6 +53,8 @@ public class AppsManagementActivity extends AbstractGBActivity { 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() { } @@ -88,7 +94,6 @@ public class AppsManagementActivity extends AbstractGBActivity { commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED); commandFilter.addAction(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX); LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, commandFilter); - initViews(); } @@ -98,8 +103,14 @@ public class AppsManagementActivity extends AbstractGBActivity { switch (intent.getAction()) { case BangleJSDeviceSupport.BANGLEJS_COMMAND_RX: { String data = String.valueOf(intent.getExtras().get("DATA")); - LOG.info("WebView TX: " + data); - bangleRxData(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; } } @@ -162,7 +173,11 @@ public class AppsManagementActivity extends AbstractGBActivity { settings.setDatabasePath(databasePath); webView.addJavascriptInterface(new WebViewInterface(this), "Android"); webView.setWebContentsDebuggingEnabled(true); // FIXME - webView.loadUrl("https://banglejs.com/apps/android.html"); + + 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){ 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 96482f5a9..289cb1b3d 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 @@ -119,6 +119,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { // Local Intents - for app manager communication public static final String BANGLEJS_COMMAND_TX = "banglejs_command_tx"; public static final String BANGLEJS_COMMAND_RX = "banglejs_command_rx"; + int bangleCommandSeq = 0; // to attempt to stop duplicate packets // Global Intents private static final String BANGLE_ACTION_UART_TX = "com.banglejs.uart.tx"; @@ -553,6 +554,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { // 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/values/strings.xml b/app/src/main/res/values/strings.xml index bfc343204..4d34f8367 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..eed82ff08 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 From 5107887beca97aefdb48bc197beb5dcafc0d9c36 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 25 May 2022 13:26:50 +0100 Subject: [PATCH 28/39] For Bangle.js app remove the Donation link (Google Play Store policy) and add text in the About dialog urging users to donate via the homepage. --- .../gadgetbridge/activities/ControlCenterv2.java | 8 ++++++++ app/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index e11ce8ae6..640d12359 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -41,6 +41,7 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.view.menu.MenuItemImpl; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; @@ -167,6 +168,13 @@ public class ControlCenterv2 extends AppCompatActivity drawer.setDrawerListener(toggle); toggle.syncState(); + /* This sucks but for the play store we're not allowed a donation link. Instead for + the Bangle.js Play Store app we put a message in the About dialog via @string/about_description */ + if (BuildConfig.FLAVOR == "banglejs") { + MenuItemImpl v = (MenuItemImpl) ((NavigationView) drawer.getChildAt(1)).getMenu().findItem(R.id.donation_link); + if (v != null) v.setVisible(false); + } + NavigationView navigationView = findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d34f8367..f2b95c3b6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,13 +9,13 @@ Bangle.js Gadgetbridge Bangle.js Gadgetbridge About Bangle.js Gadgetbridge - Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. + Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access.\n\nDue to Google Play Store policies, we are not allowed a donation link in the app itself, but if you like this app please consider donating via the Gadgetbridge homepage below. Bangle.js running Bangle.js Gadgetbridge Bangle.js Gadgetbridge About Bangle.js Gadgetbridge - Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access. + Android companion app for Bangle.js built on top of the Gadgetbridge project, with added Internet Access.\n\nDue to Google Play Store policies, we are not allowed a donation link in the app itself, but if you like this app please consider donating via the Gadgetbridge homepage below. Bangle.js running Gadgetbridge (Nightly) From 233053f2aba55f9ff1c7ecc5f964991b50db6ce0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 7 Jun 2022 13:01:34 +0100 Subject: [PATCH 29/39] Bangle.js: Keep a log of data sent from the watch, and allow it to be saved with 'Fetch Device Debug Logs' from the debug menu --- .../banglejs/BangleJSDeviceSupport.java | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) 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 289cb1b3d..b9f05dcef 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 @@ -48,14 +48,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.StringReader; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; +import java.util.Locale; import java.util.SimpleTimeZone; import java.util.UUID; import java.lang.reflect.Field; @@ -86,11 +93,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -110,16 +119,21 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private BluetoothGattCharacteristic txCharacteristic = null; private boolean allowHighMTU = false; private int mtuSize = 20; + int bangleCommandSeq = 0; // to attempt to stop duplicate packets when sending Local Intents + /// Current line of data received from Bangle.js private String receivedLine = ""; + /// All characters received from Bangle.js for debug purposes (limited to MAX_RECEIVE_HISTORY_CHARS). Can be dumped with 'Fetch Device Debug Logs' from Debug menu + private String receiveHistory = ""; private boolean realtimeHRM = false; private boolean realtimeStep = false; private int realtimeHRMInterval = 30*60; + /// Maximum amount of characters to store in receiveHistory + public static final int MAX_RECEIVE_HISTORY_CHARS = 100000; // Local Intents - for app manager communication public static final String BANGLEJS_COMMAND_TX = "banglejs_command_tx"; public static final String BANGLEJS_COMMAND_RX = "banglejs_command_rx"; - int bangleCommandSeq = 0; // to attempt to stop duplicate packets // Global Intents private static final String BANGLE_ACTION_UART_TX = "com.banglejs.uart.tx"; @@ -133,6 +147,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private void registerLocalIntents() { IntentFilter commandFilter = new IntentFilter(); + commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED); commandFilter.addAction(BANGLEJS_COMMAND_TX); BroadcastReceiver commandReceiver = new BroadcastReceiver() { @Override @@ -149,6 +164,10 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } break; } + case GBDevice.ACTION_DEVICE_CHANGED: { + LOG.info("ACTION_DEVICE_CHANGED " + gbDevice.getStateString()); + receiveHistory += "\n================================================\nACTION_DEVICE_CHANGED "+gbDevice.getStateString()+" "+(new SimpleDateFormat("yyyy-mm-dd hh:mm:ss")).format(Calendar.getInstance().getTime())+"\n================================================\n"; + } } } }; @@ -544,6 +563,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { mtuSize = chars.length; String packetStr = new String(chars); LOG.info("RX: " + packetStr); + // logging + receiveHistory += packetStr; + if (receiveHistory.length() > MAX_RECEIVE_HISTORY_CHARS) + receiveHistory = receiveHistory.substring(receiveHistory.length() - MAX_RECEIVE_HISTORY_CHARS); + // split into input lines receivedLine += packetStr; while (receivedLine.contains("\n")) { int p = receivedLine.indexOf("\n"); @@ -792,7 +816,28 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchRecordedData(int dataTypes) { - + if (dataTypes == RecordedDataTypes.TYPE_DEBUGLOGS) { + File dir; + try { + dir = FileUtils.getExternalFilesDir(); + } catch (IOException e) { + return; + } + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US); + String filename = "banglejs_debug_" + dateFormat.format(new Date()) + ".log"; + File outputFile = new File(dir, filename ); + LOG.warn("Writing log to "+outputFile.toString()); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); + writer.write(receiveHistory); + writer.close(); + receiveHistory = ""; + GB.toast(getContext(), "Log written to "+filename, Toast.LENGTH_LONG, GB.INFO); + } catch (IOException e) { + LOG.warn("Could not write to file", e); + return; + } + } } @Override From a796860188515c3eb4281b7b5ee8a7af07d2023b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 10 Jun 2022 10:14:37 +0100 Subject: [PATCH 30/39] Support for color dithered bitmaps, and converting emoji->bitmaps --- .../devices/banglejs/BangleJSCoordinator.java | 13 ++ .../banglejs/BangleJSDeviceSupport.java | 163 ++++++++++++++---- 2 files changed, 144 insertions(+), 32 deletions(-) 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 109d6c0a0..32ca36b60 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 @@ -17,6 +17,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.banglejs; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_TEXT_BITMAP; + import android.annotation.TargetApi; import android.app.Activity; import android.bluetooth.le.ScanFilter; @@ -32,6 +34,7 @@ import java.util.Collections; 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; @@ -43,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class BangleJSCoordinator extends AbstractDeviceCoordinator { @@ -167,6 +171,15 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator { return null; } + @Override + public boolean supportsUnicodeEmojis() { + /* we say yes here (because we can't get a handle to our device's prefs to check) + and then in 'renderUnicodeAsImage' we call EmojiConverter.convertUnicodeEmojiToAscii + just like DeviceCommunicationService.sanitizeNotifText would have done if we'd + reported false *if* conversion is disabled */ + return true; + } + public int[] getSupportedDeviceSpecificSettings(GBDevice device) { Vector settings = new Vector(); settings.add(R.xml.devicesettings_banglejs); 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 b9f05dcef..d46ffa76a 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 @@ -62,11 +62,15 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.SimpleTimeZone; import java.util.UUID; import java.lang.reflect.Field; +import io.wax911.emojify.Emoji; +import io.wax911.emojify.EmojiManager; +import io.wax911.emojify.EmojiUtils; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; @@ -99,6 +103,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSuppo import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; +import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -602,13 +607,25 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { return true; } + private String renderUnicodeWordAsImage(String word) { + // check for emoji + boolean hasEmoji = false; + if (EmojiUtils.getAllEmojis()==null) + EmojiManager.initEmojiData(GBApplication.getContext()); + for(Emoji emoji : EmojiUtils.getAllEmojis()) + if (word.contains(emoji.getEmoji())) hasEmoji = true; + // if we had emoji, ensure we create 3 bit color (not 1 bit B&W) + return "\0"+bitmapToEspruinoString(textToBitmap(word), hasEmoji ? BangleJSBitmapStyle.RGB_3BPP : BangleJSBitmapStyle.MONOCHROME); + } + public String renderUnicodeAsImage(String txt) { if (txt==null) return null; - // If we're not doing conversion, pass this right back + /* If we're not doing conversion, pass this right back (we use the EmojiConverter + As we would have done if BangleJSCoordinator.supportsUnicodeEmojis had reported false */ Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); if (!devicePrefs.getBoolean(PREF_BANGLEJS_TEXT_BITMAP, false)) - return txt; - // Otherwise split up and check each word + return EmojiConverter.convertUnicodeEmojiToAscii(txt, GBApplication.getContext()); + // Otherwise split up and check each word String word = "", result = ""; boolean needsTranslate = false; for (int i=0;i=0) { // word split if (needsTranslate) { // convert word - result += "\0"+bitmapToEspruinoString(textToBitmap(word))+ch; + result += renderUnicodeWordAsImage(word)+ch; } else { // or just copy across result += word+ch; } @@ -629,7 +646,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } } if (needsTranslate) { // convert word - result += "\0"+bitmapToEspruinoString(textToBitmap(word)); + result += renderUnicodeWordAsImage(word); } else { // or just copy across result += word; } @@ -954,44 +971,126 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { return image; } - /** Convert an Android bitmap to a base64 string for use in Espruino. - * Currently only 1bpp, no scaling */ - public static byte[] bitmapToEspruinoArray(Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - byte bmp[] = new byte[((height * width + 7) >> 3) + 3]; - int n = 0, c = 0, cn = 0; - bmp[n++] = (byte)width; - bmp[n++] = (byte)height; - bmp[n++] = 1; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - boolean pixel = (bitmap.getPixel(x, y) & 255) > 128; - c = (c << 1) | (pixel?1:0); - cn++; - if (cn == 8) { - bmp[n++] = (byte)c; - cn = 0; - c = 0; - } + public enum BangleJSBitmapStyle { + MONOCHROME, + RGB_3BPP + }; + + /** Used for writing single bits to an array */ + public static class BitWriter { + int n; + byte[] bits; + int currentByte, bitIdx; + + public BitWriter(byte[] array, int offset) { + bits = array; + n = offset; + } + + public void push(boolean v) { + currentByte = (currentByte << 1) | (v?1:0); + bitIdx++; + if (bitIdx == 8) { + bits[n++] = (byte)currentByte; + bitIdx = 0; + currentByte = 0; + } + } + + public void finish() { + if (bitIdx > 0) bits[n++] = (byte)currentByte; + } + } + + /** Convert an Android bitmap to a base64 string for use in Espruino. + * Currently only 1bpp, no scaling */ + public static byte[] bitmapToEspruinoArray(Bitmap bitmap, BangleJSBitmapStyle style) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int bpp = (style==BangleJSBitmapStyle.RGB_3BPP) ? 3 : 1; + byte pixels[] = new byte[width * height]; + final byte PIXELCOL_TRANSPARENT = -1; + final int ditherMatrix[] = {1*16,5*16,7*16,3*16}; // for bayer dithering + // if doing 3bpp, check image to see if it's transparent + boolean allowTransparency = (style != BangleJSBitmapStyle.MONOCHROME); + boolean isTransparent = false; + byte transparentColorIndex = 0; + /* Work out what colour index each pixel should be and write to pixels. + Also figure out if we're transparent at all, and how often each color is used */ + int colUsage[] = new int[8]; + int n = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel = bitmap.getPixel(x, y); + int r = pixel & 255; + int g = (pixel >> 8) & 255; + int b = (pixel >> 16) & 255; + int a = (pixel >> 24) & 255; + boolean pixelTransparent = allowTransparency && (a < 128); + if (pixelTransparent) { + isTransparent = true; + r = g = b = 0; + } + // do dithering here + int ditherAmt = ditherMatrix[(x&1) + (y&1)*2]; + r += ditherAmt; + g += ditherAmt; + b += ditherAmt; + int col = 0; + if (style == BangleJSBitmapStyle.MONOCHROME) + col = ((r+g+b) >= 768)?1:0; + else if (style == BangleJSBitmapStyle.RGB_3BPP) + col = ((r>=256)?1:0) | ((g>=256)?2:0) | ((b>=256)?4:0); + if (!pixelTransparent) colUsage[col]++; // if not transparent, record usage + // save colour, mark transparent separately + pixels[n++] = (byte)(pixelTransparent ? PIXELCOL_TRANSPARENT : col); + } + } + // if we're transparent, find the least-used color, and use that for transparency + if (isTransparent) { + // find least used + int minColUsage = -1; + for (int c=0;c<8;c++) { + if (minColUsage<0 || colUsage[c]> 3) + headerLen]; + bmp[0] = (byte)width; + bmp[1] = (byte)height; + bmp[2] = (byte)(bpp + (isTransparent?128:0)); + if (isTransparent) bmp[3] = transparentColorIndex; + // Now write the image out bit by bit + BitWriter bits = new BitWriter(bmp, headerLen); + n = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixel = pixels[n++]; + for (int b=bpp-1;b>=0;b--) + bits.push(((pixel>>b)&1) != 0); } } - if (cn > 0) bmp[n++] = (byte)c; - //LOG.info("BMP: " + width + "x"+height+" n "+n); - // Convert to base64 return bmp; } /** Convert an Android bitmap to a base64 string for use in Espruino. * Currently only 1bpp, no scaling */ - public static String bitmapToEspruinoString(Bitmap bitmap) { - return new String(bitmapToEspruinoArray(bitmap), StandardCharsets.ISO_8859_1); + public static String bitmapToEspruinoString(Bitmap bitmap, BangleJSBitmapStyle style) { + return new String(bitmapToEspruinoArray(bitmap, style), StandardCharsets.ISO_8859_1); } /** Convert an Android bitmap to a base64 string for use in Espruino. * Currently only 1bpp, no scaling */ - public static String bitmapToEspruinoBase64(Bitmap bitmap) { - return Base64.encodeToString(bitmapToEspruinoArray(bitmap), Base64.DEFAULT).replaceAll("\n",""); + public static String bitmapToEspruinoBase64(Bitmap bitmap, BangleJSBitmapStyle style) { + return Base64.encodeToString(bitmapToEspruinoArray(bitmap, style), Base64.DEFAULT).replaceAll("\n",""); } /** Convert a drawable to a bitmap, for use with bitmapToEspruino */ From cf40dae93b0579e476a8acb198263928c2debf29 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 10 Jun 2022 11:40:53 +0100 Subject: [PATCH 31/39] Bangle.js fix message REPLY option --- .../devices/banglejs/BangleJSDeviceSupport.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 10bd6452f..015fbcb12 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 @@ -106,6 +106,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU; @@ -134,6 +135,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private boolean realtimeStep = false; private int realtimeHRMInterval = 30*60; + private final LimitedQueue/*Long*/ mNotificationReplyAction = new LimitedQueue(16); + /// Maximum amount of characters to store in receiveHistory public static final int MAX_RECEIVE_HISTORY_CHARS = 100000; // Local Intents - for app manager communication @@ -445,6 +448,13 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { deviceEvtNotificationControl.phoneNumber = json.getString("tel"); if (json.has("msg")) deviceEvtNotificationControl.reply = json.getString("msg"); + /* REPLY responses don't use the ID from the event (MUTE/etc seem to), but instead + * they use a handle that was provided in an action list on the onNotification.. event */ + if (deviceEvtNotificationControl.event == GBDeviceEventNotificationControl.Event.REPLY) { + Long foundHandle = (Long)mNotificationReplyAction.lookup((int)deviceEvtNotificationControl.handle); + if (foundHandle!=null) + deviceEvtNotificationControl.handle = foundHandle; + } evaluateGBDeviceEvent(deviceEvtNotificationControl); } break; case "act": { @@ -675,6 +685,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { + for (int i=0;i Date: Fri, 10 Jun 2022 13:08:25 +0100 Subject: [PATCH 32/39] fix merge error --- .../banglejs/BangleJSDeviceSupport.java | 24 ------------------- 1 file changed, 24 deletions(-) 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 c2b0dee99..cdfc30115 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 @@ -139,10 +139,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { /// Maximum amount of characters to store in receiveHistory public static final int MAX_RECEIVE_HISTORY_CHARS = 100000; -<<<<<<< HEAD -======= ->>>>>>> freeyourgadget_master // Local Intents - for app manager communication public static final String BANGLEJS_COMMAND_TX = "banglejs_command_tx"; public static final String BANGLEJS_COMMAND_RX = "banglejs_command_rx"; @@ -1110,27 +1107,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { // Now write the image out bit by bit BitWriter bits = new BitWriter(bmp, headerLen); n = 0; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int pixel = pixels[n++]; - for (int b=bpp-1;b>=0;b--) - bits.push(((pixel>>b)&1) != 0); - } - // rewrite any transparent pixels as the correct color for transparency - for (n=0;n> 3) + headerLen]; - bmp[0] = (byte)width; - bmp[1] = (byte)height; - bmp[2] = (byte)(bpp + (isTransparent?128:0)); - if (isTransparent) bmp[3] = transparentColorIndex; - // Now write the image out bit by bit - BitWriter bits = new BitWriter(bmp, headerLen); - n = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = pixels[n++]; From bb274cd6bebb3a5519c94c2bc6b3f5dd0a5532a1 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 10 Jun 2022 13:11:50 +0100 Subject: [PATCH 33/39] remove un-needed imports --- .../gadgetbridge/devices/banglejs/BangleJSCoordinator.java | 3 --- 1 file changed, 3 deletions(-) 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 32ca36b60..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 @@ -17,8 +17,6 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.banglejs; -import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_TEXT_BITMAP; - import android.annotation.TargetApi; import android.app.Activity; import android.bluetooth.le.ScanFilter; @@ -46,7 +44,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class BangleJSCoordinator extends AbstractDeviceCoordinator { From 657a117c9e1258259517b664e36e0b9dfc5ecad8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 13 Jun 2022 08:46:15 +0100 Subject: [PATCH 34/39] bringing back into sync with freeyourgadget --- .../service/devices/banglejs/BangleJSDeviceSupport.java | 2 -- app/src/main/res/xml/devicesettings_banglejs.xml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) 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 cdfc30115..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 @@ -84,7 +84,6 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicContr import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration; import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -101,7 +100,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; -import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; diff --git a/app/src/main/res/xml/devicesettings_banglejs.xml b/app/src/main/res/xml/devicesettings_banglejs.xml index eed82ff08..60395238b 100644 --- a/app/src/main/res/xml/devicesettings_banglejs.xml +++ b/app/src/main/res/xml/devicesettings_banglejs.xml @@ -12,4 +12,4 @@ android:key="banglejs_webview_url" android:summary="@string/pref_summary_banglejs_webview_url" android:title="@string/pref_title_banglejs_webview_url" /> - \ No newline at end of file + From b9096b3b74bad60c876bb09f791cdbd3af023daa Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 14 Jun 2022 11:23:38 +0100 Subject: [PATCH 35/39] Keep data sent to Bangle.js in the log as well --- .../devices/banglejs/BangleJSDeviceSupport.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 647f96487..8ed323e76 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 @@ -152,6 +152,12 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { registerGlobalIntents(); } + private void addReceiveHistory(String s) { + receiveHistory += s; + if (receiveHistory.length() > MAX_RECEIVE_HISTORY_CHARS) + receiveHistory = receiveHistory.substring(receiveHistory.length() - MAX_RECEIVE_HISTORY_CHARS); + } + private void registerLocalIntents() { IntentFilter commandFilter = new IntentFilter(); commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED); @@ -173,7 +179,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } case GBDevice.ACTION_DEVICE_CHANGED: { LOG.info("ACTION_DEVICE_CHANGED " + gbDevice.getStateString()); - receiveHistory += "\n================================================\nACTION_DEVICE_CHANGED "+gbDevice.getStateString()+" "+(new SimpleDateFormat("yyyy-mm-dd hh:mm:ss")).format(Calendar.getInstance().getTime())+"\n================================================\n"; + addReceiveHistory("\n================================================\nACTION_DEVICE_CHANGED "+gbDevice.getStateString()+" "+(new SimpleDateFormat("yyyy-mm-dd hh:mm:ss")).format(Calendar.getInstance().getTime())+"\n================================================\n"); } } } @@ -260,6 +266,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private void uartTx(TransactionBuilder builder, String str) { byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1); LOG.info("UART TX: " + str); + addReceiveHistory("\n================================================\nSENDING "+str+"\n================================================\n"); // 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 MAX_RECEIVE_HISTORY_CHARS) - receiveHistory = receiveHistory.substring(receiveHistory.length() - MAX_RECEIVE_HISTORY_CHARS); + addReceiveHistory(packetStr); // split into input lines receivedLine += packetStr; while (receivedLine.contains("\n")) { From 1eab878c8e70d3dee24570f0ce2364b632910c98 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 14 Jun 2022 14:30:33 +0100 Subject: [PATCH 36/39] Bangle.js: fix null pointer issue for debug messages, and ensure '...' special char is just replaced with '...' --- .../devices/banglejs/BangleJSDeviceSupport.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 8ed323e76..ca2238e6e 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 @@ -654,6 +654,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { public String renderUnicodeAsImage(String txt) { if (txt==null) return null; + // Simple conversions + txt = txt.replaceAll("…", "..."); /* If we're not doing conversion, pass this right back (we use the EmojiConverter As we would have done if BangleJSCoordinator.supportsUnicodeEmojis had reported false */ Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); @@ -689,11 +691,12 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { - for (int i=0;i Date: Wed, 15 Jun 2022 09:00:46 +0100 Subject: [PATCH 37/39] Extra QUERY_ALL_PACKAGES permission required for SDK 30 (which Play Store needs) --- app/build.gradle | 4 ++++ app/src/main/AndroidManifest.xml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 15a94424f..6e9fad295 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,6 +160,10 @@ android { resValue "string", "about_activity_title", "@string/about_activity_title_banglejs_main" resValue "string", "about_description", "@string/about_description_banglejs_main" resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_banglejs_main" + targetSdkVersion 30 // SDK 30 needed for play store + // SDK 30 stops us querying package names - https://developer.android.com/training/package-visibility + // which we need for getting app name from notifications. We have to request android.permission.QUERY_ALL_PACKAGES + // as well for SDK 30 } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0e0ab834b..6721ef0f3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,6 +31,9 @@ + + + From 4c27f43fea07afe81f6b16abeeb0224b6f1c9f14 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 15 Jun 2022 10:20:53 +0100 Subject: [PATCH 38/39] Bangle.js - fix corruption in images with a certain sequence of chars --- .../banglejs/BangleJSDeviceSupport.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) 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 ca2238e6e..d85aa46f6 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 @@ -283,19 +283,34 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { /* Convert a string, escaping chars we can't send over out UART connection */ String s = (String)v; String json = "\""; + //String rawString = ""; for (int i=0;i='0' && nextCh<='7') json += "\\x0" + ch; + else 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 if (ch<32 || ch==127 || ch==173) + json += "\\x"+Integer.toHexString((ch&255)|256).substring(1); else json += s.charAt(i); } + // if it was less characters to send base64, do that! + if (json.length() > 5+(s.length()*4/3)) { + byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1); + return "atob(\""+Base64.encodeToString(bytes, Base64.DEFAULT).replaceAll("\n","")+"\")"; + } + // for debugging... + //addReceiveHistory("\n---------------------\n"+rawString+"\n---------------------\n"); return json + "\""; } else if (v instanceof JSONArray) { JSONArray a = (JSONArray)v; From fd31acd5172142d0cfa8d2a74384bcc38b9615ba Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 15 Jun 2022 13:41:48 +0100 Subject: [PATCH 39/39] revert last changes - we can just use banglejs/AndroidManifest.xml --- app/src/main/AndroidManifest.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6721ef0f3..0e0ab834b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,9 +31,6 @@ - - -