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)* 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..546f88f71 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,92 +756,92 @@ 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: final Location location = intent.getParcelableExtra(EXTRA_GPS_LOCATION); - mDeviceSupport.onSetGpsLocation(location); + deviceSupport.onSetGpsLocation(location); break; } } @@ -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..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,6 +1626,112 @@ 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 + 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ö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 + %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 + 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 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 diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8b76cfc26..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 @@ -1673,4 +1677,65 @@ 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 + 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 + %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 + 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 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 + 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 + 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 diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index bf4735689..44ea83cd1 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 פעיל @@ -1705,4 +1709,29 @@ אם אי אפשר לעבד תמונות עם גופן השעון, הוא יומר למפת סיביות ב־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 + עוצמת סריקה + אם חווית קפאון או חוסר תגובתיות, כדאי לנסות לשנות את עוצמת הסריקה לרמה נמוכה. אם המכשיר שלך לא מתגלה, כדאי לנסות לשנות את עוצמת הסריקה לרמה גבוהה. + פורטוגלית של פורטוגל + שעון מעורר מווידג׳ט + פורטוגלית ברזילאית + שעון מעורר מהיר + כתובת טוען יישומון + אם ברצונך להשתמש בטוען יישומון אחר נא לציין את הכתובת 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 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 diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 98c6e557f..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 @@ -1722,4 +1726,29 @@ 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, 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, 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 + 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) + 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 diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fe6f64609..6e26fa8cf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -356,7 +356,7 @@ Це може допомогти на пристроях, на які не вдається встановити мікропрограми. Ви не спали Сигнали для резервування для майбутніх подій - Використовувати датчик пульсу для поліпшення виявлення сну + Використовувати датник пульсу для поліпшення виявлення сну Час Дата та час Одиниці вимірювання @@ -529,7 +529,7 @@ Це експериментальне налаштування виключно для Pebble 2, застосуйте якщо є проблеми зі з\'єднанням Автозавантаження даних про діяльність Найменший час між отриманнями - Отримати кожні %d хвилин(и) + Отримувати що %d хвилин Російська Німецька Італійська @@ -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. @@ -1713,4 +1717,29 @@ Дозволити наміри Текст у вигляді растрових зображень Якщо слово не може бути відтворено шрифтом годинника, перетворювати його на растрове зображення в 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-розташування + Якщо ви бачите заморожування або несприйняття, спробуйте встановити нижчий рівень інтенсивності сканування. Якщо пристрій не виявлено, спробуйте встановити інтенсивність сканування на вищий рівень. + Інтенсивність сканування + Португальська (Бразилія) + Швидкий будильник + Португальська (Португалія) + Будильник з віджета + URL-адреса завантажувача застосунку + Якщо ви хочете, щоб спеціальний завантажувач застосунків помістив вашу https://…/android.html URL-адресу тут. В іншому випадку залиште порожнім для https://banglejs.com/apps + %1$s потрібен доступ до вашого місцеперебування у фоновому режимі, щоб дозволити йому залишатися на зв\'язку з годинником, навіть коли екран вимкнено. +\n +\nТоркніться «%2$s», щоб погодитися. \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c6fc36a9f..32cc228eb 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1711,4 +1711,24 @@ 允许意向 允许互联网访问 允许此设备上的应用访问互联网 + 健身应用追踪 + 在手环上开始 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 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"/>