From ec3b54ef47051bf94b9059459ed905cb94d93a9c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 16:31:22 +0100 Subject: [PATCH 01/12] Bangle.js: Add option for enabling/disabling internet access, and add ability to send Intents direct from Bangle.js # Conflicts: # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java --- .../DeviceSettingsPreferenceConst.java | 3 + .../devices/banglejs/BangleJSCoordinator.java | 15 +- .../banglejs/BangleJSDeviceSupport.java | 133 +++++++++++++----- app/src/main/res/values/strings.xml | 4 + .../res/xml/devicesettings_device_intents.xml | 9 ++ .../devicesettings_device_internet_access.xml | 9 ++ 6 files changed, 138 insertions(+), 35 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 a083c1724..c50de8312 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 ad052bf1e..50675ba29 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,7 +29,9 @@ 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; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; @@ -168,9 +170,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() { - @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); + Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); + if (BuildConfig.INTERNET_ACCESS && devicePrefs.getBoolean(PREF_DEVICE_INTERNET_ACCESS, false)) { + RequestQueue queue = Volley.newRequestQueue(getContext()); + String url = json.getString("url"); + String _xmlPath = ""; + try { + _xmlPath = json.getString("xpath"); + } catch (JSONException e) { } - }); - queue.add(stringRequest); + 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("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(); + uartTxJSONError("http", error.toString()); + } + }); + queue.add(stringRequest); + } else { + if (BuildConfig.INTERNET_ACCESS) + uartTxJSONError("http", "Internet access not enabled, check Gadgetbridge Device Settings"); + else + uartTxJSONError("http", "Internet access not enabled in this Gadgetbridge build"); + } } break; + case "intent": { + Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); + if (devicePrefs.getBoolean(PREF_DEVICE_INTENTS, false)) { + String action = json.getString("action"); + JSONObject extra = json.getJSONObject("extra"); + Intent in = new Intent(); + in.setAction(action); + if (extra != null) { + Iterator 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."); + } } } @@ -322,7 +391,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 895419d9c..b58e6c1e2 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 b324f40a789cea98afcd744642492c550e75960c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 16:35:07 +0100 Subject: [PATCH 02/12] Bangle.js: add support for rendering to a text that can't be displayed into a bitmap and sending the bitmap over # Conflicts: # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java --- .../DeviceSettingsPreferenceConst.java | 2 + .../devices/banglejs/BangleJSCoordinator.java | 1 + .../banglejs/BangleJSDeviceSupport.java | 100 ++++++++++++++++-- app/src/main/res/values/strings.xml | 2 + .../main/res/xml/devicesettings_banglejs.xml | 9 ++ 5 files changed, 103 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 c50de8312..495a92f7c 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 50675ba29..59db90428 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 @@ -171,6 +171,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 4920dfad4..86fbef1aa 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 @@ -23,6 +23,7 @@ import android.content.Intent; 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; @@ -92,6 +93,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; @@ -155,6 +157,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; @@ -164,11 +167,33 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } } + /// Write a string of data, and chunk it up + public String jsonToString(JSONObject jsonObj) { + String json = jsonObj.toString(); + // toString creates '\u0000' instead of '\0' + // FIXME: there have got to be nicer ways of handling this - maybe we just make our own JSON.toString (see below) + json = json.replaceAll("\\\\u000([01234567])", "\\\\$1"); + json = json.replaceAll("\\\\u00([0123456789abcdef][0123456789abcdef])", "\\\\x$1"); + return json; + /*String json = "{"; + Iterator iter = jsonObj.keys(); + while (iter.hasNext()) { + String key = iter.next(); + Object v = jsonObj.get(key); + if (v instanceof Integer) { + // ... + } else // .. + if (iter.hasNext()) json+=","; + } + return json+"}";*/ + } + + /// 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); @@ -196,10 +221,14 @@ 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) { + 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"); } } @@ -424,6 +453,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 { @@ -431,10 +483,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) { @@ -503,7 +555,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) { @@ -540,9 +592,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); @@ -707,9 +759,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]; @@ -732,7 +798,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 b58e6c1e2..41848b7b1 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 f0d204bdc33358a0085382437ae7d5e2564ff54e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 21:03:20 +0100 Subject: [PATCH 03/12] Add ability to receive intents to com.banglejs.uart.tx (from apps like tasker) and send them to Bangle.js Also local intents, paving way for app loader integration --- .../banglejs/BangleJSDeviceSupport.java | 75 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 +- 2 files changed, 76 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 86fbef1aa..53bdb5bd3 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; @@ -101,6 +103,7 @@ import javax.xml.xpath.XPathFactory; public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class); + private BluetoothGattCharacteristic rxCharacteristic = null; private BluetoothGattCharacteristic txCharacteristic = null; private boolean allowHighMTU = false; @@ -111,9 +114,81 @@ 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); + 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); + } + 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 41848b7b1..9af966e22 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 8d2a1278796bb4d7e68000c5e4afd6f2e817c74e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 18 May 2022 13:22:24 +0100 Subject: [PATCH 04/12] 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 9af966e22..e4c53899b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1185,6 +1185,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 51eb9cd8416ba3b39e139933f20ed622e2fd3404 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Thu, 19 May 2022 22:35:34 +0200 Subject: [PATCH 05/12] update changelog --- CHANGELOG.md | 4 +++- app/src/main/res/xml/changelog_master.xml | 3 +++ fastlane/metadata/android/en-US/changelogs/211.txt | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da38ceee8..c6b97a598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ * Mi Band 5/6: Add setting for HR activity monitoring, HR alerts, stress monitoring * Amazfit Neo: Allow to disable beeps for email notifications * Bangle.js: Fix incoming calls in release builds +* Bangle.js build: Add option for enabling/disabling internet access +* Bangle.js: Add ability to receive intents to com.banglejs.uart.tx * Fossil Hybrid HR: Support flexible custom menu on watch * Fossil Hybrid HR: Add support for native DND Call/SMS functionality * VESC: added battery indicator @@ -22,7 +24,7 @@ * Fix crash when calendar is accessed but permission is denied * Add com.asus.asusincallui and com.samsung.android.incallui to blacklist * New icons for Sony overhead headphones, Sony WF 800n and Mi Band 6 - +* When Gadgetbridge needs permissions, pop up a dialog asking nicely and explaining why ### 0.66.0 * Add basic support for Casio GBD-H1000 diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 01eae68c0..bba56bd89 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -14,6 +14,8 @@ Mi Band 5/6: Add setting for HR activity monitoring, HR alerts, stress monitoring Amazfit Neo: Allow to disable beeps for email notifications Bangle.js: Fix incoming calls in release builds + Bangle.js build: Add option for enabling/disabling internet access + Bangle.js: Add ability to receive intents to com.banglejs.uart.tx Fossil Hybrid HR: Support flexible custom menu on watch Fossil Hybrid HR: Add support for native DND Call/SMS functionality VESC: added battery indicator @@ -22,6 +24,7 @@ Fix crash when calendar is accessed but permission is denied Add com.asus.asusincallui and com.samsung.android.incallui to blacklist New icons for Sony overhead headphones, Sony WF 800n and Mi Band 6 + When Gadgetbridge needs permissions, pop up a dialog asking nicely and explaining why Add basic support for Casio GBD-H1000 diff --git a/fastlane/metadata/android/en-US/changelogs/211.txt b/fastlane/metadata/android/en-US/changelogs/211.txt index b92a455c4..f21df9e50 100644 --- a/fastlane/metadata/android/en-US/changelogs/211.txt +++ b/fastlane/metadata/android/en-US/changelogs/211.txt @@ -11,6 +11,8 @@ * Mi Band 5/6: Add setting for HR activity monitoring, HR alerts, stress monitoring * Amazfit Neo: Allow to disable beeps for email notifications * Bangle.js: Fix incoming calls in release builds +* Bangle.js build: Add option for enabling/disabling internet access +* Bangle.js: Add ability to receive intents to com.banglejs.uart.tx * Fossil Hybrid HR: Support flexible custom menu on watch * Fossil Hybrid HR: Add support for native DND Call/SMS functionality * VESC: added battery indicator @@ -19,3 +21,4 @@ * Fix crash when calendar is accessed but permission is denied * Add com.asus.asusincallui and com.samsung.android.incallui to blacklist * New icons for Sony overhead headphones, Sony WF 800n and Mi Band 6 +* When Gadgetbridge needs permissions, pop up a dialog asking nicely and explaining why From 2fe7297aaa95e094ab0df8fb3f8e5ce2814eb968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Wed, 18 May 2022 13:49:47 +0000 Subject: [PATCH 06/12] Translated using Weblate (French) Currently translated at 98.0% (1545 of 1575 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e73fea645..8b76cfc26 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1616,4 +1616,61 @@ Temps de sommeil préféré en heures Bangle.js en fonctionnement Gadgetbridge (version Nightly) Gadgetbrigde version Nightly + Rappel d\'évènements + Trouver l\'appareil + Alertes d\'inactivité + Modes de vibration + Configurer les modes de vibration pour les différentes notifications + Effacer \'%1$s\' + Fuseau horaire + Étiquette + Détails du fuseau horaire + Normal + Sensible + Course à pied en extérieur + Exercice libre + Ellyptique + Sony WF-1000XM3 + Galaxy Buds Pro + Changement de connexion sans coupure + GB nightly non Pebble en cours de fonctionnement + 105 bpm + Remplaçant autonome et sous licence libre pour remplacer les applications propriétaires des fabricants. Version Nightly de Gadgetbridge. Cette ersion contient le fournisseur Pebble renommé pour éviter les conflits, donc certaines intégrations Pebble ne marcheront pas, mais elle peut être installé à côté d\'une installation GadgetBridge existante. + 145 bpm + Alerte de rythme cardiaque (expérimentale) + Surveiller le niveau de stress pendant le repos + Surveillance du rythme cardiaque + Configuration de la surveillance du rythme cardiaque et des seuils d\'alerte + Configuration de la surveillance du rythme cardiaque + 100 bpm + 110 bpm + 112 bpm + 120 bpm + 125 bpm + 130 bpm + 135 bpm + 140 bpm + Etes-vous sûr de vouloir supprimer l\'horloge mondiale \? + Aucun emplacement de libre + Cet appareil n\'a plus d\'emplacement disponible pour des fuseaux horaires (total des emplacements : %1$s) + Faire vibrer le bracelet quand le rythme cardiaque passe un seuil, sans aucune activité physique évidente dans les 10 dernières minutes. Cette fonctionnalité est expérimentale, et n\'a pas été beaucoup testé. + Seuil d\'alerte du rythme cardiaque + Surveillance du stress + Surveillante de l\'activité + Augmenter automatiquement la fréquence de mesure du rythme cardiaque quand le bracelet détecte une activité, pour augmenter la précision de mesure du rythme cardiaque. + Volume ambiant gauche + Volume ambiant droit + Personnaliser le son ambiant + Son ambiant pendant un appel + Options pour le son ambiant + 150 bpm + Recevoir les appels dans les écouteurs lorsque vous les portez + Commutation entre les appareils déja pairés automatiquement + Entendre sa propre voix durant un appel + Horloges mondiales + Configurer les horloges pour différents fuseaux horaires + Sensibilité + Types d\'activités physiques + Choisir les types d\'activité à afficher sur l\'écran d\'activité physique + Vélo en extérieur \ No newline at end of file From ce0729b7a6508e138cff8c35ca22b77bd60e9e11 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Wed, 18 May 2022 10:28:53 +0000 Subject: [PATCH 07/12] Translated using Weblate (Hebrew) Currently translated at 100.0% (1575 of 1575 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 60 ++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index e9c790f96..4863b5340 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1639,4 +1639,64 @@ התראות בטלה איתור המכשיר Sony WF-1000XM3 + בורר שמאל + צליל הקפי + דופק 110 + דופק 120 + דופק 125 + דופק 135 + דופק 140 + מעקב אחר רמות לחץ בזמן מנוחה + מעקב פעילות + להגביר את תדיגות בדיקת הדופק כשהצמיד מזהה פעילות גופנים, כדי לשפר את דיוק לכידת הדופק. + הגדרת מעקב אחר דופק + מעבר חלק בין חיבורים + מעביר את האוזניות בין ההתקנים המצומדים אוטומטית + עצמת שמע הקפית שמאל + עצמת שמע הקפית ימין + התאמת צלילים הקפיים + לשמוע את הקול שלך במהלך שיחה + אפשרויות צליל הקפי + רמת ביטול רעשים פעיל + גבוהה + נמוכה + בורר ימין + מסייע קולי + ביטול רעשים פעיל + צליל הקפי מהיר + עצמת שמע + Spotify + החלפת בקרת שמע + שליטה ברעש עם אחת האוזניות + לאפשר בקרת רעש בשימוש באוזנייה אחת בלבד + גובה צליל הקפי + מרך לצלול + איזון + ביטול רעש →← הקפי + ביטול רעש →← כבוי + הקפי →← כבוי + בקרת רעש + זיהוי קול + לסיים לאחר טווח שקט של: + 10 שניות + הפעלת צליל הקפי והחלשת עוצמת הנגינה אוטומטית לאחר שזוהה קול + נגיעה כפולה בקצה + 15 שניות + להשמיע שיחות דרך האוזניות שלך כשהן בתוך האוזניים + דופק 100 + דופק 105 + דופק 112 + סף התראת דופק + דופק 130 + מעקב אחר לחץ + דופק 145 + דופק 150 + מעקב אחר דופק + התראת דופק (ניסיוני) + 5 שניות + הגדרת מעקב אחר דופק וסף התראה + Galaxy Buds Pro + צליל הקפי במהלך שיחה + לזהות נגיעה כפולה כשבוצעה בקצוות משטח המגע + להפעיל את הרטט בצמיד כאשר הדופק יורד מתחת לסף, ללא פעילות פיזית מובהקת ב־10 הדקות האחרונות. יכולת זאת נמצאת בשלבי ניסוי ולא נבדקה באופן קפדני. \ No newline at end of file From bb3a2a99298c3dbccdde5d7010dcf2118716823b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Thu, 19 May 2022 06:33:55 +0000 Subject: [PATCH 08/12] Translated using Weblate (Turkish) Currently translated at 100.0% (1575 of 1575 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 64 +++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 759fecacc..b7e170fd9 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -531,7 +531,7 @@ DURAKLAT Medya Oynat OYNAT - Oynatmayı AçKapat + Oynatmayı aç/kapat AÇKAPAT Önceki Medya ÖNCEKİ @@ -978,7 +978,7 @@ GPS İzlemesini Göster Yükseklik Kulaç - bpm + kalp atışı/dakika kulaç kulaç/s Düz @@ -1656,4 +1656,64 @@ Aygıt bul Farklı bildirimler için titreşim modellerini yapılandırın Sony WF-1000XM3 + Ortam Sesi Tonu + Bir kulak ile gürültü denetimi + Ortam ←→ Kapalı + Gürültü engelleme ←→ Ortam + Gürültü engelleme ←→ Kapalı + Gürültü denetimi + Ses algılama + 5 saniye + 100 kalp atışı/dakika + 105 kalp atışı/dakika + 120 kalp atışı/dakika + 130 kalp atışı/dakika + 135 kalp atışı/dakika + 140 kalp atışı/dakika + 145 kalp atışı/dakika + 150 kalp atışı/dakika + Kalp ritmi uyarısı (deneysel) + Son 10 dakika içinde herhangi bir belirgin fiziksel etkinlik olmaksızın, kalp ritmi bir eşiği aştığında bilekliği titret. Bu özellik deneyseldir ve kapsamlı bir şekilde test edilmemiştir. + Stres izleme + Dinlenirken stres seviyesini izleyin + Etkinlik izleme + Kalp ritmi yakalama doğruluğunu artırmak için bileklik fiziksel egzersiz algıladığında kalp ritmi algılama sıklığını otomatik olarak arttır. + Takılıysa aramaları kulaklıklarınızdan oynatın + Kesintisiz bağlantı değişimi + Ortam Ses Seviyesi Sol + Arama sırasında ortam sesi + Arama sırasında kendi sesinizi duyun + Etkin Gürültü Engelleme Seviyesi + Yüksek + Düşük + Denetimi sola değiştir + Denetimi sağa değiştir + Sesli Yardımcı + Etkin Gürültü Engelleme + Hızlı Ortam Sesi + Ses Seviyesi + Ortam Sesi + Spotify + Gürültü Denetimini Değiştir + Yalnızca bir kulaklık kullanırken gürültü denetimine izin ver + Yumuşaktan Temize + Denge + Ses algılandıktan sonra ortam sesini etkinleştir ve oynatmayı otomatik olarak azalt + Çift dokunma kenarı + Dokunmatik yüzeye dokunulmadığında bile çift tıklamayı algıla + Şu kadar sessiz kaldıktan sonra sonlandır: + 10 saniye + 15 saniye + 110 kalp atışı/dakika + 112 kalp atışı/dakika + 125 kalp atışı/dakika + Kalp ritmi uyarı eşiği + Kalp Ritmi İzleme + Galaxy Buds Pro + Ortam Sesini Özelleştir + Ortam Sesi Seçenekleri + Kalp ritmi izlemeyi yapılandırın + Kalp ritmi izlemeyi ve uyarı eşiklerini yapılandırın + Kulaklıkları eşleştirilen aygıtlar arasında otomatik olarak değiştirir + Ortam Ses Seviyesi Sağ \ No newline at end of file From 71eeba38f7150ee3933e5329c0e658ad72c919f8 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 18 May 2022 19:42:32 +0000 Subject: [PATCH 09/12] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1575 of 1575 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a8102ff04..03c8d82c6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1686,4 +1686,25 @@ Виявлення подвійного дотику, навіть якщо він не під\'єднаний до сенсорної панелі 10 секунд 5 секунд + 100 ударів на хвилину + Попередження про частоту серцевих скорочень (експериментально) + Поріг попередження про частоту серцевих скорочень + 110 ударів на хвилину + 140 ударів на хвилину + 150 ударів на хвилину + 105 ударів на хвилину + 120 ударів на хвилину + 112 ударів на хвилину + 130 ударів на хвилину + 145 ударів на хвилину + 125 ударів на хвилину + 135 ударів на хвилину + Стеження за рівнем стресу під час відпочинку + Вібрація годинника, коли частота серцевих скорочень перевищує поріг, без будь-якої очевидної фізичної активності протягом останніх 10 хвилин. Ця функція експериментальна, і не була достатньо випробувана. + Стеження за стресом + Стеження за діяльністю + Автоматично збільшувати частоту перевірки частоти серцевих скорочень, коли годинник виявляє фізичні вправи, щоб збільшити точність охоплення серцевого ритму. + Налаштуйте пороги стеження за частотою серцевих скорочень та попереджень + Стеження за частотою серцевих скорочень + Налаштувати стеження за частотою серцевих скорочень \ No newline at end of file From 571f726c744a5af3acd2a6f68726692fea623d34 Mon Sep 17 00:00:00 2001 From: arjan-s Date: Wed, 18 May 2022 11:10:44 +0000 Subject: [PATCH 10/12] Translated using Weblate (Dutch) Currently translated at 99.8% (1573 of 1575 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/nl/ --- app/src/main/res/values-nl/strings.xml | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 037c21543..3c6de9d58 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1640,4 +1640,62 @@ Cloudloze copyleft libre-vervanging voor closed source Android-gadget-apps van leveranciers. Nightly releases van Gadgetbridge. Deze versie heeft de naam van de Pebble-provider hernoemd om conflicten te voorkomen, dus sommige Pebble-gerelateerde integraties zullen niet werken, maar het kan naast de bestaande Gadgetbridge-installatie worden geïnstalleerd. Wereldklokken Gevoeligheid + Omgevingsgeluid + Schakel omgevingsgeluid in en verlaag het volume automatisch nadat spraak is gedetecteerd + 15 seconden + 112 bpm + 120 bpm + 125 bpm + Hartslagwaarschuwing (experimenteel) + Hartslagwaarschuwingsdrempel + Stressmonitoring + Controleer het stressniveau tijdens het rusten + Monitoring van activiteiten + Verhoogt automatisch de hartslagdetectiefrequentie wanneer de band lichaamsbeweging detecteert, om de nauwkeurigheid van de hartslagregistratie te verhogen. + Hartslagbewaking + Hartslagmeting configureren + Hartslagbewaking en waarschuwingsdrempels configureren + Naadloze verbinding + Schakelt automatisch tussen gekoppelde apparaten + Omgevingsvolume links + Omgevingsgeluid aanpassen + Eigen stem horen tijdens gesprekken + Opties voor omgevingsgeluid + Actieve ruisonderdrukking + Hoog + Laag + Spraakassistent + Actieve ruisonderdrukking + Snel omgevingsgeluid + Spotify + Schakel ruisonderdrukking + Ruisonderdrukking met één oordopje + Ruisonderdrukking toestaan bij gebruik van slechts één oordopje + Omgevingsgeluidstoon + Van zacht naar helder + Balans + Omgeving ←→ Uit + Ruisonderdrukking + Spraakdetectie + Dubbeltik op rand + Detecteer dubbel tikken, zelfs als er niet op het touchpad is getikt + Einde na stilte voor: + 5 seconden + 10 seconden + 105 bpm + 110 bpm + Gesprekken voeren via uw oordopjes wanneer ze in uw oren zitten + Omgevingsvolume rechts + 100 bpm + Galaxy Buds Pro + Omgevingsgeluid tijdens gesprekken + 145 bpm + Trillen wanneer de hartslag boven een bepaalde drempel komt, zonder enige duidelijke fysieke activiteit in de laatste 10 minuten. Deze functie is experimenteel en is niet uitgebreid getest. + Ruisonderdrukking ←→ Omgeving + Volume + Ruisonderdrukking ←→ Uit + 130 bpm + 150 bpm + 135 bpm + 140 bpm \ No newline at end of file From 6a4b28c5e0c2fe516e57514c668cb55284168aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Wed, 18 May 2022 13:39:00 +0000 Subject: [PATCH 11/12] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1575 of 1575 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c366beafa..c41056920 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1684,4 +1684,25 @@ 降噪 ←→ 关闭 噪音控制 语音检测 + 100 次/分钟 + 105 次/分钟 + 110 次/分钟 + 130 次/分钟 + 135 次/分钟 + 140 次/分钟 + 150 次/分钟 + 心率警报(实验性) + 在过去 10 分钟内没有任何明显的体力活动的情况下,当心率超过阈值时振动腕带。此功能是实验性的,未经广泛测试。 + 活动监测 + 145 次/分钟 + 112 次/分钟 + 120 次/分钟 + 125 次/分钟 + 心率警报阈值 + 压力监测 + 当手环检测到身体运动时,自动提高心率检测频率,以提高心率捕捉的准确性。 + 休息时监测压力水平 + 心率监测 + 配置心率监测 + 配置心率监测和警报阈值 \ No newline at end of file From 3f053927e0d786912954d5cebde85b1946105556 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 20 May 2022 08:39:47 +0100 Subject: [PATCH 12/12] Pop up a dialog asking about Location permissions (although others get requested too). Conversation at https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2675#issuecomment-455468 --- .../activities/ControlCenterv2.java | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 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(); + } + } }