From e40bd79fbf9a0ed7ac3391414d225173fcc5ea01 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 13 Jun 2022 08:35:32 +0100 Subject: [PATCH 01/34] Bangle.js: Adding built-in app-loader view (available via app management icon). Only available on internet-enabled builds (it's a webview) --- app/src/main/AndroidManifest.xml | 5 + .../DeviceSettingsPreferenceConst.java | 1 + .../banglejs/AppsManagementActivity.java | 188 ++++++++++++++++++ .../devices/banglejs/BangleJSCoordinator.java | 14 +- .../banglejs/BangleJSDeviceSupport.java | 91 +++++++-- .../activity_banglejs_apps_management.xml | 16 ++ app/src/main/res/values/strings.xml | 2 + .../main/res/xml/devicesettings_banglejs.xml | 8 +- 8 files changed, 294 insertions(+), 31 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 120e6c741..0e0ab834b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -468,6 +468,11 @@ + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 3dd558dc3..a07af38d8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -45,6 +45,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_DEVICE_INTENTS = "device_intents"; public static final String PREF_BANGLEJS_TEXT_BITMAP = "banglejs_text_bitmap"; + public static final String PREF_BANGLEJS_WEBVIEW_URL = "banglejs_webview_url"; public static final String PREF_DISCONNECT_NOTIFICATION = "disconnect_notification"; public static final String PREF_DISCONNECT_NOTIFICATION_START = "disconnect_notification_start"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java new file mode 100644 index 000000000..7e9e8105c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java @@ -0,0 +1,188 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.banglejs; + +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DEVICE_INTERNET_ACCESS; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.JavascriptInterface; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANGLEJS_WEBVIEW_URL; + +public class AppsManagementActivity extends AbstractGBActivity { + private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class); + + private WebView webView; + private GBDevice mGBDevice; + private DeviceCoordinator mCoordinator; + /// It seems we can get duplicate broadcasts sometimes - so this helps to avoid that + private int deviceRxSeq = -1; + + public AppsManagementActivity() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_banglejs_apps_management); + + Intent intent = getIntent(); + Bundle bundle = intent.getExtras(); + if (bundle != null) { + mGBDevice = bundle.getParcelable(GBDevice.EXTRA_DEVICE); + } else { + throw new IllegalArgumentException("Must provide a device when invoking this activity"); + } + mCoordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); + } + + private void toast(String data) { + GB.toast(data, Toast.LENGTH_LONG, GB.INFO); + } + + @Override + protected void onPause() { + super.onPause(); + webView.destroy(); + webView = null; + LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver); + finish(); + } + + @Override + protected void onResume() { + super.onResume(); + IntentFilter commandFilter = new IntentFilter(); + commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED); + commandFilter.addAction(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX); + LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, commandFilter); + initViews(); + } + + BroadcastReceiver deviceUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case BangleJSDeviceSupport.BANGLEJS_COMMAND_RX: { + String data = String.valueOf(intent.getExtras().get("DATA")); + int seq = intent.getIntExtra("SEQ",0); + LOG.info("WebView TX: " + data + "("+seq+")"); + if (seq==deviceRxSeq) { + LOG.info("WebView TX DUPLICATE AND IGNORED"); + } else { + deviceRxSeq = seq; + bangleRxData(data); + } + break; + } + } + } + }; + + public class WebViewInterface { + Context mContext; + + WebViewInterface(Context c) { + mContext = c; + } + + /// Called from the WebView when data needs to be sent to the Bangle + @JavascriptInterface + public void bangleTx(String data) { + LOG.info("WebView RX: " + data); + bangleTxData(data); + } + + } + + // Called when data received from Bangle.js - push data to the WebView + public void bangleRxData(String data) { + JSONArray s = new JSONArray(); + s.put(data); + String ss = s.toString(); + final String js = "bangleRx("+ss.substring(1, ss.length()-1)+");"; + LOG.info("WebView TX cmd: " + js); + if (webView!=null) webView.post(new Runnable() { + @Override + public void run() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript(js, null); + } else { + webView.loadUrl("javascript: "+js); + } + } + }); + } + + // Called to send data to Bangle.js + public void bangleTxData(String data) { + Intent intent = new Intent(BangleJSDeviceSupport.BANGLEJS_COMMAND_TX); + intent.putExtra("DATA", data); + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + } + + private void initViews() { + //https://stackoverflow.com/questions/4325639/android-calling-javascript-functions-in-webview + webView = findViewById(R.id.webview); + webView.setWebViewClient(new WebViewClient()); + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDatabaseEnabled(true); + settings.setDomStorageEnabled(true); + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + String databasePath = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); + settings.setDatabasePath(databasePath); + webView.addJavascriptInterface(new WebViewInterface(this), "Android"); + webView.setWebContentsDebuggingEnabled(true); // FIXME + + Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress())); + String url = devicePrefs.getString(PREF_BANGLEJS_WEBVIEW_URL, "").trim(); + if (url.isEmpty()) url = "https://banglejs.com/apps/android.html"; + webView.loadUrl(url); + + webView.setWebViewClient(new WebViewClient(){ + public void onPageFinished(WebView view, String weburl){ + //webView.loadUrl("javascript:showToast('WebView in Espruino')"); + } + }); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java index ea5a39764..6a9416a97 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java @@ -34,6 +34,7 @@ import java.util.Vector; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -135,21 +136,18 @@ public class BangleJSCoordinator extends AbstractDeviceCoordinator { return true; } - @Override - public boolean supportsAppsManagement() { - return false; - } - @Override public int getAlarmSlotCount() { return 10; } @Override - public Class getAppsManagementActivity() { - return null; - } + public boolean supportsAppsManagement() { return BuildConfig.INTERNET_ACCESS; } + @Override + public Class getAppsManagementActivity() { + return BuildConfig.INTERNET_ACCESS ? AppsManagementActivity.class : null; + } @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java index c9cdd7b56..647f96487 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java @@ -258,9 +258,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { /// Write a string of data, and chunk it up private void uartTx(TransactionBuilder builder, String str) { + byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1); LOG.info("UART TX: " + str); - byte[] bytes; - bytes = str.getBytes(StandardCharsets.ISO_8859_1); // FIXME: somehow this is still giving us UTF8 data when we put images in strings. Maybe JSON.stringify is converting to UTF-8? for (int i=0;i0) json += ","; + Object o = null; + try { + o = a.get(i); + } catch (JSONException e) { + LOG.warn("jsonToString array error: " + e.getLocalizedMessage()); + } + json += jsonToStringInternal(o); + } + return json+"]"; + } else if (v instanceof JSONObject) { + JSONObject obj = (JSONObject)v; + String json = "{"; + Iterator iter = obj.keys(); + while (iter.hasNext()) { + String key = iter.next(); + Object o = null; + try { + o = obj.get(key); + } catch (JSONException e) { + LOG.warn("jsonToString object error: " + e.getLocalizedMessage()); + } + json += key+":"+jsonToStringInternal(o); + if (iter.hasNext()) json+=","; + } + return json+"}"; + } // else int/double/null + return v.toString(); + } - /// Write a string of data, and chunk it up + /// Convert a JSON object to a JSON String (NOT 100% JSON compliant) public String jsonToString(JSONObject jsonObj) { - String json = jsonObj.toString(); - // toString creates '\u0000' instead of '\0' - // FIXME: there have got to be nicer ways of handling this - maybe we just make our own JSON.toString (see below) - json = json.replaceAll("\\\\u000([01234567])", "\\\\$1"); - json = json.replaceAll("\\\\u00([0123456789abcdef][0123456789abcdef])", "\\\\x$1"); - return json; - /*String json = "{"; - Iterator iter = jsonObj.keys(); - while (iter.hasNext()) { - String key = iter.next(); - Object v = jsonObj.get(key); - if (v instanceof Integer) { - // ... - } else // .. - if (iter.hasNext()) json+=","; - } - return json+"}";*/ + /* jsonObj.toString() works but breaks char codes>128 (encodes as UTF8?) and also uses + \u0000 when just \0 would do (and so on). + + So we do it manually, which can be more compact anyway. + This is JSON-ish, so not exactly as per JSON1 spec but good enough for Espruino. + */ + return jsonToStringInternal(jsonObj); } /// Write a JSON object of data @@ -327,10 +369,10 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { private void handleUartRxLine(String line) { LOG.info("UART RX LINE: " + line); - + if (line.length()==0) return; if (">Uncaught ReferenceError: \"GB\" is not defined".equals(line)) GB.toast(getContext(), "Gadgetbridge plugin not installed on Bangle.js", Toast.LENGTH_LONG, GB.ERROR); - else if (line.length() > 0 && line.charAt(0)=='{') { + else if (line.charAt(0)=='{') { // JSON - we hope! try { JSONObject json = new JSONObject(line); @@ -567,6 +609,11 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { receivedLine = receivedLine.substring(p+1); handleUartRxLine(line); } + // Send an intent with new data + Intent intent = new Intent(BangleJSDeviceSupport.BANGLEJS_COMMAND_RX); + intent.putExtra("DATA", packetStr); + intent.putExtra("SEQ", bangleCommandSeq++); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } return false; } diff --git a/app/src/main/res/layout/activity_banglejs_apps_management.xml b/app/src/main/res/layout/activity_banglejs_apps_management.xml new file mode 100644 index 000000000..08188a975 --- /dev/null +++ b/app/src/main/res/layout/activity_banglejs_apps_management.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0ca45df0..e7eb1ca37 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -182,6 +182,8 @@ Enable this if your device has no support for your language\'s font Text as Bitmaps If a word cannot be rendered with the watch\'s font, render it to a bitmap in Gadgetbridge and display the bitmap on the watch + App loader URL + If you want a custom app loader put your https://…/android.html URL here. Otherwise leave blank for https://banglejs.com/apps Right-To-Left Enable this if your device can not show right-to-left languages Right-To-Left Max Line Length diff --git a/app/src/main/res/xml/devicesettings_banglejs.xml b/app/src/main/res/xml/devicesettings_banglejs.xml index 9270f8baf..60395238b 100644 --- a/app/src/main/res/xml/devicesettings_banglejs.xml +++ b/app/src/main/res/xml/devicesettings_banglejs.xml @@ -6,4 +6,10 @@ android:key="banglejs_text_bitmap" android:summary="@string/pref_summary_banglejs_text_bitmap" android:title="@string/pref_title_banglejs_text_bitmap" /> - \ No newline at end of file + + From 3ef39433a66bf2413081564364380a6191e2e880 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 13 Jun 2022 08:38:05 +0100 Subject: [PATCH 02/34] Bangle.js build: remove the 'Donate' link for the Bangle.js build only, and instead add a message in the app's About dialog (unfortunately this is needed because Google Play store policy doesn't allow you to 'buy' stuff outside of the store) --- .../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 db8bc03b2..0e9a24e0e 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 e7eb1ca37..469facb0b 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 c11af9e95c08a6b8e0e869cbfe0494b980b8e033 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 14 Jun 2022 11:23:38 +0100 Subject: [PATCH 03/34] 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 f606e85e1b626674e84a5bef70ea91cb9d600ab9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 14 Jun 2022 14:30:33 +0100 Subject: [PATCH 04/34] 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: Sun, 12 Jun 2022 11:48:17 +0100 Subject: [PATCH 05/34] Update Codeberg Terms of Use link --- .github/ISSUE_TEMPLATE.md | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/device_request.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 91137f70d..a4b3d8e57 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -10,7 +10,7 @@ If you just have a question, please ask first in the user chatroom in Matrix: `# #### Before reporting a bug, please confirm the following: - [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question. - [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question. -- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md) +- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) ### I got Gadgetbridge from: * [ ] F-Droid diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 91137f70d..a4b3d8e57 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,7 +10,7 @@ If you just have a question, please ask first in the user chatroom in Matrix: `# #### Before reporting a bug, please confirm the following: - [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question. - [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question. -- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md) +- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) ### I got Gadgetbridge from: * [ ] F-Droid diff --git a/.github/ISSUE_TEMPLATE/device_request.md b/.github/ISSUE_TEMPLATE/device_request.md index e6b67390d..91a45653c 100644 --- a/.github/ISSUE_TEMPLATE/device_request.md +++ b/.github/ISSUE_TEMPLATE/device_request.md @@ -10,7 +10,7 @@ You can use the `Preview` tab ^ above to see final rendering of your report. Use #### Before proceeding further, please confirm the following: - [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find this device mentioned there - [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find this device mentioned there -- [ ] Please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md) +- [ ] Please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) #### Device information diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 7283c0b3d..787399bb3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -9,7 +9,7 @@ If you just have a question or want to discuss some things, please do so in the #### Before requesting a new feature, please confirm the following: - [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question. - [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question. -- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md) +- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/Codeberg/org/src/branch/main/TermsOfUse.md) #### Log files *If applicable, please attach [logs](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Log-Files)* From 4a8523f79048163b9c32087d1fdfc61d97fc927f Mon Sep 17 00:00:00 2001 From: dakhnod Date: Tue, 14 Jun 2022 18:05:41 +0200 Subject: [PATCH 06/34] multi-device-support (#2526) this PR aims to add device for multiple connected devices at once. A lot of stuff already works, some things need to be done: - [x] change DeviceCommunicationService to hold multiple devices and supports - [x] implement connect / disconnect logic - [x] widgets, not really suited for multiple devices, so far - [x] change the notification to show multiple devices - [ ] change GBDeviceService#onFindDevice and similar API functions to target individual devices, not all connected. - [x] move auto-reconnect setting to device settings - [x] fix music event crash - [x] work out behaviour when pressing "connect" from notification - [ ] handle service crashes - [ ] suit coordinator methods for multiple devices of same kind - [x] change ACL_CONNECTED receiver to connect to devices that are not currently registered in DeviceCommunicationService - [ ] adjust after-boot auto-connection logic - [ ] fix hanging device support. Device says disconnected, GB says connected - [x] firmware updater doesn't work My attempt to make onFindDevice work was to change the arguments to ```EventHandler#onFindDevice(GBDevice device, boolean start)```. The Problem is that this forces the device-specific implementations to also accept GBDevice as an argument. Co-authored-by: Daniel Dakhno Co-authored-by: Andreas Shimokawa Co-authored-by: dakhnod Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2526 Co-authored-by: dakhnod Co-committed-by: dakhnod --- .../activities/DebugActivity.java | 33 +- .../activities/FwAppInstallerActivity.java | 26 +- .../DeviceSpecificSettingsFragment.java | 2 + .../adapter/GBDeviceAdapterv2.java | 4 +- .../AbstractBLClassicDeviceCoordinator.java | 8 + .../devices/AbstractBLEDeviceCoordinator.java | 8 + .../devices/AbstractDeviceCoordinator.java | 26 +- .../devices/DeviceCoordinator.java | 35 ++ .../gadgetbridge/devices/DeviceManager.java | 26 +- .../devices/pebble/PebbleCoordinator.java | 22 +- .../devices/qc35/QC35Coordinator.java | 4 +- .../qhybrid/AppsManagementActivity.java | 37 +- .../devices/qhybrid/CalibrationActivity.java | 13 +- .../devices/qhybrid/ConfigActivity.java | 12 +- .../devices/qhybrid/HRConfigActivity.java | 30 +- .../devices/qhybrid/QHybridCoordinator.java | 54 +- .../um25/Coordinator/UM25Coordinator.java | 3 +- .../devices/vesc/VescCoordinator.java | 3 +- .../BluetoothConnectReceiver.java | 32 +- .../BluetoothPairingRequestReceiver.java | 23 +- .../externalevents/NotificationListener.java | 6 +- .../gadgetbridge/impl/GBDeviceService.java | 7 + .../gadgetbridge/model/DeviceService.java | 2 + .../service/DeviceCommunicationService.java | 545 +++++++++++++----- .../service/DeviceSupportFactory.java | 402 ++++++------- .../service/ServiceDeviceSupport.java | 10 +- .../AmazfitBipLiteFirmwareInfo.java | 5 +- .../amazfitbips/AmazfitBipSFirmwareInfo.java | 7 +- .../devices/pebble/PebbleIoThread.java | 4 +- .../service/devices/qc35/QC35BaseSupport.java | 2 +- .../service/devices/qc35/QC35Protocol.java | 2 + .../AutoConnectIntervalReceiver.java | 37 +- .../gadgetbridge/util/BondingUtil.java | 2 +- .../freeyourgadget/gadgetbridge/util/GB.java | 123 +++- .../gadgetbridge/util/GBPrefs.java | 10 +- app/src/main/res/values-de/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + .../devicesettings_reconnect_bl_classic.xml | 11 + .../res/xml/devicesettings_reconnect_ble.xml | 11 + app/src/main/res/xml/preferences.xml | 4 +- 40 files changed, 1036 insertions(+), 561 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLClassicDeviceCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractBLEDeviceCoordinator.java create mode 100644 app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml create mode 100644 app/src/main/res/xml/devicesettings_reconnect_ble.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index a1a1dc3ee..33987a5f6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -68,6 +68,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -272,26 +273,28 @@ public class DebugActivity extends AbstractGBActivity { if (context instanceof GBApplication) { GBApplication gbApp = (GBApplication) context; - final GBDevice device = gbApp.getDeviceManager().getSelectedDevice(); - if (device != null) { - new DatePickerDialog(DebugActivity.this, new DatePickerDialog.OnDateSetListener() { - @Override - public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { - Calendar date = Calendar.getInstance(); - date.set(year, monthOfYear, dayOfMonth); + final List devices = gbApp.getDeviceManager().getSelectedDevices(); + if(devices.size() == 0){ + GB.toast("Device not selected/connected", Toast.LENGTH_LONG, GB.INFO); + return; + } + new DatePickerDialog(DebugActivity.this, new DatePickerDialog.OnDateSetListener() { + @Override + public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + Calendar date = Calendar.getInstance(); + date.set(year, monthOfYear, dayOfMonth); - long timestamp = date.getTimeInMillis() - 1000; - GB.toast("Setting lastSyncTimeMillis: " + timestamp, Toast.LENGTH_LONG, GB.INFO); + long timestamp = date.getTimeInMillis() - 1000; + GB.toast("Setting lastSyncTimeMillis: " + timestamp, Toast.LENGTH_LONG, GB.INFO); + for(GBDevice device : devices){ SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit(); editor.remove("lastSyncTimeMillis"); //FIXME: key reconstruction is BAD editor.putLong("lastSyncTimeMillis", timestamp); editor.apply(); } - }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show(); - } else { - GB.toast("Device not selected/connected", Toast.LENGTH_LONG, GB.INFO); - } + } + }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show(); } @@ -411,8 +414,8 @@ public class DebugActivity extends AbstractGBActivity { public void onClick(View v) { Context context = getApplicationContext(); GBApplication gbApp = (GBApplication) context; - final GBDevice device = gbApp.getDeviceManager().getSelectedDevice(); - if (device != null) { + List devices = gbApp.getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ GBApplication.deleteDeviceSpecificSharedPrefs(device.getAddress()); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java index db6b1ec24..868ac188b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java @@ -214,6 +214,19 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal } else { setInfoText(getString(R.string.installer_activity_wait_while_determining_status)); + List selectedDevices = GBApplication.app().getDeviceManager().getSelectedDevices(); + if(selectedDevices.size() == 0){ + GB.toast("please connect the device you want to send to", Toast.LENGTH_LONG, GB.ERROR); + finish(); + return; + } + if(selectedDevices.size() != 1){ + GB.toast("please connect ONLY the device you want to send to", Toast.LENGTH_LONG, GB.ERROR); + finish(); + return; + } + device = selectedDevices.get(0); + // needed to get the device if (device == null || !device.isConnected()) { connect(); @@ -245,14 +258,17 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal List allCoordinators = DeviceHelper.getInstance().getAllCoordinators(); List sortedCoordinators = new ArrayList<>(allCoordinators.size()); - GBDevice connectedDevice = deviceManager.getSelectedDevice(); - if (connectedDevice != null && connectedDevice.isConnected()) { - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice); - if (coordinator != null) { - connectedCoordinators.add(coordinator); + List devices = deviceManager.getSelectedDevices(); + for(GBDevice connectedDevice : devices){ + if (connectedDevice.isConnected()) { + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice); + if (coordinator != null) { + connectedCoordinators.add(coordinator); + } } } + sortedCoordinators.addAll(connectedCoordinators); for (DeviceCoordinator coordinator : allCoordinators) { if (!connectedCoordinators.contains(coordinator)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index 53808daff..7490be798 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -811,6 +811,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device); String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device); + supportedSettings = ArrayUtils.insert(0, supportedSettings, coordinator.getSupportedDeviceSpecificConnectionSettings()); + if (supportedLanguages != null) { supportedSettings = ArrayUtils.insert(0, supportedSettings, R.xml.devicesettings_language_generic); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java index 5b4f2a3b4..fd2a8900a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java @@ -154,7 +154,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter deviceList = new ArrayList<>(); - private GBDevice selectedDevice = null; + private List selectedDevices = new ArrayList<>(); private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -137,22 +137,13 @@ public class DeviceManager { } private void updateSelectedDevice(GBDevice dev) { - if (selectedDevice == null) { - selectedDevice = dev; - } else { - if (selectedDevice.equals(dev)) { - selectedDevice = dev; // equality vs identity! - } else { - if (selectedDevice.isConnected() && dev.isConnected()) { - LOG.warn("multiple connected devices -- this is currently not really supported"); - selectedDevice = dev; // use the last one that changed - } else if (!selectedDevice.isConnected()) { - selectedDevice = dev; // use the last one that changed - } + selectedDevices.clear(); + for(GBDevice device : deviceList){ + if(device.isInitialized()){ + selectedDevices.add(device); } } - GB.updateNotification(selectedDevice, context); - + GB.updateNotification(selectedDevices, context); } private void refreshPairedDevices() { @@ -184,9 +175,8 @@ public class DeviceManager { return Collections.unmodifiableList(deviceList); } - @Nullable - public GBDevice getSelectedDevice() { - return selectedDevice; + public List getSelectedDevices() { + return selectedDevices; } private void notifyDevicesChanged() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 1131b1a91..499f3b7fd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -25,6 +25,7 @@ import androidx.annotation.NonNull; import java.io.File; import java.io.IOException; +import java.util.List; import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -168,18 +169,27 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsAppListFetching() { - GBDevice mGBDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (mGBDevice != null && mGBDevice.getFirmwareVersion() != null) { - return PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) < 3; + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if(device.getType() == DeviceType.PEBBLE){ + if (device.getFirmwareVersion() != null) { + return PebbleUtils.getFwMajor(device.getFirmwareVersion()) < 3; + } + } } + return false; } @Override public boolean supportsAppReordering() { - GBDevice mGBDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (mGBDevice != null && mGBDevice.getFirmwareVersion() != null) { - return PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3; + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if(device.getType() == DeviceType.PEBBLE){ + if (device.getFirmwareVersion() != null) { + return PebbleUtils.getFwMajor(device.getFirmwareVersion()) >= 3; + } + } } return false; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java index ff144f3e2..992136454 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qc35/QC35Coordinator.java @@ -25,7 +25,7 @@ import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; @@ -35,7 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; -public class QC35Coordinator extends AbstractDeviceCoordinator { +public class QC35Coordinator extends AbstractBLClassicDeviceCoordinator { @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java index fa78dba20..9b2309dff 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java @@ -54,25 +54,34 @@ public class AppsManagementActivity extends AbstractGBActivity { private void refreshInstalledApps() { try { - GBDevice selected = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (selected.getType() != DeviceType.FOSSILQHYBRID || !selected.isConnected() || !selected.getModel().startsWith("DN") || selected.getState() != GBDevice.State.INITIALIZED) { - throw new RuntimeException("Device not connected"); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + boolean deviceFound = false; + for(GBDevice device : devices){ + if ( + device.getType() == DeviceType.FOSSILQHYBRID && + device.isConnected() && + device.getModel().startsWith("DN") && + device.getState() == GBDevice.State.INITIALIZED + ) { + String installedAppsJson = device.getDeviceInfo("INSTALLED_APPS").getDetails(); + if (installedAppsJson == null || installedAppsJson.isEmpty()) { + throw new RuntimeException("can't get installed apps"); + } + JSONArray apps = new JSONArray(installedAppsJson); + appNames = new String[apps.length()]; + for (int i = 0; i < apps.length(); i++) { + appNames[i] = apps.getString(i); + } + appsListView.setAdapter(new AppsListAdapter(this, appNames)); + } + return; } - String installedAppsJson = selected.getDeviceInfo("INSTALLED_APPS").getDetails(); - if (installedAppsJson == null || installedAppsJson.isEmpty()) { - throw new RuntimeException("cant get installed apps"); - } - JSONArray apps = new JSONArray(installedAppsJson); - appNames = new String[apps.length()]; - for (int i = 0; i < apps.length(); i++) { - appNames[i] = apps.getString(i); - } - appsListView.setAdapter(new AppsListAdapter(this, appNames)); - } catch (Exception e) { + } catch (JSONException e) { toast(e.getMessage()); finish(); return; } + throw new RuntimeException("Device not connected"); } class AppsListAdapter extends ArrayAdapter { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java index eb248efe2..87d23e2ed 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/CalibrationActivity.java @@ -29,6 +29,8 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import java.util.List; + import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; @@ -85,9 +87,16 @@ public class CalibrationActivity extends AbstractGBActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_qhybrid_calibration); - GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + boolean atLeastOneConnected = false; + for(GBDevice device : devices){ + if(device.getType() == DeviceType.FOSSILQHYBRID){ + atLeastOneConnected = true; + break; + } + } - if(device == null || device.getType() != DeviceType.FOSSILQHYBRID){ + if(!atLeastOneConnected){ Toast.makeText(this, R.string.watch_not_connected, Toast.LENGTH_LONG).show(); finish(); return; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java index 3b8cbb429..37c600347 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/ConfigActivity.java @@ -303,12 +303,14 @@ public class ConfigActivity extends AbstractGBActivity { } }); - device = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (device == null || device.getType() != DeviceType.FOSSILQHYBRID || device.getFirmwareVersion().charAt(2) != '0') { - setSettingsError(getString(R.string.watch_not_connected)); - } else { - updateSettings(); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if (device.getType() == DeviceType.FOSSILQHYBRID && device.getFirmwareVersion().charAt(2) == '0') { + updateSettings(); + return; + } } + setSettingsError(getString(R.string.watch_not_connected)); } private void updateTimeOffset() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java index fd7537f1d..d57e38d60 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java @@ -46,6 +46,8 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement; @@ -168,19 +170,25 @@ public class HRConfigActivity extends AbstractGBActivity { } // Disable some functions on watches with too new firmware (from official app 4.6.0 and higher) - String fwVersion_str = GBApplication.app().getDeviceManager().getSelectedDevice().getFirmwareVersion(); - fwVersion_str = fwVersion_str.replaceFirst("^DN", "").replaceFirst("r\\.v.*", ""); - Version fwVersion = new Version(fwVersion_str); - if (fwVersion.compareTo(new Version("1.0.2.20")) >= 0) { - findViewById(R.id.qhybrid_widget_add).setEnabled(false); - for (int i = 0; i < widgetButtonsMapping.size(); i++) { - final int widgetButtonId = widgetButtonsMapping.keyAt(i); - findViewById(widgetButtonId).setEnabled(false); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if(device.getType() == DeviceType.FOSSILQHYBRID){ + String fwVersion_str = device.getFirmwareVersion(); + fwVersion_str = fwVersion_str.replaceFirst("^DN", "").replaceFirst("r\\.v.*", ""); + Version fwVersion = new Version(fwVersion_str); + if (fwVersion.compareTo(new Version("1.0.2.20")) >= 0) { + findViewById(R.id.qhybrid_widget_add).setEnabled(false); + for (int i = 0; i < widgetButtonsMapping.size(); i++) { + final int widgetButtonId = widgetButtonsMapping.keyAt(i); + findViewById(widgetButtonId).setEnabled(false); + } + findViewById(R.id.qhybrid_set_background).setEnabled(false); + findViewById(R.id.qhybrid_unset_background).setEnabled(false); + GB.toast(getString(R.string.fossil_hr_warning_firmware_too_new), Toast.LENGTH_LONG, GB.INFO); + } } - findViewById(R.id.qhybrid_set_background).setEnabled(false); - findViewById(R.id.qhybrid_unset_background).setEnabled(false); - GB.toast(getString(R.string.fossil_hr_warning_firmware_too_new), Toast.LENGTH_LONG, GB.INFO); } + } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java index 90eebd59e..e22b7b73a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridCoordinator.java @@ -35,6 +35,7 @@ import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -42,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -54,7 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.Version; -public class QHybridCoordinator extends AbstractDeviceCoordinator { +public class QHybridCoordinator extends AbstractBLEDeviceCoordinator { private static final Logger LOG = LoggerFactory.getLogger(QHybridCoordinator.class); @NonNull @@ -89,8 +91,13 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityDataFetching() { - GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); - return connectedDevice != null && connectedDevice.getType() == DeviceType.FOSSILQHYBRID && connectedDevice.getState() == GBDevice.State.INITIALIZED; + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if(isFossilHybrid(device) && device.getState() == GBDevice.State.INITIALIZED){ + return true; + } + } + return false; } @Override @@ -129,11 +136,14 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator { } private boolean supportsAlarmConfiguration() { - GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); - if(connectedDevice == null || connectedDevice.getType() != DeviceType.FOSSILQHYBRID || connectedDevice.getState() != GBDevice.State.INITIALIZED){ - return false; + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + LOG.debug("devices count: " + devices.size()); + for(GBDevice device : devices){ + if(isFossilHybrid(device) && device.getState() == GBDevice.State.INITIALIZED){ + return true; + } } - return true; + return false; } @Override @@ -268,22 +278,34 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator { } private boolean isHybridHR() { - GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (connectedDevice != null) { - return connectedDevice.getName().startsWith("Hybrid HR"); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if(isFossilHybrid(device) && device.getName().startsWith("Hybrid HR")){ + return true; + } } return false; } private Version getFirmwareVersion() { - String firmware = GBApplication.app().getDeviceManager().getSelectedDevice().getFirmwareVersion(); - if (firmware != null) { - Matcher matcher = Pattern.compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+").matcher(firmware); // DN1.0.2.19r.v5 - if (matcher.find()) { - firmware = matcher.group(0); - return new Version(firmware); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + if(isFossilHybrid(device)){ + String firmware = device.getFirmwareVersion(); + if (firmware != null) { + Matcher matcher = Pattern.compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+").matcher(firmware); // DN1.0.2.19r.v5 + if (matcher.find()) { + firmware = matcher.group(0); + return new Version(firmware); + } + } } } + return null; } + + private boolean isFossilHybrid(GBDevice device){ + return device.getType() == DeviceType.FOSSILQHYBRID; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java index 4acf35b94..e24f017d3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/um25/Coordinator/UM25Coordinator.java @@ -14,6 +14,7 @@ import java.util.Collections; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -26,7 +27,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support; -public class UM25Coordinator extends AbstractDeviceCoordinator { +public class UM25Coordinator extends AbstractBLEDeviceCoordinator { @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java index 773b7e6a1..da084a95a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -36,7 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; -public class VescCoordinator extends AbstractDeviceCoordinator { +public class VescCoordinator extends AbstractBLEDeviceCoordinator { public final static String UUID_SERVICE_SERIAL_HM10 = "0000ffe0-0000-1000-8000-00805f9b34fb"; public final static String UUID_CHARACTERISTIC_SERIAL_TX_HM10 = "0000ffe1-0000-1000-8000-00805f9b34fb"; public final static String UUID_CHARACTERISTIC_SERIAL_RX_HM10 = "0000ffe1-0000-1000-8000-00805f9b34fb"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java index c60b3ac76..b6bc4c84b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java @@ -20,13 +20,17 @@ import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; public class BluetoothConnectReceiver extends BroadcastReceiver { @@ -46,14 +50,30 @@ public class BluetoothConnectReceiver extends BroadcastReceiver { } BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - LOG.info("connection attempt detected from or to " + device.getAddress() + "(" + device.getName() + ")"); + LOG.info("connection attempt detected from " + device.getAddress() + "(" + device.getName() + ")"); - GBDevice gbDevice = service.getGBDevice(); - if (gbDevice != null) { - if (device.getAddress().equals(gbDevice.getAddress()) && gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { - LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); - GBApplication.deviceService().connect(); + GBDevice gbDevice = getKnownDeviceByAddressOrNull(device.getAddress()); + if(gbDevice == null){ + LOG.info("connected device {} unknown", device.getAddress()); + return; + } + SharedPreferences deviceSpecificPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()); + boolean reactToConnection = deviceSpecificPreferences.getBoolean(GBPrefs.DEVICE_CONNECT_BACK, false); + reactToConnection |= gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT; + if(!reactToConnection){ + return; + } + LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); + GBApplication.deviceService().connect(gbDevice); + } + + private GBDevice getKnownDeviceByAddressOrNull(String deviceAddress){ + List knownDevices = GBApplication.app().getDeviceManager().getDevices(); + for(GBDevice device : knownDevices){ + if(device.getAddress().equals(deviceAddress)){ + return device; } } + return null; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java index 56104c5cf..efb90d8a5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java @@ -47,21 +47,26 @@ public class BluetoothPairingRequestReceiver extends BroadcastReceiver { if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { return; } - - GBDevice gbDevice = service.getGBDevice(); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (gbDevice == null || device == null) { + if (device == null) { return; } - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); + GBDevice gbDevice = null; try { - if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_NONE) { - LOG.info("Aborting unwanted pairing request"); - abortBroadcast(); + gbDevice = service.getDeviceByAddress(device.getAddress()); + + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); + try { + if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_NONE) { + LOG.info("Aborting unwanted pairing request"); + abortBroadcast(); + } + } catch (Exception e) { + LOG.warn("Could not abort pairing request process"); } - } catch (Exception e) { - LOG.warn("Could not abort pairing request process"); + } catch (DeviceCommunicationService.DeviceNotFoundException e) { + e.printStackTrace(); } } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 547afd723..38e641787 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -767,9 +767,9 @@ public class NotificationListener extends NotificationListenerService { notificationsActive.removeAll(notificationsToRemove); // Send notification remove request to device - GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (connectedDevice != null) { - Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(connectedDevice.getAddress())); + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ + Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); if (prefs.getBoolean("autoremove_notifications", true)) { for (int id : notificationsToRemove) { LOG.info("Notification " + id + " removed, will ask device to delete it"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index ff04bde7c..409ff088b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -118,6 +118,13 @@ public class GBDeviceService implements DeviceService { invokeService(intent); } + @Override + public void disconnect(@Nullable GBDevice device) { + Intent intent = createIntent().setAction(ACTION_DISCONNECT) + .putExtra(GBDevice.EXTRA_DEVICE, device); + invokeService(intent); + } + @Override public void disconnect() { Intent intent = createIntent().setAction(ACTION_DISCONNECT); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index c653dae77..d26f55944 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -154,6 +154,8 @@ public interface DeviceService extends EventHandler { void connect(@Nullable GBDevice device, boolean firstTime); + void disconnect(@Nullable GBDevice device); + void disconnect(); void quit(); 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 defa78adf..8e251f03c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -44,9 +44,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Arrays; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; @@ -189,6 +191,108 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEA import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WORLD_CLOCKS; public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener { + public static class DeviceStruct{ + private GBDevice device; + private DeviceCoordinator coordinator; + private DeviceSupport deviceSupport; + + public GBDevice getDevice() { + return device; + } + + public void setDevice(GBDevice device) { + this.device = device; + } + + public DeviceCoordinator getCoordinator() { + return coordinator; + } + + public void setCoordinator(DeviceCoordinator coordinator) { + this.coordinator = coordinator; + } + + public DeviceSupport getDeviceSupport() { + return deviceSupport; + } + + public void setDeviceSupport(DeviceSupport deviceSupport) { + this.deviceSupport = deviceSupport; + } + } + + private class FeatureSet{ + private boolean supportsWeather = false; + private boolean supportsActivityDataFetching = false; + private boolean supportsCalendarEvents = false; + private boolean supportsMusicInfo = false; + + public boolean supportsWeather() { + return supportsWeather; + } + + public void setSupportsWeather(boolean supportsWeather) { + this.supportsWeather = supportsWeather; + } + + public boolean supportsActivityDataFetching() { + return supportsActivityDataFetching; + } + + public void setSupportsActivityDataFetching(boolean supportsActivityDataFetching) { + this.supportsActivityDataFetching = supportsActivityDataFetching; + } + + public boolean supportsCalendarEvents() { + return supportsCalendarEvents; + } + + public void setSupportsCalendarEvents(boolean supportsCalendarEvents) { + this.supportsCalendarEvents = supportsCalendarEvents; + } + + public boolean supportsMusicInfo() { + return supportsMusicInfo; + } + + public void setSupportsMusicInfo(boolean supportsMusicInfo) { + this.supportsMusicInfo = supportsMusicInfo; + } + + public void logicalOr(DeviceCoordinator operand){ + if(operand.supportsCalendarEvents()){ + setSupportsCalendarEvents(true); + } + if(operand.supportsWeather()){ + setSupportsWeather(true); + } + if(operand.supportsActivityDataFetching()){ + setSupportsActivityDataFetching(true); + } + if(operand.supportsMusicInfo()){ + setSupportsMusicInfo(true); + } + } + } + + public static class DeviceNotFoundException extends GBException{ + private final String address; + + public DeviceNotFoundException(GBDevice device) { + this.address = device.getAddress(); + } + + public DeviceNotFoundException(String address) { + this.address = address; + } + + @Nullable + @Override + public String getMessage() { + return String.format("device %s not found cached", address); + } + } + private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class); @SuppressLint("StaticFieldLeak") // only used for test cases private static DeviceSupportFactory DEVICE_SUPPORT_FACTORY = null; @@ -196,9 +300,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private boolean mStarted = false; private DeviceSupportFactory mFactory; - private GBDevice mGBDevice = null; - private DeviceSupport mDeviceSupport; - private DeviceCoordinator mCoordinator = null; + private final ArrayList deviceStructs = new ArrayList<>(1); private PhoneCallReceiver mPhoneCallReceiver = null; private SMSReceiver mSMSReceiver = null; @@ -236,6 +338,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere * * @param factory */ + @SuppressWarnings("JavaDoc") public static void setDeviceSupportFactory(DeviceSupportFactory factory) { DEVICE_SUPPORT_FACTORY = factory; } @@ -248,20 +351,45 @@ public class DeviceCommunicationService extends Service implements SharedPrefere @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) { + if(GBDevice.ACTION_DEVICE_CHANGED.equals(action)){ GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); - if (mGBDevice != null && mGBDevice.equals(device)) { - mGBDevice = device; - mCoordinator = DeviceHelper.getInstance().getCoordinator(device); - boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized()); - setReceiversEnableState(enableReceivers, mGBDevice.isInitialized(), mCoordinator); - } else { - LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + device); + + // create a new instance of the changed devices coordinator, in case it's capabilities changed + DeviceStruct cachedStruct = getDeviceStructOrNull(device); + if(cachedStruct != null) { + cachedStruct.setDevice(device); + DeviceCoordinator newCoordinator = DeviceHelper.getInstance().getCoordinator(device); + cachedStruct.setCoordinator(newCoordinator); } + updateReceiversState(); } } }; + private void updateReceiversState(){ + boolean enableReceivers = false; + boolean anyDeviceInitialized = false; + + FeatureSet features = new FeatureSet(); + + for(DeviceStruct struct: deviceStructs){ + DeviceSupport deviceSupport = struct.getDeviceSupport(); + if((deviceSupport != null && deviceSupport.useAutoConnect()) || isDeviceInitialized(struct.getDevice())){ + enableReceivers = true; + } + if(isDeviceInitialized(struct.getDevice())){ + anyDeviceInitialized = true; + } + + DeviceCoordinator coordinator = struct.getCoordinator(); + if(coordinator != null){ + features.logicalOr(coordinator); + } + } + + setReceiversEnableState(enableReceivers, anyDeviceInitialized, features); + } + @Override public void onCreate() { LOG.debug("DeviceCommunicationService is being created"); @@ -269,6 +397,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)); mFactory = getDeviceSupportFactory(); + mBlueToothConnectReceiver = new BluetoothConnectReceiver(this); + registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)); + if (hasPrefs()) { getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this); } @@ -306,14 +437,15 @@ public class DeviceCommunicationService extends Service implements SharedPrefere return START_NOT_STICKY; } - if (mDeviceSupport == null || (!isInitialized() && !action.equals(ACTION_DISCONNECT) && (!mDeviceSupport.useAutoConnect() || isConnected()))) { + // TODO + /*if (mDeviceSupport == null || (!isInitialized() && !action.equals(ACTION_DISCONNECT) && (!mDeviceSupport.useAutoConnect() || isConnected()))) { // trying to send notification without valid Bluetooth connection if (mGBDevice != null) { // at least send back the current device state mGBDevice.sendDeviceUpdateIntent(this); } return START_STICKY; - } + }*/ } // when we get past this, we should have valid mDeviceSupport and mGBDevice instances @@ -338,41 +470,73 @@ public class DeviceCommunicationService extends Service implements SharedPrefere btDeviceAddress = gbDevice.getAddress(); } + if(gbDevice == null){ + return START_NOT_STICKY; + } + boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT; if (prefs != null && prefs.getPreferences() != null) { prefs.getPreferences().edit().putString("last_device_address", btDeviceAddress).apply(); - autoReconnect = getGBPrefs().getAutoReconnect(); + autoReconnect = getGBPrefs().getAutoReconnect(gbDevice); } - if (gbDevice != null && !isConnecting() && !isConnected()) { - setDeviceSupport(null); - try { - DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice); - if (deviceSupport != null) { - setDeviceSupport(deviceSupport); - if (firstTime) { - deviceSupport.connectFirstTime(); - } else { - deviceSupport.setAutoReconnect(autoReconnect); - deviceSupport.connect(); - } - } else { - GB.toast(this, getString(R.string.cannot_connect, "Can't create device support"), Toast.LENGTH_SHORT, GB.ERROR); - } - } catch (Exception e) { - GB.toast(this, getString(R.string.cannot_connect, e.getMessage()), Toast.LENGTH_SHORT, GB.ERROR, e); - setDeviceSupport(null); + DeviceStruct registeredStruct = getDeviceStructOrNull(gbDevice); + if(registeredStruct != null){ + boolean deviceAlreadyConnected = isDeviceConnecting(registeredStruct.getDevice()) || isDeviceConnected(registeredStruct.getDevice()); + if(deviceAlreadyConnected){ + break; } - } else if (mGBDevice != null) { - // send an update at least - mGBDevice.sendDeviceUpdateIntent(this); + try { + removeDeviceSupport(gbDevice); + } catch (DeviceNotFoundException e) { + e.printStackTrace(); + } + }else{ + registeredStruct = new DeviceStruct(); + registeredStruct.setDevice(gbDevice); + registeredStruct.setCoordinator(DeviceHelper.getInstance().getCoordinator(gbDevice)); + deviceStructs.add(registeredStruct); + } + + try { + DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice); + if (deviceSupport != null) { + setDeviceSupport(gbDevice, deviceSupport); + if (firstTime) { + deviceSupport.connectFirstTime(); + } else { + deviceSupport.setAutoReconnect(autoReconnect); + deviceSupport.connect(); + } + } else { + GB.toast(this, getString(R.string.cannot_connect, "Can't create device support"), Toast.LENGTH_SHORT, GB.ERROR); + } + } catch (Exception e) { + GB.toast(this, getString(R.string.cannot_connect, e.getMessage()), Toast.LENGTH_SHORT, GB.ERROR, e); + } + + for(DeviceStruct struct2 : deviceStructs){ + struct2.getDevice().sendDeviceUpdateIntent(this); } break; default: - if (mDeviceSupport == null || mGBDevice == null) { - LOG.warn("device support:" + mDeviceSupport + ", device: " + mGBDevice + ", aborting"); - } else { - handleAction(intent, action, prefs); + GBDevice targetedDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); + ArrayList targetedDevices = new ArrayList<>(); + if(targetedDevice != null){ + targetedDevices.add(targetedDevice); + }else{ + for(GBDevice device : getGBDevices()){ + if(isDeviceInitialized(device)){ + targetedDevices.add(device); + } + } + } + for (GBDevice device1 : targetedDevices) { + try { + handleAction(intent, action, device1); + } catch (DeviceNotFoundException e) { + e.printStackTrace(); + } } break; } @@ -383,21 +547,35 @@ public class DeviceCommunicationService extends Service implements SharedPrefere * @param text original text * @return 'text' or a new String without non supported chars like emoticons, etc. */ - private String sanitizeNotifText(String text) { + private String sanitizeNotifText(String text, GBDevice device) throws DeviceNotFoundException { if (text == null || text.length() == 0) return text; - text = mDeviceSupport.customStringFilter(text); + text = getDeviceSupport(device).customStringFilter(text); - if (!mCoordinator.supportsUnicodeEmojis()) { + if (!getDeviceCoordinator(device).supportsUnicodeEmojis()) { return EmojiConverter.convertUnicodeEmojiToAscii(text, getApplicationContext()); } return text; } - private void handleAction(Intent intent, String action, Prefs prefs) { - Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress())); + private DeviceCoordinator getDeviceCoordinator(GBDevice device) throws DeviceNotFoundException { + if(device == null){ + throw new DeviceNotFoundException("null"); + } + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().equals(device)){ + return struct.getCoordinator(); + } + } + throw new DeviceNotFoundException(device); + } + + private void handleAction(Intent intent, String action, GBDevice device) throws DeviceNotFoundException { + DeviceSupport deviceSupport = getDeviceSupport(device); + + Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); boolean transliterate = devicePrefs.getBoolean(PREF_TRANSLITERATION_ENABLED, false); if (transliterate) { @@ -410,16 +588,16 @@ public class DeviceCommunicationService extends Service implements SharedPrefere switch (action) { case ACTION_REQUEST_DEVICEINFO: - mGBDevice.sendDeviceUpdateIntent(this); + device.sendDeviceUpdateIntent(this); break; case ACTION_NOTIFICATION: { int desiredId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); NotificationSpec notificationSpec = new NotificationSpec(desiredId); notificationSpec.phoneNumber = intent.getStringExtra(EXTRA_NOTIFICATION_PHONENUMBER); - notificationSpec.sender = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SENDER)); - notificationSpec.subject = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT)); - notificationSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE)); - notificationSpec.body = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_BODY)); + notificationSpec.sender = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SENDER), device); + notificationSpec.subject = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT), device); + notificationSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE), device); + notificationSpec.body = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_BODY), device); notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE); notificationSpec.attachedActions = (ArrayList) intent.getSerializableExtra(EXTRA_NOTIFICATION_ACTIONS); @@ -449,11 +627,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere notificationSpec.cannedReplies = replies.toArray(new String[0]); } - mDeviceSupport.onNotification(notificationSpec); + deviceSupport.onNotification(notificationSpec); break; } case ACTION_DELETE_NOTIFICATION: { - mDeviceSupport.onDeleteNotification(intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)); + deviceSupport.onDeleteNotification(intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)); break; } case ACTION_ADD_CALENDAREVENT: { @@ -462,61 +640,62 @@ public class DeviceCommunicationService extends Service implements SharedPrefere calendarEventSpec.type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1); calendarEventSpec.timestamp = intent.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1); calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1); - calendarEventSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE)); - calendarEventSpec.description = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION)); - calendarEventSpec.location = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_LOCATION)); - mDeviceSupport.onAddCalendarEvent(calendarEventSpec); + calendarEventSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE), device); + calendarEventSpec.description = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION), device); + calendarEventSpec.location = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_LOCATION), device); + deviceSupport.onAddCalendarEvent(calendarEventSpec); break; } case ACTION_DELETE_CALENDAREVENT: { long id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1); byte type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1); - mDeviceSupport.onDeleteCalendarEvent(type, id); + deviceSupport.onDeleteCalendarEvent(type, id); break; } case ACTION_RESET: { int flags = intent.getIntExtra(EXTRA_RESET_FLAGS, 0); - mDeviceSupport.onReset(flags); + deviceSupport.onReset(flags); break; } case ACTION_HEARTRATE_TEST: { - mDeviceSupport.onHeartRateTest(); + deviceSupport.onHeartRateTest(); break; } case ACTION_FETCH_RECORDED_DATA: { - int dataTypes = intent.getIntExtra(EXTRA_RECORDED_DATA_TYPES, 0); - mDeviceSupport.onFetchRecordedData(dataTypes); - break; - } - case ACTION_DISCONNECT: { - mDeviceSupport.dispose(); - if (mGBDevice != null) { - mGBDevice.setState(GBDevice.State.NOT_CONNECTED); - mGBDevice.sendDeviceUpdateIntent(this); + if(!getDeviceCoordinator(device).supportsActivityDataFetching()){ + break; } - setReceiversEnableState(false, false, null); - mGBDevice = null; - mDeviceSupport = null; - mCoordinator = null; + int dataTypes = intent.getIntExtra(EXTRA_RECORDED_DATA_TYPES, 0); + deviceSupport.onFetchRecordedData(dataTypes); break; } + case ACTION_DISCONNECT: + try { + removeDeviceSupport(device); + } catch (DeviceNotFoundException e) { + e.printStackTrace(); + } + device.setState(GBDevice.State.NOT_CONNECTED); + device.sendDeviceUpdateIntent(this); + updateReceiversState(); + break; case ACTION_FIND_DEVICE: { boolean start = intent.getBooleanExtra(EXTRA_FIND_START, false); - mDeviceSupport.onFindDevice(start); + deviceSupport.onFindDevice(start); break; } case ACTION_SET_CONSTANT_VIBRATION: { int intensity = intent.getIntExtra(EXTRA_VIBRATION_INTENSITY, 0); - mDeviceSupport.onSetConstantVibration(intensity); + deviceSupport.onSetConstantVibration(intensity); break; } case ACTION_CALLSTATE: CallSpec callSpec = new CallSpec(); callSpec.command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED); callSpec.number = intent.getStringExtra(EXTRA_CALL_PHONENUMBER); - callSpec.name = sanitizeNotifText(intent.getStringExtra(EXTRA_CALL_DISPLAYNAME)); + callSpec.name = sanitizeNotifText(intent.getStringExtra(EXTRA_CALL_DISPLAYNAME), device); callSpec.dndSuppressed = intent.getIntExtra(EXTRA_CALL_DNDSUPPRESSED, 0); - mDeviceSupport.onSetCallState(callSpec); + deviceSupport.onSetCallState(callSpec); break; case ACTION_SETCANNEDMESSAGES: int type = intent.getIntExtra(EXTRA_CANNEDMESSAGES_TYPE, -1); @@ -525,24 +704,24 @@ public class DeviceCommunicationService extends Service implements SharedPrefere CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec(); cannedMessagesSpec.type = type; cannedMessagesSpec.cannedMessages = cannedMessages; - mDeviceSupport.onSetCannedMessages(cannedMessagesSpec); + deviceSupport.onSetCannedMessages(cannedMessagesSpec); break; case ACTION_SETTIME: - mDeviceSupport.onSetTime(); + deviceSupport.onSetTime(); break; case ACTION_SETMUSICINFO: MusicSpec musicSpec = new MusicSpec(); - musicSpec.artist = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ARTIST)); - musicSpec.album = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ALBUM)); - musicSpec.track = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_TRACK)); + musicSpec.artist = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ARTIST), device); + musicSpec.album = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ALBUM), device); + musicSpec.track = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_TRACK), device); musicSpec.duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0); musicSpec.trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0); musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0); - mDeviceSupport.onSetMusicInfo(musicSpec); + deviceSupport.onSetMusicInfo(musicSpec); break; case ACTION_SET_PHONE_VOLUME: float phoneVolume = intent.getFloatExtra(EXTRA_PHONE_VOLUME, 0); - mDeviceSupport.onSetPhoneVolume(phoneVolume); + deviceSupport.onSetPhoneVolume(phoneVolume); break; case ACTION_SETMUSICSTATE: MusicStateSpec stateSpec = new MusicStateSpec(); @@ -551,23 +730,23 @@ public class DeviceCommunicationService extends Service implements SharedPrefere stateSpec.position = intent.getIntExtra(EXTRA_MUSIC_POSITION, 0); stateSpec.playRate = intent.getIntExtra(EXTRA_MUSIC_RATE, 0); stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte) 0); - mDeviceSupport.onSetMusicState(stateSpec); + deviceSupport.onSetMusicState(stateSpec); break; case ACTION_REQUEST_APPINFO: - mDeviceSupport.onAppInfoReq(); + deviceSupport.onAppInfoReq(); break; case ACTION_REQUEST_SCREENSHOT: - mDeviceSupport.onScreenshotReq(); + deviceSupport.onScreenshotReq(); break; case ACTION_STARTAPP: { UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); boolean start = intent.getBooleanExtra(EXTRA_APP_START, true); - mDeviceSupport.onAppStart(uuid, start); + deviceSupport.onAppStart(uuid, start); break; } case ACTION_DELETEAPP: { UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); - mDeviceSupport.onAppDelete(uuid); + deviceSupport.onAppDelete(uuid); break; } case ACTION_APP_CONFIGURE: { @@ -577,87 +756,87 @@ public class DeviceCommunicationService extends Service implements SharedPrefere if (intent.hasExtra(EXTRA_APP_CONFIG_ID)) { id = intent.getIntExtra(EXTRA_APP_CONFIG_ID, 0); } - mDeviceSupport.onAppConfiguration(uuid, config, id); + deviceSupport.onAppConfiguration(uuid, config, id); break; } case ACTION_APP_REORDER: { UUID[] uuids = (UUID[]) intent.getSerializableExtra(EXTRA_APP_UUID); - mDeviceSupport.onAppReorder(uuids); + deviceSupport.onAppReorder(uuids); break; } case ACTION_INSTALL: Uri uri = intent.getParcelableExtra(EXTRA_URI); if (uri != null) { LOG.info("will try to install app/fw"); - mDeviceSupport.onInstallApp(uri); + deviceSupport.onInstallApp(uri); } break; case ACTION_SET_ALARMS: ArrayList alarms = (ArrayList) intent.getSerializableExtra(EXTRA_ALARMS); - mDeviceSupport.onSetAlarms(alarms); + deviceSupport.onSetAlarms(alarms); break; case ACTION_SET_REMINDERS: ArrayList reminders = (ArrayList) intent.getSerializableExtra(EXTRA_REMINDERS); - mDeviceSupport.onSetReminders(reminders); + deviceSupport.onSetReminders(reminders); break; case ACTION_SET_WORLD_CLOCKS: ArrayList clocks = (ArrayList) intent.getSerializableExtra(EXTRA_WORLD_CLOCKS); - mDeviceSupport.onSetWorldClocks(clocks); + deviceSupport.onSetWorldClocks(clocks); break; case ACTION_ENABLE_REALTIME_STEPS: { boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); - mDeviceSupport.onEnableRealtimeSteps(enable); + deviceSupport.onEnableRealtimeSteps(enable); break; } case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: { boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); - mDeviceSupport.onEnableHeartRateSleepSupport(enable); + deviceSupport.onEnableHeartRateSleepSupport(enable); break; } case ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL: { int seconds = intent.getIntExtra(EXTRA_INTERVAL_SECONDS, 0); - mDeviceSupport.onSetHeartRateMeasurementInterval(seconds); + deviceSupport.onSetHeartRateMeasurementInterval(seconds); break; } case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: { boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); - mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable); + deviceSupport.onEnableRealtimeHeartRateMeasurement(enable); break; } case ACTION_SEND_CONFIGURATION: { String config = intent.getStringExtra(EXTRA_CONFIG); - mDeviceSupport.onSendConfiguration(config); + deviceSupport.onSendConfiguration(config); break; } case ACTION_READ_CONFIGURATION: { String config = intent.getStringExtra(EXTRA_CONFIG); - mDeviceSupport.onReadConfiguration(config); + deviceSupport.onReadConfiguration(config); break; } case ACTION_TEST_NEW_FUNCTION: { - mDeviceSupport.onTestNewFunction(); + deviceSupport.onTestNewFunction(); break; } case ACTION_SEND_WEATHER: { WeatherSpec weatherSpec = intent.getParcelableExtra(EXTRA_WEATHER); if (weatherSpec != null) { - mDeviceSupport.onSendWeather(weatherSpec); + deviceSupport.onSendWeather(weatherSpec); } break; } case ACTION_SET_LED_COLOR: int color = intent.getIntExtra(EXTRA_LED_COLOR, 0); if (color != 0) { - mDeviceSupport.onSetLedColor(color); + deviceSupport.onSetLedColor(color); } break; case ACTION_POWER_OFF: - mDeviceSupport.onPowerOff(); + deviceSupport.onPowerOff(); break; case ACTION_SET_FM_FREQUENCY: float frequency = intent.getFloatExtra(EXTRA_FM_FREQUENCY, -1); if (frequency != -1) { - mDeviceSupport.onSetFmFrequency(frequency); + deviceSupport.onSetFmFrequency(frequency); } break; case ACTION_SET_GPS_LOCATION: @@ -671,18 +850,79 @@ public class DeviceCommunicationService extends Service implements SharedPrefere * Disposes the current DeviceSupport instance (if any) and sets a new device support instance * (if not null). * - * @param deviceSupport + * @param deviceSupport deviceSupport to reokace/add */ - private void setDeviceSupport(@Nullable DeviceSupport deviceSupport) { - if (deviceSupport != mDeviceSupport && mDeviceSupport != null) { - mDeviceSupport.dispose(); - mDeviceSupport = null; - mGBDevice = null; - mCoordinator = null; + private void setDeviceSupport(GBDevice device, DeviceSupport deviceSupport) throws DeviceNotFoundException { + DeviceStruct deviceStruct = getDeviceStruct(device); + DeviceSupport cachedDeviceSupport = deviceStruct.getDeviceSupport(); + if (deviceSupport != cachedDeviceSupport && cachedDeviceSupport != null) { + cachedDeviceSupport.dispose(); + } + deviceStruct.setDeviceSupport(deviceSupport); + } + + private void removeDeviceSupport(GBDevice device) throws DeviceNotFoundException { + DeviceStruct struct = getDeviceStruct(device); + if(struct.getDeviceSupport() != null){ + struct.getDeviceSupport().dispose(); } - mDeviceSupport = deviceSupport; - mGBDevice = mDeviceSupport != null ? mDeviceSupport.getDevice() : null; - mCoordinator = mGBDevice != null ? DeviceHelper.getInstance().getCoordinator(mGBDevice) : null; + struct.setDeviceSupport(null); + } + + private DeviceStruct getDeviceStructOrNull(GBDevice device){ + DeviceStruct deviceStruct = null; + try { + deviceStruct = getDeviceStruct(device); + } catch (DeviceNotFoundException e) { + e.printStackTrace(); + } + return deviceStruct; + } + + public DeviceStruct getDeviceStruct(GBDevice device) throws DeviceNotFoundException { + if(device == null){ + throw new DeviceNotFoundException("null"); + } + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().equals(device)){ + return struct; + } + } + throw new DeviceNotFoundException(device); + } + + public GBDevice getDeviceByAddress(String deviceAddress) throws DeviceNotFoundException { + if(deviceAddress == null){ + throw new DeviceNotFoundException(deviceAddress); + } + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().getAddress().equals(deviceAddress)){ + return struct.getDevice(); + } + } + throw new DeviceNotFoundException(deviceAddress); + } + + public GBDevice getDeviceByAddressOrNull(String deviceAddress){ + GBDevice device = null; + try { + device = getDeviceByAddress(deviceAddress); + } catch (DeviceNotFoundException e) { + e.printStackTrace(); + } + return device; + } + + private DeviceSupport getDeviceSupport(GBDevice device) throws DeviceNotFoundException { + if(device == null){ + throw new DeviceNotFoundException("null"); + } + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().equals(device)){ + return struct.getDeviceSupport(); + } + } + throw new DeviceNotFoundException(device); } private void start() { @@ -697,30 +937,49 @@ public class DeviceCommunicationService extends Service implements SharedPrefere return mStarted; } - private boolean isConnected() { - return mGBDevice != null && mGBDevice.isConnected(); + private boolean isDeviceConnected(GBDevice device) { + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().equals(device) ){ + return struct.getDevice().isConnected(); + } + } + return false; } - private boolean isConnecting() { - return mGBDevice != null && mGBDevice.isConnecting(); + private boolean isDeviceConnecting(GBDevice device) { + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().equals(device) ){ + return struct.getDevice().isConnecting(); + } + } + return false; } - private boolean isInitialized() { - return mGBDevice != null && mGBDevice.isInitialized(); + private boolean isDeviceInitialized(GBDevice device) { + for(DeviceStruct struct : deviceStructs){ + if(struct.getDevice().equals(device) ){ + return struct.getDevice().isInitialized(); + } + } + return false; } - private void setReceiversEnableState(boolean enable, boolean initialized, DeviceCoordinator coordinator) { + private void setReceiversEnableState(boolean enable, boolean initialized, FeatureSet features) { LOG.info("Setting broadcast receivers to: " + enable); - if (enable && initialized && coordinator != null && coordinator.supportsCalendarEvents()) { + if(enable && features == null){ + throw new RuntimeException("features cannot be null when enabling receivers"); + } + + if (enable && initialized && features.supportsCalendarEvents()) { if (mCalendarReceiver == null && getPrefs().getBoolean("enable_calendar_sync", true)) { if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) { IntentFilter calendarIntentFilter = new IntentFilter(); calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED"); calendarIntentFilter.addDataScheme("content"); calendarIntentFilter.addDataAuthority("com.android.calendar", null); - mCalendarReceiver = new CalendarReceiver(mGBDevice); + mCalendarReceiver = new CalendarReceiver(null); registerReceiver(mCalendarReceiver, calendarIntentFilter); } } @@ -756,7 +1015,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mPebbleReceiver = new PebbleReceiver(); registerReceiver(mPebbleReceiver, new IntentFilter("com.getpebble.action.SEND_NOTIFICATION")); } - if (mMusicPlaybackReceiver == null && coordinator != null && coordinator.supportsMusicInfo()) { + if (mMusicPlaybackReceiver == null && features.supportsMusicInfo()) { mMusicPlaybackReceiver = new MusicPlaybackReceiver(); IntentFilter filter = new IntentFilter(); for (String action : mMusicActions) { @@ -771,10 +1030,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere filter.addAction("android.intent.action.TIMEZONE_CHANGED"); registerReceiver(mTimeChangeReceiver, filter); } - if (mBlueToothConnectReceiver == null) { - mBlueToothConnectReceiver = new BluetoothConnectReceiver(this); - registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)); - } if (mBlueToothPairingRequestReceiver == null) { mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this); registerReceiver(mBlueToothPairingRequestReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)); @@ -790,7 +1045,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere } // Weather receivers - if ( coordinator != null && coordinator.supportsWeather()) { + if (features.supportsWeather()) { if (GBApplication.isRunningOreoOrLater()) { if (mLineageOsWeatherReceiver == null) { mLineageOsWeatherReceiver = new LineageOsWeatherReceiver(); @@ -818,7 +1073,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere } if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) && - coordinator != null && coordinator.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) { + features.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) { mGBAutoFetchReceiver = new GBAutoFetchReceiver(); registerReceiver(mGBAutoFetchReceiver, new IntentFilter("android.intent.action.USER_PRESENT")); } @@ -847,10 +1102,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mTimeChangeReceiver); mTimeChangeReceiver = null; } - if (mBlueToothConnectReceiver != null) { - unregisterReceiver(mBlueToothConnectReceiver); - mBlueToothConnectReceiver = null; - } if (mBlueToothPairingRequestReceiver != null) { unregisterReceiver(mBlueToothPairingRequestReceiver); @@ -900,7 +1151,15 @@ public class DeviceCommunicationService extends Service implements SharedPrefere LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); setReceiversEnableState(false, false, null); // disable BroadcastReceivers - setDeviceSupport(null); + unregisterReceiver(mBlueToothConnectReceiver); + + for(GBDevice device : getGBDevices()){ + try { + removeDeviceSupport(device); + } catch (DeviceNotFoundException e) { + e.printStackTrace(); + } + } GB.removeNotification(GB.NOTIFICATION_ID, this); // need to do this because the updated notification won't be cancelled when service stops } @@ -911,10 +1170,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (GBPrefs.AUTO_RECONNECT.equals(key)) { - boolean autoReconnect = getGBPrefs().getAutoReconnect(); - if (mDeviceSupport != null) { - mDeviceSupport.setAutoReconnect(autoReconnect); + if (GBPrefs.DEVICE_AUTO_RECONNECT.equals(key)) { + for(DeviceStruct deviceStruct : deviceStructs){ + boolean autoReconnect = getGBPrefs().getAutoReconnect(deviceStruct.getDevice()); + deviceStruct.getDeviceSupport().setAutoReconnect(autoReconnect); } } if (GBPrefs.CHART_MAX_HEART_RATE.equals(key) || GBPrefs.CHART_MIN_HEART_RATE.equals(key)) { @@ -934,7 +1193,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere return GBApplication.getGBPrefs(); } - public GBDevice getGBDevice() { - return mGBDevice; + public GBDevice[] getGBDevices() { + GBDevice[] devices = new GBDevice[deviceStructs.size()]; + for(int i = 0; i < devices.length; i++){ + devices[i] = deviceStructs.get(i).getDevice(); + } + return devices; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index 0172aef6c..e1c7c6cc5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -161,248 +161,170 @@ public class DeviceSupportFactory { } } + private ServiceDeviceSupport createServiceDeviceSupport(GBDevice device){ + switch (device.getType()) { + case PEBBLE: + return new ServiceDeviceSupport(new PebbleSupport()); + case MIBAND: + return new ServiceDeviceSupport(new MiBandSupport(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case MIBAND2: + return new ServiceDeviceSupport(new HuamiSupport(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case MIBAND3: + return new ServiceDeviceSupport(new MiBand3Support()); + case MIBAND4: + return new ServiceDeviceSupport(new MiBand4Support()); + case MIBAND5: + return new ServiceDeviceSupport(new MiBand5Support()); + case MIBAND6: + return new ServiceDeviceSupport(new MiBand6Support()); + case AMAZFITBIP: + return new ServiceDeviceSupport(new AmazfitBipSupport()); + case AMAZFITBIP_LITE: + return new ServiceDeviceSupport(new AmazfitBipLiteSupport()); + case AMAZFITBIPS: + return new ServiceDeviceSupport(new AmazfitBipSSupport()); + case AMAZFITBIPS_LITE: + return new ServiceDeviceSupport(new AmazfitBipSLiteSupport()); + case AMAZFITBIPU: + return new ServiceDeviceSupport(new AmazfitBipUSupport()); + case AMAZFITBIPUPRO: + return new ServiceDeviceSupport(new AmazfitBipUProSupport()); + case AMAZFITPOP: + return new ServiceDeviceSupport(new AmazfitPopSupport()); + case AMAZFITPOPPRO: + return new ServiceDeviceSupport(new AmazfitPopProSupport()); + case AMAZFITGTR: + return new ServiceDeviceSupport(new AmazfitGTRSupport()); + case AMAZFITGTR_LITE: + return new ServiceDeviceSupport(new AmazfitGTRLiteSupport()); + case AMAZFITGTR2: + return new ServiceDeviceSupport(new AmazfitGTR2Support()); + case ZEPP_E: + return new ServiceDeviceSupport(new ZeppESupport()); + case AMAZFITGTR2E: + return new ServiceDeviceSupport(new AmazfitGTR2eSupport()); + case AMAZFITTREX: + return new ServiceDeviceSupport(new AmazfitTRexSupport()); + case AMAZFITTREXPRO: + return new ServiceDeviceSupport(new AmazfitTRexProSupport()); + case AMAZFITGTS: + return new ServiceDeviceSupport(new AmazfitGTSSupport()); + case AMAZFITVERGEL: + return new ServiceDeviceSupport(new AmazfitVergeLSupport()); + case AMAZFITGTS2: + return new ServiceDeviceSupport(new AmazfitGTS2Support()); + case AMAZFITGTS2_MINI: + return new ServiceDeviceSupport(new AmazfitGTS2MiniSupport()); + case AMAZFITGTS2E: + return new ServiceDeviceSupport(new AmazfitGTS2eSupport()); + case AMAZFITCOR: + return new ServiceDeviceSupport(new AmazfitCorSupport()); + case AMAZFITCOR2: + return new ServiceDeviceSupport(new AmazfitCor2Support()); + case AMAZFITBAND5: + return new ServiceDeviceSupport(new AmazfitBand5Support()); + case AMAZFITX: + return new ServiceDeviceSupport(new AmazfitXSupport()); + case AMAZFITNEO: + return new ServiceDeviceSupport(new AmazfitNeoSupport()); + case VIBRATISSIMO: + return new ServiceDeviceSupport(new VibratissimoSupport(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case LIVEVIEW: + return new ServiceDeviceSupport(new LiveviewSupport()); + case HPLUS: + case MAKIBESF68: + case EXRIZUK8: + case Q8: + return new ServiceDeviceSupport(new HPlusSupport(device.getType())); + case NO1F1: + return new ServiceDeviceSupport(new No1F1Support()); + case TECLASTH30: + return new ServiceDeviceSupport(new TeclastH30Support()); + case XWATCH: + return new ServiceDeviceSupport(new XWatchSupport()); + case FOSSILQHYBRID: + return new ServiceDeviceSupport(new QHybridSupport()); + case ZETIME: + return new ServiceDeviceSupport(new ZeTimeDeviceSupport()); + case ID115: + return new ServiceDeviceSupport(new ID115Support()); + case WATCH9: + return new ServiceDeviceSupport(new Watch9DeviceSupport(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case WATCHXPLUS: + return new ServiceDeviceSupport(new WatchXPlusDeviceSupport(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case ROIDMI: + return new ServiceDeviceSupport(new RoidmiSupport()); + case ROIDMI3: + return new ServiceDeviceSupport(new RoidmiSupport()); + case Y5: + return new ServiceDeviceSupport(new Y5Support()); + case CASIOGB6900: + return new ServiceDeviceSupport(new CasioGB6900DeviceSupport()); + case CASIOGBX100: + return new ServiceDeviceSupport(new CasioGBX100DeviceSupport()); + case MISCALE2: + return new ServiceDeviceSupport(new MiScale2DeviceSupport()); + case BFH16: + return new ServiceDeviceSupport(new BFH16DeviceSupport()); + case MIJIA_LYWSD02: + return new ServiceDeviceSupport(new MijiaLywsd02Support()); + case MAKIBESHR3: + return new ServiceDeviceSupport(new MakibesHR3DeviceSupport()); + case ITAG: + return new ServiceDeviceSupport(new ITagSupport()); + case NUTMINI: + return new ServiceDeviceSupport(new NutSupport()); + case BANGLEJS: + return new ServiceDeviceSupport(new BangleJSDeviceSupport()); + case TLW64: + return new ServiceDeviceSupport(new TLW64Support()); + case PINETIME_JF: + return new ServiceDeviceSupport(new PineTimeJFSupport()); + case SG2: + return new ServiceDeviceSupport(new HPlusSupport(DeviceType.SG2)); + case LEFUN: + return new ServiceDeviceSupport(new LefunDeviceSupport()); + case SONY_SWR12: + return new ServiceDeviceSupport(new SonySWR12DeviceSupport()); + case WASPOS: + return new ServiceDeviceSupport(new WaspOSDeviceSupport()); + case SMAQ2OSS: + return new ServiceDeviceSupport(new SMAQ2OSSSupport()); + case UM25: + return new ServiceDeviceSupport(new UM25Support()); + case DOMYOS_T540: + return new ServiceDeviceSupport(new DomyosT540Support(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case FITPRO: + return new ServiceDeviceSupport(new FitProDeviceSupport(), ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING); + case NOTHING_EAR1: + return new ServiceDeviceSupport(new Ear1Support()); + case GALAXY_BUDS: + return new ServiceDeviceSupport(new GalaxyBudsDeviceSupport()); + case GALAXY_BUDS_LIVE: + return new ServiceDeviceSupport(new GalaxyBudsDeviceSupport()); + case GALAXY_BUDS_PRO: + return new ServiceDeviceSupport(new GalaxyBudsDeviceSupport(), ServiceDeviceSupport.Flags.BUSY_CHECKING); + case SONY_WH_1000XM3: + return new ServiceDeviceSupport(new SonyHeadphonesSupport()); + case SONY_WH_1000XM4: + return new ServiceDeviceSupport(new SonyHeadphonesSupport()); + case SONY_WF_SP800N: + return new ServiceDeviceSupport(new SonyHeadphonesSupport(), ServiceDeviceSupport.Flags.BUSY_CHECKING); + case SONY_WF_1000XM3: + return new ServiceDeviceSupport(new SonyHeadphonesSupport()); + case VESC_NRF: + case VESC_HM10: + return new ServiceDeviceSupport(new VescDeviceSupport(device.getType())); + case BOSE_QC35: + return new ServiceDeviceSupport(new QC35BaseSupport()); + } + return null; + } + private DeviceSupport createBTDeviceSupport(GBDevice gbDevice) throws GBException { if (mBtAdapter != null && mBtAdapter.isEnabled()) { - DeviceSupport deviceSupport = null; - try { - switch (gbDevice.getType()) { - case PEBBLE: - deviceSupport = new ServiceDeviceSupport(new PebbleSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIBAND: - deviceSupport = new ServiceDeviceSupport(new MiBandSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIBAND2: - deviceSupport = new ServiceDeviceSupport(new HuamiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIBAND3: - deviceSupport = new ServiceDeviceSupport(new MiBand3Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIBAND4: - deviceSupport = new ServiceDeviceSupport(new MiBand4Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIBAND5: - deviceSupport = new ServiceDeviceSupport(new MiBand5Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIBAND6: - deviceSupport = new ServiceDeviceSupport(new MiBand6Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBIP: - deviceSupport = new ServiceDeviceSupport(new AmazfitBipSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBIP_LITE: - deviceSupport = new ServiceDeviceSupport(new AmazfitBipLiteSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBIPS: - deviceSupport = new ServiceDeviceSupport(new AmazfitBipSSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBIPS_LITE: - deviceSupport = new ServiceDeviceSupport(new AmazfitBipSLiteSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBIPU: - deviceSupport = new ServiceDeviceSupport(new AmazfitBipUSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBIPUPRO: - deviceSupport = new ServiceDeviceSupport(new AmazfitBipUProSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITPOP: - deviceSupport = new ServiceDeviceSupport(new AmazfitPopSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITPOPPRO: - deviceSupport = new ServiceDeviceSupport(new AmazfitPopProSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTR: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTRSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTR_LITE: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTRLiteSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTR2: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTR2Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case ZEPP_E: - deviceSupport = new ServiceDeviceSupport(new ZeppESupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTR2E: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTR2eSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITTREX: - deviceSupport = new ServiceDeviceSupport(new AmazfitTRexSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITTREXPRO: - deviceSupport = new ServiceDeviceSupport(new AmazfitTRexProSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTS: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTSSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITVERGEL: - deviceSupport = new ServiceDeviceSupport(new AmazfitVergeLSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTS2: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTS2Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTS2_MINI: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTS2MiniSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITGTS2E: - deviceSupport = new ServiceDeviceSupport(new AmazfitGTS2eSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITCOR: - deviceSupport = new ServiceDeviceSupport(new AmazfitCorSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITCOR2: - deviceSupport = new ServiceDeviceSupport(new AmazfitCor2Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITBAND5: - deviceSupport = new ServiceDeviceSupport(new AmazfitBand5Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITX: - deviceSupport = new ServiceDeviceSupport(new AmazfitXSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case AMAZFITNEO: - deviceSupport = new ServiceDeviceSupport(new AmazfitNeoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case VIBRATISSIMO: - deviceSupport = new ServiceDeviceSupport(new VibratissimoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case LIVEVIEW: - deviceSupport = new ServiceDeviceSupport(new LiveviewSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case HPLUS: - deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.HPLUS), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MAKIBESF68: - deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.MAKIBESF68), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case EXRIZUK8: - deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.EXRIZUK8), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case Q8: - deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.Q8), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case NO1F1: - deviceSupport = new ServiceDeviceSupport(new No1F1Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case TECLASTH30: - deviceSupport = new ServiceDeviceSupport(new TeclastH30Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case XWATCH: - deviceSupport = new ServiceDeviceSupport(new XWatchSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case FOSSILQHYBRID: - deviceSupport = new ServiceDeviceSupport(new QHybridSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case ZETIME: - deviceSupport = new ServiceDeviceSupport(new ZeTimeDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case ID115: - deviceSupport = new ServiceDeviceSupport(new ID115Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case WATCH9: - deviceSupport = new ServiceDeviceSupport(new Watch9DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case WATCHXPLUS: - deviceSupport = new ServiceDeviceSupport(new WatchXPlusDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case ROIDMI: - deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case ROIDMI3: - deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case Y5: - deviceSupport = new ServiceDeviceSupport(new Y5Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case CASIOGB6900: - deviceSupport = new ServiceDeviceSupport(new CasioGB6900DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case CASIOGBX100: - deviceSupport = new ServiceDeviceSupport(new CasioGBX100DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MISCALE2: - deviceSupport = new ServiceDeviceSupport(new MiScale2DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case BFH16: - deviceSupport = new ServiceDeviceSupport(new BFH16DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MIJIA_LYWSD02: - deviceSupport = new ServiceDeviceSupport(new MijiaLywsd02Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case MAKIBESHR3: - deviceSupport = new ServiceDeviceSupport(new MakibesHR3DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case ITAG: - deviceSupport = new ServiceDeviceSupport(new ITagSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case NUTMINI: - deviceSupport = new ServiceDeviceSupport(new NutSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case BANGLEJS: - deviceSupport = new ServiceDeviceSupport(new BangleJSDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case TLW64: - deviceSupport = new ServiceDeviceSupport(new TLW64Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case PINETIME_JF: - deviceSupport = new ServiceDeviceSupport(new PineTimeJFSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SG2: - deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.SG2), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case LEFUN: - deviceSupport = new ServiceDeviceSupport(new LefunDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SONY_SWR12: - deviceSupport = new ServiceDeviceSupport(new SonySWR12DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case WASPOS: - deviceSupport = new ServiceDeviceSupport(new WaspOSDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SMAQ2OSS: - deviceSupport = new ServiceDeviceSupport(new SMAQ2OSSSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case UM25: - deviceSupport = new ServiceDeviceSupport(new UM25Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case DOMYOS_T540: - deviceSupport = new ServiceDeviceSupport(new DomyosT540Support(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case FITPRO: - deviceSupport = new ServiceDeviceSupport(new FitProDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case NOTHING_EAR1: - deviceSupport = new ServiceDeviceSupport(new Ear1Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case GALAXY_BUDS: - deviceSupport = new ServiceDeviceSupport(new GalaxyBudsDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case GALAXY_BUDS_LIVE: - deviceSupport = new ServiceDeviceSupport(new GalaxyBudsDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case GALAXY_BUDS_PRO: - deviceSupport = new ServiceDeviceSupport(new GalaxyBudsDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SONY_WH_1000XM3: - deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SONY_WH_1000XM4: - deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SONY_WF_SP800N: - deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case SONY_WF_1000XM3: - deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case VESC_NRF: - case VESC_HM10: - deviceSupport = new ServiceDeviceSupport(new VescDeviceSupport(gbDevice.getType()), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - case BOSE_QC35: - deviceSupport = new ServiceDeviceSupport(new QC35BaseSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); - break; - } + DeviceSupport deviceSupport = createServiceDeviceSupport(gbDevice); if (deviceSupport != null) { deviceSupport.setContext(gbDevice, mBtAdapter, mContext); return deviceSupport; @@ -416,7 +338,7 @@ public class DeviceSupportFactory { private DeviceSupport createTCPDeviceSupport(GBDevice gbDevice) throws GBException { try { - DeviceSupport deviceSupport = new ServiceDeviceSupport(new PebbleSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + DeviceSupport deviceSupport = new ServiceDeviceSupport(new PebbleSupport(), ServiceDeviceSupport.Flags.BUSY_CHECKING); deviceSupport.setContext(gbDevice, mBtAdapter, mContext); return deviceSupport; } catch (Exception e) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index fe6fffd30..2bc337687 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.UUID; @@ -61,9 +62,14 @@ public class ServiceDeviceSupport implements DeviceSupport { private String lastNotificationKind; private final EnumSet flags; - public ServiceDeviceSupport(DeviceSupport delegate, EnumSet flags) { + public ServiceDeviceSupport(DeviceSupport delegate, Flags... flags) { this.delegate = delegate; - this.flags = flags; + this.flags = EnumSet.noneOf(Flags.class); + this.flags.addAll(Arrays.asList(flags)); + } + + public ServiceDeviceSupport(DeviceSupport delegate){ + this(delegate, Flags.BUSY_CHECKING); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java index 313b80941..76cf7b0dd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbip/AmazfitBipLiteFirmwareInfo.java @@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip; import java.util.HashMap; +import java.util.List; import java.util.Map; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -69,8 +70,8 @@ public class AmazfitBipLiteFirmwareInfo extends HuamiFirmwareInfo { if (searchString32BitAligned(bytes, "Amazfit Bip Lite")) { return HuamiFirmwareType.FIRMWARE; } - GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); - if (device != null) { + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for(GBDevice device : devices){ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); if (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_RELAX_FIRMWARE_CHECKS, false)) { if (searchString32BitAligned(bytes, "Amazfit Bip")) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java index 7ee832f89..e31eb8d60 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitbips/AmazfitBipSFirmwareInfo.java @@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips; import java.util.HashMap; +import java.util.List; import java.util.Map; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -66,10 +67,8 @@ public class AmazfitBipSFirmwareInfo extends HuamiFirmwareInfo { @Override protected HuamiFirmwareType determineFirmwareType(byte[] bytes) { - - GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); - - if (device != null) { + List devices = GBApplication.app().getDeviceManager().getSelectedDevices(); + for (GBDevice device : devices) { if (device.getFirmwareVersion().startsWith("2.")) { //For devices on firmware 2.x it is a tonleasp device and needs a header which looks like Mi Band 4 if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 628488c27..67df664ac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -242,7 +242,7 @@ class PebbleIoThread extends GBDeviceIoThread { public void run() { mIsConnected = connect(); if (!mIsConnected) { - if (GBApplication.getGBPrefs().getAutoReconnect() && !mQuit) { + if (GBApplication.getGBPrefs().getAutoReconnect(getDevice()) && !mQuit) { gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); gbDevice.sendDeviceUpdateIntent(getContext()); } @@ -406,7 +406,7 @@ class PebbleIoThread extends GBDeviceIoThread { enablePebbleKitSupport(false); - if (mQuit || !GBApplication.getGBPrefs().getAutoReconnect()) { + if (mQuit || !GBApplication.getGBPrefs().getAutoReconnect(getDevice())) { gbDevice.setState(GBDevice.State.NOT_CONNECTED); } else { gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java index 9aea24b28..e0aa4776e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java @@ -69,7 +69,7 @@ public class QC35BaseSupport extends AbstractSerialDeviceSupport { public boolean connect() { getDeviceProtocol(); getDeviceIOThread().start(); - getDevice().setBatteryThresholdPercent((short)15); + getDevice().setBatteryThresholdPercent((short)25); return true; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java index c7f026a46..cdd4b5162 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; @@ -57,6 +58,7 @@ public class QC35Protocol extends GBDeviceProtocol { if(third == 0x03){ GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo(); batteryInfo.level = data[0]; + batteryInfo.state = BatteryState.BATTERY_NORMAL; events.add(batteryInfo); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java index 3e11e7c24..fe424c4e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/AutoConnectIntervalReceiver.java @@ -58,24 +58,31 @@ public class AutoConnectIntervalReceiver extends BroadcastReceiver { return; } - GBDevice gbDevice = service.getGBDevice(); - if (gbDevice == null) { - return; - } - - if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)) { - if (gbDevice.isInitialized()) { - LOG.info("will reset connection delay, device is initialized!"); - mDelay = 4; + GBDevice[] devices = service.getGBDevices(); + if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)){ + boolean scheduleAutoConnect = false; + boolean allDevicesInitialized = true; + for(GBDevice device : devices){ + if(!device.isInitialized()){ + allDevicesInitialized = false; + }else if(device.getState() == GBDevice.State.WAITING_FOR_RECONNECT){ + scheduleAutoConnect = true; + } } - else if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + + if(allDevicesInitialized){ + LOG.info("will reset connection delay, all devices are initialized!"); + return; + } + if(scheduleAutoConnect){ scheduleReconnect(); } - } - else if (action.equals("GB_RECONNECT")) { - if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { - LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); - GBApplication.deviceService().connect(); + }else if (action.equals("GB_RECONNECT")){ + for(GBDevice device : devices){ + if(device.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + LOG.info("Will re-connect to " + device.getAddress() + "(" + device.getName() + ")"); + GBApplication.deviceService().connect(device); + } } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java index b7279f799..0dafb618f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java @@ -180,7 +180,7 @@ public class BondingUtil { public static void connectThenComplete(BondingInterface bondingInterface, GBDevice device) { toast(bondingInterface.getContext(), bondingInterface.getContext().getString(R.string.discovery_trying_to_connect_to, device.getName()), Toast.LENGTH_SHORT, GB.INFO); // Disconnect when LE Pebble so that the user can manually initiate a connection - GBApplication.deviceService().disconnect(); + GBApplication.deviceService().disconnect(device); GBApplication.deviceService().connect(device, true); bondingInterface.onBondingComplete(true); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index 53b7c1c51..e55f0ee0b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -29,6 +29,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; +import android.text.Html; +import android.text.SpannableString; import android.widget.Toast; import androidx.annotation.NonNull; @@ -44,6 +46,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBEnvironment; @@ -145,44 +148,102 @@ public class GB { return pendingIntent; } - public static Notification createNotification(GBDevice device, Context context) { - String deviceName = device.getAliasOrName(); - String text = device.getStateString(); - if (device.getBatteryLevel() != GBDevice.BATTERY_UNKNOWN) { - text += ": " + context.getString(R.string.battery) + " " + device.getBatteryLevel() + "%"; - } - - boolean connected = device.isInitialized(); + public static Notification createNotification(List devices, Context context) { NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); - builder.setContentTitle(deviceName) - .setTicker(deviceName + " - " + text) - .setContentText(text) - .setSmallIcon(connected ? device.getNotificationIconConnected() : device.getNotificationIconDisconnected()) - .setContentIntent(getContentIntent(context)) - .setShowWhen(false) - .setOngoing(true); + if(devices.size() == 0){ + builder.setContentTitle(context.getString(R.string.info_no_devices_connected)) + .setSmallIcon(R.drawable.ic_notification_disconnected) + .setContentIntent(getContentIntent(context)) + .setShowWhen(false) + .setOngoing(true); - if (!GBApplication.isRunningTwelveOrLater()) { - builder.setColor(context.getResources().getColor(R.color.accent)); - } + if (!GBApplication.isRunningTwelveOrLater()) { + builder.setColor(context.getResources().getColor(R.color.accent)); + } + }else if(devices.size() == 1) { + GBDevice device = devices.get(0); + String deviceName = device.getAliasOrName(); + String text = device.getStateString(); + if (device.getBatteryLevel() != GBDevice.BATTERY_UNKNOWN) { + text += ": " + context.getString(R.string.battery) + " " + device.getBatteryLevel() + "%"; + } - Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class); - if (connected) { - deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_DISCONNECT); - PendingIntent disconnectPendingIntent = PendingIntent.getService(context, 0, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); - builder.addAction(R.drawable.ic_notification_disconnected, context.getString(R.string.controlcenter_disconnect), disconnectPendingIntent); - if (GBApplication.isRunningLollipopOrLater() && DeviceHelper.getInstance().getCoordinator(device).supportsActivityDataFetching()) { //for some reason this fails on KK + boolean connected = device.isInitialized(); + builder.setContentTitle(deviceName) + .setTicker(deviceName + " - " + text) + .setContentText(text) + .setSmallIcon(connected ? device.getNotificationIconConnected() : device.getNotificationIconDisconnected()) + .setContentIntent(getContentIntent(context)) + .setShowWhen(false) + .setOngoing(true); + + if (!GBApplication.isRunningTwelveOrLater()) { + builder.setColor(context.getResources().getColor(R.color.accent)); + } + + Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class); + if (connected) { + deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_DISCONNECT); + PendingIntent disconnectPendingIntent = PendingIntent.getService(context, 0, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); + builder.addAction(R.drawable.ic_notification_disconnected, context.getString(R.string.controlcenter_disconnect), disconnectPendingIntent); + if (GBApplication.isRunningLollipopOrLater() && DeviceHelper.getInstance().getCoordinator(device).supportsActivityDataFetching()) { //for some reason this fails on KK + deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA); + deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); + PendingIntent fetchPendingIntent = PendingIntent.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); + builder.addAction(R.drawable.ic_refresh, context.getString(R.string.controlcenter_fetch_activity_data), fetchPendingIntent); + } + } else if (device.getState().equals(GBDevice.State.WAITING_FOR_RECONNECT) || device.getState().equals(GBDevice.State.NOT_CONNECTED)) { + deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT); + deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device); + PendingIntent reconnectPendingIntent = PendingIntent.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); + builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent); + } + }else{ + StringBuilder contentText = new StringBuilder(); + boolean isConnected = true; + boolean anyDeviceSupportesActivityDataFetching = false; + for(GBDevice device : devices){ + if(!device.isInitialized()){ + isConnected = false; + } + + anyDeviceSupportesActivityDataFetching |= DeviceHelper.getInstance().getCoordinator(device).supportsActivityDataFetching(); + + String deviceName = device.getAliasOrName(); + String text = device.getStateString(); + if (device.getBatteryLevel() != GBDevice.BATTERY_UNKNOWN) { + text += ": " + context.getString(R.string.battery) + " " + device.getBatteryLevel() + "%"; + } + contentText.append(deviceName).append(" (").append(text).append(")
"); + } + + SpannableString formated = new SpannableString( + Html.fromHtml(contentText.substring(0, contentText.length() - 4)) // cut away last
+ ); + + String title = context.getString(R.string.info_connected_count, devices.size()); + + builder.setContentTitle(title) + .setContentText(formated) + .setSmallIcon(isConnected ? R.drawable.ic_notification : R.drawable.ic_notification_disconnected) + .setContentIntent(getContentIntent(context)) + .setStyle(new NotificationCompat.BigTextStyle().bigText(formated).setBigContentTitle(title)) + .setShowWhen(false) + .setOngoing(true); + + if (!GBApplication.isRunningTwelveOrLater()) { + builder.setColor(context.getResources().getColor(R.color.accent)); + } + + if (GBApplication.isRunningLollipopOrLater() && anyDeviceSupportesActivityDataFetching) { //for some reason this fails on KK + Intent deviceCommunicationServiceIntent = new Intent(context, DeviceCommunicationService.class); deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA); deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); PendingIntent fetchPendingIntent = PendingIntent.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); builder.addAction(R.drawable.ic_refresh, context.getString(R.string.controlcenter_fetch_activity_data), fetchPendingIntent); } - } else if (device.getState().equals(GBDevice.State.WAITING_FOR_RECONNECT) || device.getState().equals(GBDevice.State.NOT_CONNECTED)) { - deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT); - deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device); - PendingIntent reconnectPendingIntent = PendingIntent.getService(context, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); - builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent); } + if (GBApplication.isRunningLollipopOrLater()) { builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); } @@ -220,8 +281,8 @@ public class GB { return builder.build(); } - public static void updateNotification(GBDevice device, Context context) { - Notification notification = createNotification(device, context); + public static void updateNotification(List devices, Context context) { + Notification notification = createNotification(devices, context); notify(NOTIFICATION_ID, notification, context); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java index ebaecc0c2..d848a8de0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java @@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.util; import android.Manifest; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.location.Criteria; import android.location.Location; @@ -33,6 +34,7 @@ import java.util.Date; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; public class GBPrefs { // Since this class must not log to slf4j, we use plain android.util.Log @@ -41,7 +43,8 @@ public class GBPrefs { public static final String PACKAGE_BLACKLIST = "package_blacklist"; public static final String PACKAGE_PEBBLEMSG_BLACKLIST = "package_pebblemsg_blacklist"; public static final String CALENDAR_BLACKLIST = "calendar_blacklist"; - public static final String AUTO_RECONNECT = "general_autocreconnect"; + public static final String DEVICE_AUTO_RECONNECT = "prefs_key_device_auto_reconnect"; + public static final String DEVICE_CONNECT_BACK = "prefs_key_device_reconnect_on_acl"; private static final String AUTO_START = "general_autostartonboot"; public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled"; public static final String AUTO_EXPORT_LOCATION = "auto_export_location"; @@ -67,8 +70,9 @@ public class GBPrefs { mPrefs = prefs; } - public boolean getAutoReconnect() { - return mPrefs.getBoolean(AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT); + public boolean getAutoReconnect(GBDevice device) { + SharedPreferences deviceSpecificPreferences = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()); + return deviceSpecificPreferences.getBoolean(DEVICE_AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT); } public boolean getAutoStart() { diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 06f9c1833..86bcf6289 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1628,4 +1628,7 @@ Über Bangle.js Gadgetbridge Fitness-App-Tracking umschalten Text als Bitmaps + + %d Geräte verbunden + Keine Geräte verbunden \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 469facb0b..20d95325d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1666,4 +1666,7 @@ Used for starting/stopping GPS track recording in external fitness app. pre-setting position to %s Light up on new notification + + no devices connected + %d devices connected diff --git a/app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml b/app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml new file mode 100644 index 000000000..4bc3cd59d --- /dev/null +++ b/app/src/main/res/xml/devicesettings_reconnect_bl_classic.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/devicesettings_reconnect_ble.xml b/app/src/main/res/xml/devicesettings_reconnect_ble.xml new file mode 100644 index 000000000..194be53e0 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_reconnect_ble.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 1b2ab2787..53eae5040 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -17,7 +17,9 @@ android:layout="@layout/preference_checkbox" android:defaultValue="false" android:key="general_autocreconnect" - android:title="@string/pref_title_general_autoreconnect" /> + android:title="@string/pref_title_general_autoreconnect" + android:enabled="false" + android:summary="setting has been moved to device specific settings"/> Date: Tue, 14 Jun 2022 20:16:05 +0200 Subject: [PATCH 07/34] remove german translation to help weblate --- app/src/main/res/values-de/strings.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 86bcf6289..06f9c1833 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1628,7 +1628,4 @@ Über Bangle.js Gadgetbridge Fitness-App-Tracking umschalten Text als Bitmaps - - %d Geräte verbunden - Keine Geräte verbunden \ No newline at end of file From 89f877cd5a4bc30b3027cf8c51595dca3e9e6a52 Mon Sep 17 00:00:00 2001 From: Artem Date: Thu, 2 Jun 2022 20:03:46 +0000 Subject: [PATCH 08/34] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1581 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fe6f64609..4e676378c 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -356,7 +356,7 @@ Це може допомогти на пристроях, на які не вдається встановити мікропрограми. Ви не спали Сигнали для резервування для майбутніх подій - Використовувати датчик пульсу для поліпшення виявлення сну + Використовувати датник пульсу для поліпшення виявлення сну Час Дата та час Одиниці вимірювання From c2f77637b47f15b27bd23b02109d2023dd25011c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Sat, 4 Jun 2022 06:37:51 +0000 Subject: [PATCH 09/34] Translated using Weblate (French) Currently translated at 98.0% (1550 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8b76cfc26..25dab7920 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1673,4 +1673,9 @@ Temps de sommeil préféré en heures
Types d\'activités physiques Choisir les types d\'activité à afficher sur l\'écran d\'activité physique Vélo en extérieur + Texte comme Images + Si un mot ne peut être affiché avec la police de la montre, en faire une image dans GadgetBridge et l\'afficher comme image dans la montre + Autoriser l\'accès à Internet + Permettre aux apps sur cet appareil d\'accéder à Internet + Permettre les intentions \ No newline at end of file From d8cae33bd3a12a6c4c5155cb433835bffa0ae423 Mon Sep 17 00:00:00 2001 From: HenRy Date: Sat, 4 Jun 2022 21:18:57 +0000 Subject: [PATCH 10/34] Translated using Weblate (German) Currently translated at 100.0% (1583 of 1583 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/ --- app/src/main/res/values-de/strings.xml | 92 ++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 06f9c1833..dea18643b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1628,4 +1628,96 @@ Über Bangle.js Gadgetbridge Fitness-App-Tracking umschalten Text als Bitmaps + Terminerinnerung + Gerät finden + Leerlaufwarnungen + Art von Workout-Aktivitäten + Aktivitätsarten wählen, die auf dem Workout-Bildschirm angezeigt werden sollen + Freistil + Sony WF-1000XM3 + Galaxy Buds Pro + Wiedergabe von Anrufen über Ohrstöpsel, wenn diese im Ohr sind + Lautstärke + Schnelle Umgebungsgeräusche + Umgebungsgeräusche aktivieren und Wiedergabe automatisch absenken, nachdem eine Stimme erkannt wurde + 145 BPM + 150 BPM + Beschriftung + Das Gerät hat keine freien Slots für Weltzeituhren (Slots insgesamt: %1$s) + Elliptisch + Wenn ein Wort nicht mit der Schriftart der Uhr gerendert werden kann, in Gadgetbridge eine Bitmap rendern und die Bitmap auf der Uhr anzeigen + Internetzugang zulassen + Apps auf diesem Gerät Internetzugriff erlauben + Absichten erlauben + Bangle.js-Watch-Apps erlauben, Android-Intents zu senden und anderen Apps auf Android (wie Tasker) erlauben, Daten mit dem com.banglejs.uart.tx-Intent an Bangle.js zu senden. + Vibrationsmuster + Vibrationsmuster für verschiedene Benachrichtigungen konfigurieren + 100 BPM + 105 BPM + 110 BPM + 112 BPM + 120 BPM + 125 BPM + 130 BPM + 135 BPM + 140 BPM + Details Weltzeituhr + Pulsalarm (experimentell) + Band vibriert, wenn der Puls über einem Schwellenwert liegt, ohne dass Sie in den letzten 10 Minuten eine offensichtliche körperliche Aktivität hatten. Diese Funktion ist experimentell und wurde nicht ausführlich getestet. + Alarmschwelle Herzfrequenz + Stressüberwachung + Stresslevel während des Ausruhens überwachen + Aktivitätsüberwachung + Herzfrequenz überwachen + Pulsüberwachung konfigurieren + Pulsüberwachung und Warnschwellen konfigurieren + Laufen im Freien + Radfahren im Freien + Geräuschkontrolle + Geräuschunterdrückung ←→ Aus + Umgebung ←→ Aus + Doppeltippen erkennen, auch wenn nicht auf dem Touchpad getippt wird + Ende nach Ruhe für: + 5 Sekunden + 10 Sekunden + 15 Sekunden + Spracherkennung + Kante doppelt antippen + %1$s benötigt Zugriff auf die Einstellungen von \'DND, um sie auf Ihrer Uhr zu berücksichtigen. +\n +\nBitte auf \'%2$s\', dann auf \'%1$s tippen und \'DND\' aktiveren und dann auf \'Zurück\' tippen, um zu %1$s zurückzukehren + Nahtloser Verbindungswechsel + Wechselt die Stöpsel automatisch zwischen den gekoppelten Geräten + Umgebungslautstärke links + Umgebungslautstärke rechts + An Umgebungsgeräusche anpassen + Eigene Stimme während des Anrufs hören + Optionen Umgebungsgeräusche + Stufe der aktiven Geräuschunterdrückung + Hoch + Niedrig + Schaltersteuerung rechts + Sprachassistent + Aktive Geräuschunterdrückung + Umgebungsgeräusche + Spotify + Lärmschutz umschalten + Ermöglicht Geräuschkontrolle bei Verwendung nur eines Ohrhörers + Geräuschkontrolle mit einem Ohrhörer + Umgebungsgeräusche + Von sanft bis klar + Ausgeglichen + Uhren für andere Zeitzonen konfigurieren + \'%1$s\' löschen + Zeitzone + Weltuhren + Möchten Sie die Weltzeituhr wirklich löschen\? + Keine freien Slots + Erhöht automatisch die Frequenz der Pulserfassung, wenn das Band körperliche Bewegung erkennt, um die Genauigkeit der Pulserfassung zu erhöhen. + Umgebungsgeräusche während des Anrufs + %1$s benötigt Zugriff auf Benachrichtigungen, um sie auf Ihrer Uhr anzeigen zu können. +\n +\nBitte auf \'%2$s\' und dann auf \'%1$s\' tippen, \'Zugriff auf Benachrichtigungen zulassen\' aktivieren und auf \'Zurück\' tippen, um zu %1$s zurückzukehren + Schaltersteuerung links + Geräuschunterdrückung ←→ Umgebung \ No newline at end of file From 88828c227e45064a996b2f7b994e6e5ccb41b842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 5 Jun 2022 15:32:25 +0000 Subject: [PATCH 11/34] Translated using Weblate (Portuguese) Currently translated at 91.8% (1452 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/pt/ --- app/src/main/res/values-pt/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d26dbd7aa..1441140c2 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1576,4 +1576,7 @@ Optimizador de cancelamento de ruído Pressão atmosférica Coloque os headphones como os utilizaria normalmente. Se as condições de utilização ou pressão atmosférica alterarem, execute o optimizador novamente. + Bangle.js Gadgetbridge + Acerca de Bangle.js Gadgetbridge + Bangle.js Gadgetbridge \ No newline at end of file From 70804f6784fb7be5a14c9d33fbb6896f41173bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Thu, 9 Jun 2022 12:58:25 +0000 Subject: [PATCH 12/34] Translated using Weblate (French) Currently translated at 97.8% (1557 of 1591 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 25dab7920..456af519a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1678,4 +1678,11 @@ Temps de sommeil préféré en heures
Autoriser l\'accès à Internet Permettre aux apps sur cet appareil d\'accéder à Internet Permettre les intentions + Application de suivi sportif + Démmare/arrête le suivi sportif sur le téléphone si une activité GPS est démarré sur le bracelet + Envoyer le GPS durant l\'exercice + Envoyer les données GPS en cours durant un exercice + GPS Gadgebridge + Envoi des données GPS au(x) appareil(s) %1$d + Suivi GPS \ No newline at end of file From dfbd1c35be02c21cc3f4acf20ed043a1c8698597 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Thu, 9 Jun 2022 20:53:23 +0000 Subject: [PATCH 13/34] Translated using Weblate (Hebrew) Currently translated at 99.8% (1589 of 1591 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index bf4735689..214accbb6 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1705,4 +1705,12 @@ אם אי אפשר לעבד תמונות עם גופן השעון, הוא יומר למפת סיביות ב־Gadgetbridge שתוצג בשעון לאפשר ליישומוני שעון של Bangle.js לשלוח Intents ל־Android ולאפשר ליישומי Android אחרים (כמו Tasker) לשלוח נתונים ל־Bangle.js באמצעות ה־Intent ‏com.banglejs.uart.tx. לאפשר Intents + שליחת GPS במהלך אימון + שליחת מיקום ה־GPS הנוכחי לצמיד במהלך אימון + GPS של Gadgetbridge + שליחת מיקום GPS ל־%1$d מכשירים + עצירת האזנת מיקום GPS + מעקב ביישומון חיטוב + התחלת/עצירת מעקב ביישומון חיטוב בטלפון כאשר מופעל אימון GPS בצמיד + מעקב GPS \ No newline at end of file From b28ecce3cae782598f670d1f441a8707afeef77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Thu, 9 Jun 2022 15:09:58 +0000 Subject: [PATCH 14/34] Translated using Weblate (Turkish) Currently translated at 100.0% (1591 of 1591 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 98c6e557f..aa09a1eee 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1722,4 +1722,18 @@ Bu aygıttaki uygulamaların internete erişmesine izin ver Bir sözcük saatin yazı tipi kullanılarak görüntülenemiyorsa, onu Gadgetbridge\'de bir bit eşleme dönüştür ve bit eşlemi saatte görüntüle İnternet Erişimine İzin Ver + Fitness uygulaması izlemesi + Bileklikte bir GPS egzersizi başlatıldığında telefonda fitness uygulaması izlemeyi başlat/durdur + Egzersiz sırasında geçerli GPS konumunu bilekliğe gönder + GPS izleme + Gadgetbridge GPS + GPS konumu %1$d aygıta gönderiliyor + GPS Konumu Dinleyiciyi Durdur + Egzersiz sırasında GPS gönder + %1$s, saatinizde görüntülemek için Bildirimlere erişmeye ihtiyaç duyuyor. +\n +\nLütfen önce \'%2$s\', sonra \'%1$s\' düğmesine dokunun ve \'Bildirim Erişimine İzin Ver\' seçeneğini etkinleştirin, ardından %1$s\'e dönmek için \'Geri\' düğmesine dokunun + %1$s, saatinizde bu ayarları yerine getirebilmek için Rahatsız Etme ayarlarına erişmeye ihtiyaç duyuyor. +\n +\nLütfen önce \'%2$s\', sonra \'%1$s\' düğmesine dokunun ve \'Rahatsız Etmeye İzin Ver\' seçeneğini etkinleştirin, ardından %1$s\'e dönmek için \'Geri\' düğmesine dokunun \ No newline at end of file From 2781aba3cba209395e45843f55c3a64e8ec2f233 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Thu, 9 Jun 2022 20:00:36 +0000 Subject: [PATCH 15/34] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1591 of 1591 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 4e676378c..0ca9f98d1 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1713,4 +1713,18 @@ Дозволити наміри Текст у вигляді растрових зображень Якщо слово не може бути відтворено шрифтом годинника, перетворювати його на растрове зображення в Gadgetbridge і показувати растрове зображення на годиннику + Відстеження фітнес-застосунку + Запускати/припиняти відстеження фітнес-застосунку на телефоні, коли GPS-тренування розпочато на браслеті + Надсилати GPS під час тренування + Надсилати поточне місцеперебування GPS на годинник під час тренування + GPS-стеження + Gadgetbridge GPS + Надсилання місцеперебування GPS на пристрій %1$d + %1$s потребує доступу до сповіщень, щоб показувати їх на годиннику. +\n +\nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити доступ до сповіщень», а потім торкніться «Назад», щоб повернутися до %1$s + %1$s потребує доступу до налаштувань «Не турбувати», щоб дотримуватися їх на годиннику. +\n +\nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити не турбувати», а потім торкніться «Назад», щоб повернутися до %1$s + Зупинка відстежувача GPS-розташування \ No newline at end of file From 7beec2554275117661ba33b2230c02e85a4ffa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Thu, 9 Jun 2022 15:51:32 +0000 Subject: [PATCH 16/34] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1591 of 1591 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c6fc36a9f..fffcf9dc1 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1711,4 +1711,18 @@ 允许意向 允许互联网访问 允许此设备上的应用访问互联网 + 健身应用追踪 + 在手环上开始 GPS 锻炼时,在手机上开始/停止健身应用跟踪 + GPS跟踪 + %1$s需要访问通知,以便在你的手表上显示这些通知。 +\n +\n请点击\'%2$s\'然后\'%1$s\'并启用\'允许通知访问\',然后点击\'返回\'以返回到%1$s + %1$s 需要访问“请勿打扰”设置才能在您的手表上使用它们。 +\n +\n请点击“%2$s”然后点击“%1$s”并启用“允许请勿打扰”,然后点击“返回”返回到%1$s + 在锻炼期间发送 GPS + 锻炼时将当前的GPS位置发送到手环上 + Gadgetbridge GPS + 将 GPS 位置发送到 %1$d 设备 + GPS 位置监听器停止 \ No newline at end of file From b51bc7eb49caf5a0246ecfe2de8ac1bb737cb54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 12 Jun 2022 05:27:35 +0000 Subject: [PATCH 17/34] Translated using Weblate (Turkish) Currently translated at 100.0% (1593 of 1593 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index aa09a1eee..b81d85cab 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1736,4 +1736,6 @@ %1$s, saatinizde bu ayarları yerine getirebilmek için Rahatsız Etme ayarlarına erişmeye ihtiyaç duyuyor. \n \nLütfen önce \'%2$s\', sonra \'%1$s\' düğmesine dokunun ve \'Rahatsız Etmeye İzin Ver\' seçeneğini etkinleştirin, ardından %1$s\'e dönmek için \'Geri\' düğmesine dokunun + Tarama yoğunluğu + Donma veya yanıt alamama yaşıyorsanız, tarama yoğunluğunu daha düşük bir seviyeye ayarlamayı deneyin. Aygıtınız bulunamıyorsa, tarama yoğunluğunu daha yüksek bir seviyeye ayarlamayı deneyin. \ No newline at end of file From de878bac7f4291afc90a05dd8dfca3fac665236a Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sat, 11 Jun 2022 22:27:57 +0000 Subject: [PATCH 18/34] Translated using Weblate (Ukrainian) Currently translated at 99.9% (1592 of 1593 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0ca9f98d1..cfbb24cc3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -529,7 +529,7 @@ Це експериментальне налаштування виключно для Pebble 2, застосуйте якщо є проблеми зі з\'єднанням Автозавантаження даних про діяльність Найменший час між отриманнями - Отримати кожні %d хвилин(и) + Отримувати що %d хвилин Російська Німецька Італійська @@ -1727,4 +1727,6 @@ \n \nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити не турбувати», а потім торкніться «Назад», щоб повернутися до %1$s
Зупинка відстежувача GPS-розташування + Якщо ви бачите заморожування або несприйняття, спробуйте встановити нижчий рівень інтенсивності сканування. Якщо пристрій не виявлено, спробуйте встановити інтенсивність сканування на вищий рівень. + Інтенсивність сканування \ No newline at end of file From c121faadec0da26f921ad0c580417985c70b0646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Sun, 12 Jun 2022 04:37:26 +0000 Subject: [PATCH 19/34] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1593 of 1593 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fffcf9dc1..2dd776787 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1725,4 +1725,6 @@ Gadgetbridge GPS 将 GPS 位置发送到 %1$d 设备 GPS 位置监听器停止 + 扫描强度 + 如果您遇到卡住或无反应的情况,尝试将扫描强度设置为较低水平。如果你的设备没有被发现,尝试将扫描强度设置到更高的水平。 \ No newline at end of file From cd57c9d0214b640944481fe2358d9523dcfeb5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Sun, 12 Jun 2022 07:35:34 +0000 Subject: [PATCH 20/34] Translated using Weblate (French) Currently translated at 97.9% (1562 of 1595 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 456af519a..aae435f21 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1685,4 +1685,11 @@ Temps de sommeil préféré en heures GPS Gadgebridge Envoi des données GPS au(x) appareil(s) %1$d Suivi GPS + %1$s a besoin d\'accéder aux notifications pour les afficher sur votre montre. +\n +\nMerci le sélectionner \'%2$s\' puis \'%1$s\' et activer \'Autoriser l\'accès aux notifications\', puis appuyer sur \'Retour\' pour revenir à %1$s + Intensité du scan + Si vous rencontrez des problèmes de blocage ou non réponse, essayer de régler l\'intensité du scan à un faible niveau. Si votre appareil n\'est pas découvert, essayer l\'intensité du scan au plus haut niveau. + Portugais (Brésil) + Portugais (Portugal) \ No newline at end of file From e90fe5cb2ecb36afd7c52fce32130f22ea94420a Mon Sep 17 00:00:00 2001 From: nautilusx Date: Sun, 12 Jun 2022 11:47:50 +0000 Subject: [PATCH 21/34] Translated using Weblate (German) Currently translated at 100.0% (1597 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/ --- app/src/main/res/values-de/strings.xml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index dea18643b..87ba4e882 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1592,7 +1592,7 @@ OpenTracks Paketname Dient zum Starten/Stoppen der GPS-Track-Aufzeichnung in der externen Fitness-App. Android-Benachrichtigungseinstellungen - setze position auf %s + Voreinstellung der Position auf %s Fitness-App Tracking Stopp Fitness-App Tracking Start Autom. Export ist aktiviert. @@ -1626,7 +1626,7 @@ Bangle.js Gadgetbridge Bangle.js läuft Über Bangle.js Gadgetbridge - Fitness-App-Tracking umschalten + Fitness-App Tracking umschalten Text als Bitmaps Terminerinnerung Gerät finden @@ -1711,7 +1711,7 @@ \'%1$s\' löschen Zeitzone Weltuhren - Möchten Sie die Weltzeituhr wirklich löschen\? + Möchtest du die Weltzeituhr wirklich löschen\? Keine freien Slots Erhöht automatisch die Frequenz der Pulserfassung, wenn das Band körperliche Bewegung erkennt, um die Genauigkeit der Pulserfassung zu erhöhen. Umgebungsgeräusche während des Anrufs @@ -1720,4 +1720,18 @@ \nBitte auf \'%2$s\' und dann auf \'%1$s\' tippen, \'Zugriff auf Benachrichtigungen zulassen\' aktivieren und auf \'Zurück\' tippen, um zu %1$s zurückzukehren Schaltersteuerung links Geräuschunterdrückung ←→ Umgebung + Startet/stoppt das Fitness-App-Tracking auf dem Telefon, wenn ein GPS-Training auf dem Band gestartet wird + GPS während des Trainings senden + Senden der aktuellen GPS-Position an das Band während des Trainings + Fitness-App-Tracking + Wenn dein Gerät einfriert oder nicht reagiert, versuche, die Scanning-Intensität auf eine niedrigere Stufe einzustellen. Wenn dein Gerät nicht erkannt wird, stelle die Scanning-Intensität auf eine höhere Stufe. + Scanning-Intensität + Portugiesisch (Brasilien) + Schneller Alarm + Alarm vom Widget + GPS-Standort Stopp + GPS-Ortung + Gadgetbridge GPS + Portugiesisch (Portugal) + Senden des GPS-Standorts an %1$d Geräte \ No newline at end of file From 6e907cdd1fbd85d79ebf56808f0e86101728620b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Fern=C3=A1ndez=20D=C3=ADaz?= Date: Sun, 12 Jun 2022 22:13:44 +0000 Subject: [PATCH 22/34] Translated using Weblate (Spanish) Currently translated at 93.8% (1499 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/ --- app/src/main/res/values-es/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7cddb3f44..caa3f12a3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1624,4 +1624,9 @@ Configurar los relojes para otras zonas horarias Eliminar \'%1$s\' ¿Está seguro de que quiere eliminar el reloj mundial\? + Texto como mapa de bits + Si una palabra no puede ser reproducida con la fuente del reloj, renderícela en un mapa de bits en Gadgetbridge y muestre el mapa de bits en el reloj + Permitir acceso a Internet + Permitir que las aplicaciones de este dispositivo accedan a Internet + Permitir intenciones \ No newline at end of file From dd9f7e2d78f62c825530d417a125f9fd97143946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Sun, 12 Jun 2022 07:57:31 +0000 Subject: [PATCH 23/34] Translated using Weblate (French) Currently translated at 100.0% (1597 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index aae435f21..6846f3024 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1692,4 +1692,41 @@ Temps de sommeil préféré en heures Si vous rencontrez des problèmes de blocage ou non réponse, essayer de régler l\'intensité du scan à un faible niveau. Si votre appareil n\'est pas découvert, essayer l\'intensité du scan au plus haut niveau. Portugais (Brésil) Portugais (Portugal) + Bas + Double appui sur le bord + Arrêt après pause pendant : + 5 secondes + Mettre le contrôle à gauche + Mettre le contrôle à droite + Son Ambiant + Spotify + Changer le contrôle du bruit + Contrôle du bruit avec un écouteur + Niveau actif de l\'annulation de bruit + Haut + Assistant vocal + Annulation de bruit active + Son Ambiant + Détecter un double appui même si hors zone tactile + 10 secondes + Arrêt du gestionnaire GPS + %1$s doit accéder aux réglages Ne Pas Déranger afin de les respecter sur votre montre. +\n +\nMerci de sélectionner \'%2$s\' puis \'%1$s\' et activer \'Autoriser Ne Pas Déranger\', puis sélectionner \'Retour\' pour revenir à %1$s + Activer le son ambiant et réduction de bruit automatique lors de la détection de voix + Alarme rapide + Alarme d\'un widget + Permettre à Bangle.js d\'envoyer des notifications Android, et permettre aux autres apps Android (comme Tasker) d\'envoyer des données à Bangle.js avec le dispositif com.banglejs.uart.tx . + Volume + Permettre le contrôle du bruit en utilisant un seul écouteur + Tonalité du son ambiant + De doux à Clair + Balance + Ambiant <-> Off + Contrôle du bruit + Détection de la voix + Annulation de bruit <-> Ambiant + Annulation de bruit <-> Off + 15 secondes + Basculer d\'app de suivi sportif \ No newline at end of file From 1ce1b22ea2cf85416250465f48057899a0246619 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sun, 12 Jun 2022 13:13:41 +0000 Subject: [PATCH 24/34] Translated using Weblate (Hebrew) Currently translated at 99.8% (1595 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 214accbb6..4c336608f 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1713,4 +1713,10 @@ מעקב ביישומון חיטוב התחלת/עצירת מעקב ביישומון חיטוב בטלפון כאשר מופעל אימון GPS בצמיד מעקב GPS + עוצמת סריקה + אם חווית קפאון או חוסר תגובתיות, כדאי לנסות לשנות את עוצמת הסריקה לרמה נמוכה. אם המכשיר שלך לא מתגלה, כדאי לנסות לשנות את עוצמת הסריקה לרמה גבוהה. + פורטוגלית של פורטוגל + שעון מעורר מווידג׳ט + פורטוגלית ברזילאית + שעון מעורר מהיר \ No newline at end of file From 5579ee4ef054fc9a06da131857b4eed87e49835d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 12 Jun 2022 08:07:42 +0000 Subject: [PATCH 25/34] Translated using Weblate (Turkish) Currently translated at 100.0% (1597 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index b81d85cab..1eb166c7e 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1738,4 +1738,8 @@ \nLütfen önce \'%2$s\', sonra \'%1$s\' düğmesine dokunun ve \'Rahatsız Etmeye İzin Ver\' seçeneğini etkinleştirin, ardından %1$s\'e dönmek için \'Geri\' düğmesine dokunun Tarama yoğunluğu Donma veya yanıt alamama yaşıyorsanız, tarama yoğunluğunu daha düşük bir seviyeye ayarlamayı deneyin. Aygıtınız bulunamıyorsa, tarama yoğunluğunu daha yüksek bir seviyeye ayarlamayı deneyin. + Hızlı alarm + Widget alarmı + Portekizce (Portekiz) + Portekizce (Brezilya) \ No newline at end of file From adda0a646fbdfddd0eef3f54e5bb79ba3443078d Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sun, 12 Jun 2022 12:13:12 +0000 Subject: [PATCH 26/34] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1597 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index cfbb24cc3..bd38689da 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1729,4 +1729,8 @@ Зупинка відстежувача GPS-розташування Якщо ви бачите заморожування або несприйняття, спробуйте встановити нижчий рівень інтенсивності сканування. Якщо пристрій не виявлено, спробуйте встановити інтенсивність сканування на вищий рівень. Інтенсивність сканування + Португальська (Бразилія) + Швидкий будильник + Португальська (Португалія) + Будильник з віджета \ No newline at end of file From b6d1b308485f4ac056f3043749aa3b59f460a8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Sun, 12 Jun 2022 12:33:04 +0000 Subject: [PATCH 27/34] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1597 of 1597 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2dd776787..32cc228eb 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1727,4 +1727,8 @@ GPS 位置监听器停止 扫描强度 如果您遇到卡住或无反应的情况,尝试将扫描强度设置为较低水平。如果你的设备没有被发现,尝试将扫描强度设置到更高的水平。 + 葡萄牙语(巴西) + 快速闹钟 + 葡萄牙语 (葡萄牙) + 从小工具发出的闹钟 \ No newline at end of file From 4347128da88c9bd29bead7671b235b835f7cfd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Mon, 13 Jun 2022 08:16:59 +0000 Subject: [PATCH 28/34] Translated using Weblate (French) Currently translated at 100.0% (1600 of 1600 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6846f3024..355df19e0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1601,7 +1601,9 @@ Temps de sommeil préféré en heures Bangle.js Gadgetbridge A propos de Bangle.js Gadgetbridge Bangle.js Gadgetbridge - Application complémentaire Android pour Bangle.js élaborée sur la base du projet Gadgetbridge, avec des fonctionnalités Internet supplémentaires. + Application complémentaire Android pour Bangle.js élaborée sur la base du projet Gadgetbridge, avec des fonctionnalités Internet supplémentaires. +\n +\nEn raison des règles du Google Play Store, nous ne pouvons pas mettre de lien de don directement dans l\'application, mais si vous appréciez cette application, pensez à faire un don via la page web de Gadgetbridge ci-dessous. Bangle.js pour Gadgetbridge Bangle.js pour Gadgetbridge A propos de Gatgetbridge Nightly @@ -1612,7 +1614,9 @@ Temps de sommeil préféré en heures Gadgetbridge Nightly Sans Pebble GB Nightly en fonctionnement Bangle.js en fonctionnement - Application complémentaire Android pour Bangle.js élaborée sur la base du projet Gadgetbridge, avec des fonctionnalités Internet supplémentaires. + Application complémentaire Android pour Bangle.js élaborée sur la base du projet Gadgetbridge, avec des fonctionnalités Internet supplémentaires. +\n +\nEn raison des règles du Google Play Store, nous ne pouvons pas mettre de lien de don directement dans l\'application, mais si vous appréciez cette application, pensez à faire un don via la page web de Gadgetbridge ci-dessous. Bangle.js en fonctionnement Gadgetbridge (version Nightly) Gadgetbrigde version Nightly @@ -1685,7 +1689,7 @@ Temps de sommeil préféré en heures GPS Gadgebridge Envoi des données GPS au(x) appareil(s) %1$d Suivi GPS - %1$s a besoin d\'accéder aux notifications pour les afficher sur votre montre. + %1$s a besoin d\'accéder aux notifications pour les afficher sur votre montre quand l\'écran de votre téléphone est éteint. \n \nMerci le sélectionner \'%2$s\' puis \'%1$s\' et activer \'Autoriser l\'accès aux notifications\', puis appuyer sur \'Retour\' pour revenir à %1$s Intensité du scan @@ -1710,7 +1714,7 @@ Temps de sommeil préféré en heures Détecter un double appui même si hors zone tactile 10 secondes Arrêt du gestionnaire GPS - %1$s doit accéder aux réglages Ne Pas Déranger afin de les respecter sur votre montre. + %1$s doit accéder aux réglages Ne Pas Déranger afin de les respecter sur votre montre quand l\'écran de votre téléphone est éteint. \n \nMerci de sélectionner \'%2$s\' puis \'%1$s\' et activer \'Autoriser Ne Pas Déranger\', puis sélectionner \'Retour\' pour revenir à %1$s Activer le son ambiant et réduction de bruit automatique lors de la détection de voix @@ -1729,4 +1733,9 @@ Temps de sommeil préféré en heures Annulation de bruit <-> Off 15 secondes Basculer d\'app de suivi sportif + URL de chargement de l\'app + %1$s a besoin d\'accès à votre emplacement en arrière-plan pour lui permettre de rester connecté à votre montre même quand votre écran est éteint. +\n +\nAppuyez-ici \'%2$s\' pour accepter. + Si vous souhaitez utiliser un chargeur d\'app spécifique mettre votre URL https://…/android.html ici. Sinon laissez blanc pour https://banglejs.com/apps \ No newline at end of file From 9830d628d131691529f7963727b717ea0a423a30 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Mon, 13 Jun 2022 09:54:50 +0000 Subject: [PATCH 29/34] Translated using Weblate (Hebrew) Currently translated at 99.8% (1597 of 1600 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 4c336608f..b0f2474f3 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1602,8 +1602,12 @@ על Bangle.js Gadgetbridge Bangle.js Gadgetbridge על Bangle.js Gadgetbridge - יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט. - יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט. + יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט. +\n +\nעקב מדיניות של חנות Google Play, אסור לנו להציב קישור תרומה ביישומון עצמו אך אם היישומון נושא חן בעיניך אפשר לתרום דרך עמוד הבית של Gadgetbridge להלן. + יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט. +\n +\nעקב מדיניות של חנות Google Play, אסור לנו להציב קישור תרומה ביישומון עצמו אך אם היישומון נושא חן בעיניך אפשר לתרום דרך עמוד הבית של Gadgetbridge להלן. Gadgetbridge (גרסה ניסיונית) Bangle.js Gadgetbridge Bangle.js פעיל @@ -1719,4 +1723,6 @@ שעון מעורר מווידג׳ט פורטוגלית ברזילאית שעון מעורר מהיר + כתובת טוען יישומון + אם ברצונך להשתמש בטוען יישומון אחר נא לציין את הכתובת https://…/android.html, אם לא אפשר להשאיר ריק כדי להשתמש ב־https://banglejs.com/apps \ No newline at end of file From d17960e8b9625fc52aa70b7a97bce8286218757a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 13 Jun 2022 15:16:57 +0000 Subject: [PATCH 30/34] Translated using Weblate (Turkish) Currently translated at 100.0% (1600 of 1600 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1eb166c7e..4e3cb1a94 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1623,12 +1623,16 @@ Gecelik PebbleYok GB çalışıyor Bangle.js Gadgetbridge Bangle.js Gadgetbridge Hakkında - İnternet erişimi eklenen Gadgetbridge projesinin üzerine inşa edilmiş Bangle.js için Android yardımcı uygulaması. + Bangle.js için Gadgetbridge projesinin üzerine inşa edilmiş, internet erişimi eklenmiş Android yardımcı uygulaması. +\n +\nGoogle Play Store politikaları nedeniyle, uygulamanın kendisinde bir bağış bağlantısına izin verilmiyor, ancak bu uygulamayı beğendiyseniz lütfen aşağıdaki Gadgetbridge ana sayfası üzerinden bağış yapmayı düşünün. Gadgetbridge Gecelik Gadgetbridge (Gecelik, Pebble sağlayıcısı yok) Bangle.js Gadgetbridge Satıcıların kapalı kaynaklı Android aygıt uygulamalarının yerine bulut gerektirmeyen copyleft özgür alternatif. Gadgetbridge gecelik sürümleri. Bu sürümde, çakışmaları önlemek için Pebble sağlayıcısının adı değiştirilmiştir, bu nedenle Pebble ile ilgili bazı bütünleşmeler çalışmayacaktır, ancak mevcut bir Gadgetbridge kurulumunun yanına kurulabilir. - İnternet erişimi eklenen Gadgetbridge projesinin üzerine inşa edilmiş Bangle.js için Android yardımcı uygulaması. + Bangle.js için Gadgetbridge projesinin üzerine inşa edilmiş, internet erişimi eklenmiş Android yardımcı uygulaması. +\n +\nGoogle Play Store politikaları nedeniyle, uygulamanın kendisinde bir bağış bağlantısına izin verilmiyor, ancak bu uygulamayı beğendiyseniz lütfen aşağıdaki Gadgetbridge ana sayfası üzerinden bağış yapmayı düşünün. Bangle.js çalışıyor Gadgetbridge (Gecelik) Hassasiyet @@ -1730,10 +1734,10 @@ GPS konumu %1$d aygıta gönderiliyor GPS Konumu Dinleyiciyi Durdur Egzersiz sırasında GPS gönder - %1$s, saatinizde görüntülemek için Bildirimlere erişmeye ihtiyaç duyuyor. + %1$s, telefonunuzun ekranı kapalıyken saatinizde görüntülemek için Bildirimlere erişmeye ihtiyaç duyuyor. \n \nLütfen önce \'%2$s\', sonra \'%1$s\' düğmesine dokunun ve \'Bildirim Erişimine İzin Ver\' seçeneğini etkinleştirin, ardından %1$s\'e dönmek için \'Geri\' düğmesine dokunun - %1$s, saatinizde bu ayarları yerine getirebilmek için Rahatsız Etme ayarlarına erişmeye ihtiyaç duyuyor. + %1$s, telefonunuzun ekranı kapalıyken saatinizde bu ayarları yerine getirebilmek için Rahatsız Etme ayarlarına erişmeye ihtiyaç duyuyor. \n \nLütfen önce \'%2$s\', sonra \'%1$s\' düğmesine dokunun ve \'Rahatsız Etmeye İzin Ver\' seçeneğini etkinleştirin, ardından %1$s\'e dönmek için \'Geri\' düğmesine dokunun Tarama yoğunluğu @@ -1742,4 +1746,9 @@ Widget alarmı Portekizce (Portekiz) Portekizce (Brezilya) + Uygulama yükleyici URL\'si + Özel bir uygulama yükleyicisi istiyorsanız https://.../android.html URL\'nizi buraya koyun. Aksi takdirde https://banglejs.com/apps için boş bırakın + %1$s ekranınız kapalıyken bile saatinize bağlı kalmasını sağlamak için arka planda konumunuza erişmeye ihtiyaç duyuyor. +\n +\nKabul etmek için lütfen \'%2$s\' düğmesine dokunun. \ No newline at end of file From 40e913895097d0c7e549d4558c7dcbf324faa1aa Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Mon, 13 Jun 2022 18:51:03 +0000 Subject: [PATCH 31/34] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1600 of 1600 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index bd38689da..6e26fa8cf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1603,7 +1603,9 @@ Засвітлювати за нового сповіщення Е-пошта Bangle.js Gadgetbridge - Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету. + Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету. +\n +\nЗгідно з правилами Google Play Маркету, нам не дозволено вказувати посилання на допомогу в самому застосунку, але якщо він вам до вподоби, ви можете посприяти розвитку проєкту через домашню сторінку Gadgetbridge нижче. Bangle.js працює Bangle.js Gadgetbridge Bangle.js Gadgetbridge @@ -1618,7 +1620,9 @@ Про Bangle.js Gadgetbridge Про Bangle.js Gadgetbridge Bangle.js Gadgetbridge - Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету. + Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету. +\n +\nЗгідно з правилами Google Play Маркету, нам не дозволено вказувати посилання на допомогу в самому застосунку, але якщо він вам до вподоби, ви можете посприяти розвитку проєкту через домашню сторінку Gadgetbridge нижче. Gadgetbridge Nightly Безхмарна вільна з копілефт ліцензією заміна власницьких застосунків для пристроїв Android. Випуски Gadgetbridge Nightly. Його не можна встановити, якщо у вас уже встановлено застосунок Gadgetbridge або Pebble через конфлікт у постачальника Pebble. Безхмарна вільна з копілефт ліцензією заміна власницьких застосунків для пристроїв Android. Випуски Gadgetbridge Nightly. У цій версії постачальник Pebble перейменований для запобігання конфліктів, тому деякі інтеграції, пов\'язані з Pebble, не працюватимуть, але його можна встановити разом із наявним установленим Gadgetbridge. @@ -1720,10 +1724,10 @@ GPS-стеження Gadgetbridge GPS Надсилання місцеперебування GPS на пристрій %1$d - %1$s потребує доступу до сповіщень, щоб показувати їх на годиннику. + %1$s потребує доступу до сповіщень, щоб показувати їх на годиннику коли екран телефону вимкнено. \n \nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити доступ до сповіщень», а потім торкніться «Назад», щоб повернутися до %1$s - %1$s потребує доступу до налаштувань «Не турбувати», щоб дотримуватися їх на годиннику. + %1$s потребує доступу до налаштувань «Не турбувати», щоб дотримуватися їх на годиннику коли екран телефону вимкнено. \n \nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити не турбувати», а потім торкніться «Назад», щоб повернутися до %1$s Зупинка відстежувача GPS-розташування @@ -1733,4 +1737,9 @@ Швидкий будильник Португальська (Португалія) Будильник з віджета + URL-адреса завантажувача застосунку + Якщо ви хочете, щоб спеціальний завантажувач застосунків помістив вашу https://…/android.html URL-адресу тут. В іншому випадку залиште порожнім для https://banglejs.com/apps + %1$s потрібен доступ до вашого місцеперебування у фоновому режимі, щоб дозволити йому залишатися на зв\'язку з годинником, навіть коли екран вимкнено. +\n +\nТоркніться «%2$s», щоб погодитися. \ No newline at end of file From def03d7d62602d89be91df416abc6dc2e78804f7 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 14 Jun 2022 14:18:06 +0000 Subject: [PATCH 32/34] Translated using Weblate (Hebrew) Currently translated at 100.0% (1600 of 1600 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b0f2474f3..44ea83cd1 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1725,4 +1725,13 @@ שעון מעורר מהיר כתובת טוען יישומון אם ברצונך להשתמש בטוען יישומון אחר נא לציין את הכתובת https://…/android.html, אם לא אפשר להשאיר ריק כדי להשתמש ב־https://banglejs.com/apps + ל־%1$s דרושה גישה להתראות כדי להציג אותן בשעון שלך כאשר מסך הטלפון שלך כבוי. +\n +\nנא לגעת ב־‚%2$s’ ואז ‚%1$s’ ולהפעיל את ‚לאפשר גישה להתראות’ ואז לגעת ב‚חזרה’ כדי לחזור אל %1$s + ל־%1$s דרושה גישה למיקום שלך ברקע כדי לאפשר לו להישאר מחובר לשעון שלך גם כאשר המסך שלך כבוי. +\n +\nנא לגעת ב‚%2$s’ כדי להסכים. + ל־%1$s דרושה גישה להגדרות לא להפריע כדי לכבד אותן בשעון שלך כאשר מסך הטלפון שלך כבוי. +\n +\nנא לגעת ב־‚%2$s’ ואז ‚%1$s’ ולהפעיל את ‚לאפשר לא להפריע’ ואז לגעת ב‚חזרה’ כדי לחזור אל %1$s \ No newline at end of file From b29900f671d7d880526514911b3f74e05451974a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Tue, 14 Jun 2022 19:12:26 +0100 Subject: [PATCH 33/34] Fix build after multi-device-support rebase --- .../gadgetbridge/service/DeviceCommunicationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8e251f03c..546f88f71 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -841,7 +841,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere break; case ACTION_SET_GPS_LOCATION: final Location location = intent.getParcelableExtra(EXTRA_GPS_LOCATION); - mDeviceSupport.onSetGpsLocation(location); + deviceSupport.onSetGpsLocation(location); break; } } From c2d98b5a24d069f1f1299413bf38d969a7943858 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 15 Jun 2022 10:20:53 +0100 Subject: [PATCH 34/34] 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;