1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-10 08:16:48 +02:00

Merge remote-tracking branch 'freeyourgadget/master'

This commit is contained in:
Gordon Williams 2022-06-15 11:49:08 +01:00
commit abf6785fb5
51 changed files with 1336 additions and 578 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)*

View File

@ -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<GBDevice> 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<GBDevice> devices = gbApp.getDeviceManager().getSelectedDevices();
for(GBDevice device : devices){
GBApplication.deleteDeviceSpecificSharedPrefs(device.getAddress());
}
}

View File

@ -214,6 +214,19 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
} else {
setInfoText(getString(R.string.installer_activity_wait_while_determining_status));
List<GBDevice> 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<DeviceCoordinator> allCoordinators = DeviceHelper.getInstance().getAllCoordinators();
List<DeviceCoordinator> 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<GBDevice> 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)) {

View File

@ -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);
}

View File

@ -154,7 +154,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
public boolean onLongClick(View v) {
if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect();
GBApplication.deviceService().disconnect(device);
}
return true;
}
@ -261,7 +261,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
);
//device specific settings
holder.deviceSpecificSettingsView.setVisibility(coordinator.getSupportedDeviceSpecificSettings(device) != null ? View.VISIBLE : View.GONE);
holder.deviceSpecificSettingsView.setVisibility(coordinator.getSupportedDeviceSpecificConnectionSettings() != null ? View.VISIBLE : View.GONE);
holder.deviceSpecificSettingsView.setOnClickListener(new View.OnClickListener()
{

View File

@ -0,0 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
public abstract class AbstractBLClassicDeviceCoordinator extends AbstractDeviceCoordinator {
@Override
public ConnectionType getConnectionType() {
return ConnectionType.BL_CLASSIC;
}
}

View File

@ -0,0 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
public abstract class AbstractBLEDeviceCoordinator extends AbstractDeviceCoordinator{
@Override
public ConnectionType getConnectionType() {
return ConnectionType.BLE;
}
}

View File

@ -26,6 +26,7 @@ import android.bluetooth.le.ScanFilter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,6 +38,7 @@ import java.util.Collections;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
@ -61,6 +63,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return getSupportedType(candidate).isSupported();
}
@Override
public ConnectionType getConnectionType() {
return ConnectionType.BOTH;
}
@Override
public boolean supports(GBDevice device) {
return getDeviceType().equals(device.getType());
@ -86,7 +93,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public void deleteDevice(final GBDevice gbDevice) throws GBException {
LOG.info("will try to delete device: " + gbDevice.getName());
if (gbDevice.isConnected() || gbDevice.isConnecting()) {
GBApplication.deviceService().disconnect();
GBApplication.deviceService().disconnect(gbDevice);
}
Prefs prefs = getPrefs();
@ -258,9 +265,24 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false;
}
@Override
public int[] getSupportedDeviceSpecificConnectionSettings() {
int[] settings = new int[0];
ConnectionType connectionType = getConnectionType();
if(connectionType.usesBluetoothLE()){
settings = ArrayUtils.insert(0, settings, R.xml.devicesettings_reconnect_ble);
}
if(connectionType.usesBluetoothClassic()){
settings = ArrayUtils.insert(0, settings, R.xml.devicesettings_reconnect_bl_classic);
}
return settings;
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return null;
return new int[0];
}
@Override

View File

@ -77,6 +77,27 @@ public interface DeviceCoordinator {
*/
int BONDING_STYLE_LAZY = 4;
enum ConnectionType{
BLE(false, true),
BL_CLASSIC(true, false),
BOTH(true, true)
;
boolean usesBluetoothClassic, usesBluetoothLE;
ConnectionType(boolean usesBluetoothClassic, boolean usesBluetoothLE) {
this.usesBluetoothClassic = usesBluetoothClassic;
this.usesBluetoothLE = usesBluetoothLE;
}
public boolean usesBluetoothLE(){
return usesBluetoothLE;
}
public boolean usesBluetoothClassic(){
return usesBluetoothClassic;
}
}
/**
* Checks whether this coordinator handles the given candidate.
* Returns the supported device type for the given candidate or
@ -88,6 +109,13 @@ public interface DeviceCoordinator {
@NonNull
DeviceType getSupportedType(GBDeviceCandidate candidate);
/**
* Returns the type of connection, Classic of BLE
*
* @return ConnectionType
*/
ConnectionType getConnectionType();
/**
* Checks whether this coordinator handles the given candidate.
*
@ -363,6 +391,13 @@ public interface DeviceCoordinator {
*/
boolean supportsUnicodeEmojis();
/**
* Returns device specific settings related to connection
*
* @return int[]
*/
int[] getSupportedDeviceSpecificConnectionSettings();
/**
* Indicates which device specific settings the device supports (not per device type or family, but unique per device).
*/

View File

@ -67,7 +67,7 @@ public class DeviceManager {
* This allows direct access to the list from ListAdapters.
*/
private final List<GBDevice> deviceList = new ArrayList<>();
private GBDevice selectedDevice = null;
private List<GBDevice> 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<GBDevice> getSelectedDevices() {
return selectedDevices;
}
private void notifyDevicesChanged() {

View File

@ -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<GBDevice> 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<GBDevice> 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;
}

View File

@ -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 {

View File

@ -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<GBDevice> 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<String> {

View File

@ -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<GBDevice> 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;

View File

@ -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<GBDevice> 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() {

View File

@ -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<GBDevice> 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

View File

@ -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<GBDevice> 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<GBDevice> 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<GBDevice> 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<GBDevice> 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;
}
}

View File

@ -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 {

View File

@ -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";

View File

@ -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<GBDevice> knownDevices = GBApplication.app().getDeviceManager().getDevices();
for(GBDevice device : knownDevices){
if(device.getAddress().equals(deviceAddress)){
return device;
}
}
return null;
}
}

View File

@ -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();
}
}
}

View File

@ -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<GBDevice> 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");

View File

@ -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);

View File

@ -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();

View File

@ -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<DeviceStruct> 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<GBDevice> 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<NotificationSpec.Action>) 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<? extends Alarm> alarms = (ArrayList<? extends Alarm>) intent.getSerializableExtra(EXTRA_ALARMS);
mDeviceSupport.onSetAlarms(alarms);
deviceSupport.onSetAlarms(alarms);
break;
case ACTION_SET_REMINDERS:
ArrayList<? extends Reminder> reminders = (ArrayList<? extends Reminder>) intent.getSerializableExtra(EXTRA_REMINDERS);
mDeviceSupport.onSetReminders(reminders);
deviceSupport.onSetReminders(reminders);
break;
case ACTION_SET_WORLD_CLOCKS:
ArrayList<? extends WorldClock> clocks = (ArrayList<? extends WorldClock>) 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;
}
}

View File

@ -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) {

View File

@ -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> flags;
public ServiceDeviceSupport(DeviceSupport delegate, EnumSet<Flags> 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

View File

@ -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<GBDevice> 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")) {

View File

@ -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<GBDevice> 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)) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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<GBDevice> 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(")<br>");
}
SpannableString formated = new SpannableString(
Html.fromHtml(contentText.substring(0, contentText.length() - 4)) // cut away last <br>
);
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<GBDevice> devices, Context context) {
Notification notification = createNotification(devices, context);
notify(NOTIFICATION_ID, notification, context);
}

View File

@ -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() {

View File

@ -1592,7 +1592,7 @@
<string name="pref_title_opentracks_packagename">OpenTracks Paketname</string>
<string name="pref_summary_opentracks_packagename">Dient zum Starten/Stoppen der GPS-Track-Aufzeichnung in der externen Fitness-App.</string>
<string name="pref_title_notifications_generic_settings">Android-Benachrichtigungseinstellungen</string>
<string name="watchface_dialog_pre_setting_position">setze position auf %s</string>
<string name="watchface_dialog_pre_setting_position">Voreinstellung der Position auf %s</string>
<string name="pref_device_action_fitness_app_control_stop">Fitness-App Tracking Stopp</string>
<string name="pref_device_action_fitness_app_control_start">Fitness-App Tracking Start</string>
<string name="activity_db_management_autoexport_enabled_yes">Autom. Export ist aktiviert.</string>
@ -1626,6 +1626,112 @@
<string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
<string name="gadgetbridge_running_banglejs_nopebble">Bangle.js läuft</string>
<string name="about_activity_title_banglejs_nopebble">Über Bangle.js Gadgetbridge</string>
<string name="pref_device_action_fitness_app_control_toggle">Fitness-App-Tracking umschalten</string>
<string name="pref_device_action_fitness_app_control_toggle">Fitness-App Tracking umschalten</string>
<string name="pref_title_banglejs_text_bitmap">Text als Bitmaps</string>
<string name="pref_screen_notification_profile_event_reminder">Terminerinnerung</string>
<string name="pref_screen_notification_profile_find_device">Gerät finden</string>
<string name="pref_screen_notification_idle_alerts">Leerlaufwarnungen</string>
<string name="mi5_prefs_workout_activity_types">Art von Workout-Aktivitäten</string>
<string name="mi5_prefs_workout_activity_types_summary">Aktivitätsarten wählen, die auf dem Workout-Bildschirm angezeigt werden sollen</string>
<string name="activity_type_freestyle">Freistil</string>
<string name="devicetype_sony_wf_1000xm3">Sony WF-1000XM3</string>
<string name="devicetype_galaxybuds_pro">Galaxy Buds Pro</string>
<string name="prefs_in_ear_detection_summary">Wiedergabe von Anrufen über Ohrstöpsel, wenn diese im Ohr sind</string>
<string name="pref_title_touch_volume">Lautstärke</string>
<string name="pref_title_touch_quick_ambient">Schnelle Umgebungsgeräusche</string>
<string name="prefs_voice_detect_summary">Umgebungsgeräusche aktivieren und Wiedergabe automatisch absenken, nachdem eine Stimme erkannt wurde</string>
<string name="heartrate_bpm_145">145 BPM</string>
<string name="heartrate_bpm_150">150 BPM</string>
<string name="world_clock_label">Beschriftung</string>
<string name="world_clock_no_free_slots_description">Das Gerät hat keine freien Slots für Weltzeituhren (Slots insgesamt: %1$s)</string>
<string name="activity_type_elliptical">Elliptisch</string>
<string name="pref_summary_banglejs_text_bitmap">Wenn ein Wort nicht mit der Schriftart der Uhr gerendert werden kann, in Gadgetbridge eine Bitmap rendern und die Bitmap auf der Uhr anzeigen</string>
<string name="pref_title_device_internet_access">Internetzugang zulassen</string>
<string name="pref_summary_device_internet_access">Apps auf diesem Gerät Internetzugriff erlauben</string>
<string name="pref_title_device_intents">Absichten erlauben</string>
<string name="pref_summary_device_intents">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.</string>
<string name="pref_screen_vibration_patterns_title">Vibrationsmuster</string>
<string name="pref_screen_vibration_patterns_summary">Vibrationsmuster für verschiedene Benachrichtigungen konfigurieren</string>
<string name="heartrate_bpm_100">100 BPM</string>
<string name="heartrate_bpm_105">105 BPM</string>
<string name="heartrate_bpm_110">110 BPM</string>
<string name="heartrate_bpm_112">112 BPM</string>
<string name="heartrate_bpm_120">120 BPM</string>
<string name="heartrate_bpm_125">125 BPM</string>
<string name="heartrate_bpm_130">130 BPM</string>
<string name="heartrate_bpm_135">135 BPM</string>
<string name="heartrate_bpm_140">140 BPM</string>
<string name="title_activity_world_clock_details">Details Weltzeituhr</string>
<string name="prefs_heartrate_alert_experimental_title">Pulsalarm (experimentell)</string>
<string name="prefs_heartrate_alert_experimental_description">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.</string>
<string name="prefs_heartrate_alert_threshold">Alarmschwelle Herzfrequenz</string>
<string name="prefs_stress_monitoring_title">Stressüberwachung</string>
<string name="prefs_stress_monitoring_description">Stresslevel während des Ausruhens überwachen</string>
<string name="prefs_activity_monitoring_title">Aktivitätsüberwachung</string>
<string name="mi2_prefs_heart_rate_monitoring">Herzfrequenz überwachen</string>
<string name="mi2_prefs_heart_rate_monitoring_summary">Pulsüberwachung konfigurieren</string>
<string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Pulsüberwachung und Warnschwellen konfigurieren</string>
<string name="activity_type_outdoor_running">Laufen im Freien</string>
<string name="activity_type_outdoor_cycling">Radfahren im Freien</string>
<string name="prefs_noise_control">Geräuschkontrolle</string>
<string name="pref_switch_controls_anc_off">Geräuschunterdrückung ←→ Aus</string>
<string name="pref_switch_controls_ambient_off">Umgebung ←→ Aus</string>
<string name="prefs_double_tap_edge_summary">Doppeltippen erkennen, auch wenn nicht auf dem Touchpad getippt wird</string>
<string name="prefs_voice_detect_duration">Ende nach Ruhe für:</string>
<string name="pref_voice_detect_duration_5">5 Sekunden</string>
<string name="pref_voice_detect_duration_10">10 Sekunden</string>
<string name="pref_voice_detect_duration_15">15 Sekunden</string>
<string name="prefs_voice_detect">Spracherkennung</string>
<string name="prefs_double_tap_edge">Kante doppelt antippen</string>
<string name="permission_notification_policy_access">%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</string>
<string name="prefs_seamless_connection_switch_title">Nahtloser Verbindungswechsel</string>
<string name="prefs_seamless_connection_switch_summary">Wechselt die Stöpsel automatisch zwischen den gekoppelten Geräten</string>
<string name="prefs_ambient_volume_left">Umgebungslautstärke links</string>
<string name="prefs_ambient_volume_right">Umgebungslautstärke rechts</string>
<string name="prefs_customize_ambient_sound_summary">An Umgebungsgeräusche anpassen</string>
<string name="prefs_ambient_sound_during_call_summary">Eigene Stimme während des Anrufs hören</string>
<string name="prefs_ambient_settings_title">Optionen Umgebungsgeräusche</string>
<string name="prefs_active_noise_cancelling_level">Stufe der aktiven Geräuschunterdrückung</string>
<string name="prefs_active_noise_cancelling_level_high">Hoch</string>
<string name="prefs_active_noise_cancelling_level_low">Niedrig</string>
<string name="prefs_switch_control_right">Schaltersteuerung rechts</string>
<string name="pref_title_touch_voice_assistant">Sprachassistent</string>
<string name="pref_title_touch_anc">Aktive Geräuschunterdrückung</string>
<string name="pref_title_touch_ambient">Umgebungsgeräusche</string>
<string name="pref_title_touch_spotify">Spotify</string>
<string name="pref_switch_noise_control">Lärmschutz umschalten</string>
<string name="prefs_noise_control_with_one_earbud_summary">Ermöglicht Geräuschkontrolle bei Verwendung nur eines Ohrhörers</string>
<string name="prefs_noise_control_with_one_earbud">Geräuschkontrolle mit einem Ohrhörer</string>
<string name="pref_ambient_sound_tone">Umgebungsgeräusche</string>
<string name="pref_ambient_sound_tone_summary">Von sanft bis klar</string>
<string name="pref_balance">Ausgeglichen</string>
<string name="pref_world_clocks_summary">Uhren für andere Zeitzonen konfigurieren</string>
<string name="world_clock_delete_confirm_title">\'%1$s\' löschen</string>
<string name="world_clock_timezone">Zeitzone</string>
<string name="pref_world_clocks_title">Weltuhren</string>
<string name="world_clock_delete_confirm_description">Möchtest du die Weltzeituhr wirklich löschen\?</string>
<string name="world_clock_no_free_slots_title">Keine freien Slots</string>
<string name="prefs_activity_monitoring_description">Erhöht automatisch die Frequenz der Pulserfassung, wenn das Band körperliche Bewegung erkennt, um die Genauigkeit der Pulserfassung zu erhöhen.</string>
<string name="prefs_ambient_sound_during_call_title">Umgebungsgeräusche während des Anrufs</string>
<string name="permission_notification_listener">%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</string>
<string name="prefs_switch_control_left">Schaltersteuerung links</string>
<string name="pref_switch_controls_anc_ambient">Geräuschunterdrückung ←→ Umgebung</string>
<string name="pref_workout_start_on_phone_summary">Startet/stoppt das Fitness-App-Tracking auf dem Telefon, wenn ein GPS-Training auf dem Band gestartet wird</string>
<string name="pref_workout_send_gps_title">GPS während des Trainings senden</string>
<string name="pref_workout_send_gps_summary">Senden der aktuellen GPS-Position an das Band während des Trainings</string>
<string name="pref_workout_start_on_phone_title">Fitness-App-Tracking</string>
<string name="discovery_scanning_intensity_warning">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.</string>
<string name="discovery_scanning_intensity">Scanning-Intensität</string>
<string name="portuguese_br">Portugiesisch (Brasilien)</string>
<string name="quick_alarm">Schneller Alarm</string>
<string name="quick_alarm_description">Alarm vom Widget</string>
<string name="pref_device_action_phone_gps_location_listener_stop">GPS-Standort Stopp</string>
<string name="notification_channel_gps">GPS-Ortung</string>
<string name="notification_gps_title">Gadgetbridge GPS</string>
<string name="portuguese_pt">Portugiesisch (Portugal)</string>
<string name="notification_gps_text">Senden des GPS-Standorts an %1$d Geräte</string>
</resources>

View File

@ -1624,4 +1624,9 @@
<string name="pref_world_clocks_summary">Configurar los relojes para otras zonas horarias</string>
<string name="world_clock_delete_confirm_title">Eliminar \'%1$s\'</string>
<string name="world_clock_delete_confirm_description">¿Está seguro de que quiere eliminar el reloj mundial\?</string>
<string name="pref_title_banglejs_text_bitmap">Texto como mapa de bits</string>
<string name="pref_summary_banglejs_text_bitmap">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</string>
<string name="pref_title_device_internet_access">Permitir acceso a Internet</string>
<string name="pref_summary_device_internet_access">Permitir que las aplicaciones de este dispositivo accedan a Internet</string>
<string name="pref_title_device_intents">Permitir intenciones</string>
</resources>

View File

@ -1601,7 +1601,9 @@ Temps de sommeil préféré en heures</string>
<string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_activity_title_banglejs_main">A propos de Bangle.js Gadgetbridge</string>
<string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_description_banglejs_main">Application complémentaire Android pour Bangle.js élaborée sur la base du projet Gadgetbridge, avec des fonctionnalités Internet supplémentaires.</string>
<string name="about_description_banglejs_main">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.</string>
<string name="application_name_banglejs_nopebble">Bangle.js pour Gadgetbridge</string>
<string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js pour Gadgetbridge</string>
<string name="about_activity_title_main_nightly">A propos de Gatgetbridge Nightly</string>
@ -1612,7 +1614,9 @@ Temps de sommeil préféré en heures</string>
<string name="title_activity_controlcenter_main_nopebble">Gadgetbridge Nightly Sans Pebble</string>
<string name="gadgetbridge_running_main_nightly">GB Nightly en fonctionnement</string>
<string name="gadgetbridge_running_banglejs_main">Bangle.js en fonctionnement</string>
<string name="about_description_banglejs_nopebble">Application complémentaire Android pour Bangle.js élaborée sur la base du projet Gadgetbridge, avec des fonctionnalités Internet supplémentaires.</string>
<string name="about_description_banglejs_nopebble">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.</string>
<string name="gadgetbridge_running_banglejs_nopebble">Bangle.js en fonctionnement</string>
<string name="application_name_main_nightly">Gadgetbridge (version Nightly)</string>
<string name="title_activity_controlcenter_main_nightly">Gadgetbrigde version Nightly</string>
@ -1673,4 +1677,65 @@ Temps de sommeil préféré en heures</string>
<string name="mi5_prefs_workout_activity_types">Types d\'activités physiques</string>
<string name="mi5_prefs_workout_activity_types_summary">Choisir les types d\'activité à afficher sur l\'écran d\'activité physique</string>
<string name="activity_type_outdoor_cycling">Vélo en extérieur</string>
<string name="pref_title_banglejs_text_bitmap">Texte comme Images</string>
<string name="pref_summary_banglejs_text_bitmap">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</string>
<string name="pref_title_device_internet_access">Autoriser l\'accès à Internet</string>
<string name="pref_summary_device_internet_access">Permettre aux apps sur cet appareil d\'accéder à Internet</string>
<string name="pref_title_device_intents">Permettre les intentions</string>
<string name="pref_workout_start_on_phone_title">Application de suivi sportif</string>
<string name="pref_workout_start_on_phone_summary">Démmare/arrête le suivi sportif sur le téléphone si une activité GPS est démarré sur le bracelet</string>
<string name="pref_workout_send_gps_title">Envoyer le GPS durant l\'exercice</string>
<string name="pref_workout_send_gps_summary">Envoyer les données GPS en cours durant un exercice</string>
<string name="notification_gps_title">GPS Gadgebridge</string>
<string name="notification_gps_text">Envoi des données GPS au(x) appareil(s) %1$d</string>
<string name="notification_channel_gps">Suivi GPS</string>
<string name="permission_notification_listener">%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</string>
<string name="discovery_scanning_intensity">Intensité du scan</string>
<string name="discovery_scanning_intensity_warning">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.</string>
<string name="portuguese_br">Portugais (Brésil)</string>
<string name="portuguese_pt">Portugais (Portugal)</string>
<string name="prefs_active_noise_cancelling_level_low">Bas</string>
<string name="prefs_double_tap_edge">Double appui sur le bord</string>
<string name="prefs_voice_detect_duration">Arrêt après pause pendant:</string>
<string name="pref_voice_detect_duration_5">5 secondes</string>
<string name="prefs_switch_control_left">Mettre le contrôle à gauche</string>
<string name="prefs_switch_control_right">Mettre le contrôle à droite</string>
<string name="pref_title_touch_ambient">Son Ambiant</string>
<string name="pref_title_touch_spotify">Spotify</string>
<string name="pref_switch_noise_control">Changer le contrôle du bruit</string>
<string name="prefs_noise_control_with_one_earbud">Contrôle du bruit avec un écouteur</string>
<string name="prefs_active_noise_cancelling_level">Niveau actif de l\'annulation de bruit</string>
<string name="prefs_active_noise_cancelling_level_high">Haut</string>
<string name="pref_title_touch_voice_assistant">Assistant vocal</string>
<string name="pref_title_touch_anc">Annulation de bruit active</string>
<string name="pref_title_touch_quick_ambient">Son Ambiant</string>
<string name="prefs_double_tap_edge_summary">Détecter un double appui même si hors zone tactile</string>
<string name="pref_voice_detect_duration_10">10 secondes</string>
<string name="pref_device_action_phone_gps_location_listener_stop">Arrêt du gestionnaire GPS</string>
<string name="permission_notification_policy_access">%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</string>
<string name="prefs_voice_detect_summary">Activer le son ambiant et réduction de bruit automatique lors de la détection de voix</string>
<string name="quick_alarm">Alarme rapide</string>
<string name="quick_alarm_description">Alarme d\'un widget</string>
<string name="pref_summary_device_intents">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 .</string>
<string name="pref_title_touch_volume">Volume</string>
<string name="prefs_noise_control_with_one_earbud_summary">Permettre le contrôle du bruit en utilisant un seul écouteur</string>
<string name="pref_ambient_sound_tone">Tonalité du son ambiant</string>
<string name="pref_ambient_sound_tone_summary">De doux à Clair</string>
<string name="pref_balance">Balance</string>
<string name="pref_switch_controls_ambient_off">Ambiant &lt;-&gt; Off</string>
<string name="prefs_noise_control">Contrôle du bruit</string>
<string name="prefs_voice_detect">Détection de la voix</string>
<string name="pref_switch_controls_anc_ambient">Annulation de bruit &lt;-&gt; Ambiant</string>
<string name="pref_switch_controls_anc_off">Annulation de bruit &lt;-&gt; Off</string>
<string name="pref_voice_detect_duration_15">15 secondes</string>
<string name="pref_device_action_fitness_app_control_toggle">Basculer d\'app de suivi sportif</string>
<string name="pref_title_banglejs_webview_url">URL de chargement de l\'app</string>
<string name="permission_location">%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.</string>
<string name="pref_summary_banglejs_webview_url">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</string>
</resources>

View File

@ -1602,8 +1602,12 @@
<string name="about_activity_title_banglejs_main">על Bangle.js Gadgetbridge</string>
<string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
<string name="about_activity_title_banglejs_nopebble">על Bangle.js Gadgetbridge</string>
<string name="about_description_banglejs_nopebble">יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט.</string>
<string name="about_description_banglejs_main">יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט.</string>
<string name="about_description_banglejs_nopebble">יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט.
\n
\nעקב מדיניות של חנות Google Play, אסור לנו להציב קישור תרומה ביישומון עצמו אך אם היישומון נושא חן בעיניך אפשר לתרום דרך עמוד הבית של Gadgetbridge להלן.</string>
<string name="about_description_banglejs_main">יישומון Android מלווה ל־Bangle.js שנבנה על גבי מיזם Gadgetbridge עם תוספת של גישה לאינטרנט.
\n
\nעקב מדיניות של חנות Google Play, אסור לנו להציב קישור תרומה ביישומון עצמו אך אם היישומון נושא חן בעיניך אפשר לתרום דרך עמוד הבית של Gadgetbridge להלן.</string>
<string name="application_name_main_nightly">Gadgetbridge (גרסה ניסיונית)</string>
<string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
<string name="gadgetbridge_running_banglejs_main">Bangle.js פעיל</string>
@ -1705,4 +1709,29 @@
<string name="pref_summary_banglejs_text_bitmap">אם אי אפשר לעבד תמונות עם גופן השעון, הוא יומר למפת סיביות ב־Gadgetbridge שתוצג בשעון</string>
<string name="pref_summary_device_intents">לאפשר ליישומוני שעון של Bangle.js לשלוח Intents ל־Android ולאפשר ליישומי Android אחרים (כמו Tasker) לשלוח נתונים ל־Bangle.js באמצעות ה־Intent com.banglejs.uart.tx.</string>
<string name="pref_title_device_intents">לאפשר Intents</string>
<string name="pref_workout_send_gps_title">שליחת GPS במהלך אימון</string>
<string name="pref_workout_send_gps_summary">שליחת מיקום ה־GPS הנוכחי לצמיד במהלך אימון</string>
<string name="notification_gps_title">GPS של Gadgetbridge</string>
<string name="notification_gps_text">שליחת מיקום GPS ל־%1$d מכשירים</string>
<string name="pref_device_action_phone_gps_location_listener_stop">עצירת האזנת מיקום GPS</string>
<string name="pref_workout_start_on_phone_title">מעקב ביישומון חיטוב</string>
<string name="pref_workout_start_on_phone_summary">התחלת/עצירת מעקב ביישומון חיטוב בטלפון כאשר מופעל אימון GPS בצמיד</string>
<string name="notification_channel_gps">מעקב GPS</string>
<string name="discovery_scanning_intensity">עוצמת סריקה</string>
<string name="discovery_scanning_intensity_warning">אם חווית קפאון או חוסר תגובתיות, כדאי לנסות לשנות את עוצמת הסריקה לרמה נמוכה. אם המכשיר שלך לא מתגלה, כדאי לנסות לשנות את עוצמת הסריקה לרמה גבוהה.</string>
<string name="portuguese_pt">פורטוגלית של פורטוגל</string>
<string name="quick_alarm_description">שעון מעורר מווידג׳ט</string>
<string name="portuguese_br">פורטוגלית ברזילאית</string>
<string name="quick_alarm">שעון מעורר מהיר</string>
<string name="pref_title_banglejs_webview_url">כתובת טוען יישומון</string>
<string name="pref_summary_banglejs_webview_url">אם ברצונך להשתמש בטוען יישומון אחר נא לציין את הכתובת https://…/android.html, אם לא אפשר להשאיר ריק כדי להשתמש ב־https://banglejs.com/apps</string>
<string name="permission_notification_listener">ל־%1$s דרושה גישה להתראות כדי להציג אותן בשעון שלך כאשר מסך הטלפון שלך כבוי.
\n
\nנא לגעת ב־‚%2$s ואז %1$s ולהפעיל את ‚לאפשר גישה להתראות’ ואז לגעת ב‚חזרה’ כדי לחזור אל %1$s</string>
<string name="permission_location">ל־%1$s דרושה גישה למיקום שלך ברקע כדי לאפשר לו להישאר מחובר לשעון שלך גם כאשר המסך שלך כבוי.
\n
\nנא לגעת ב‚%2$s כדי להסכים.</string>
<string name="permission_notification_policy_access">ל־%1$s דרושה גישה להגדרות לא להפריע כדי לכבד אותן בשעון שלך כאשר מסך הטלפון שלך כבוי.
\n
\nנא לגעת ב־‚%2$s ואז %1$s ולהפעיל את ‚לאפשר לא להפריע’ ואז לגעת ב‚חזרה’ כדי לחזור אל %1$s</string>
</resources>

View File

@ -1576,4 +1576,7 @@
<string name="sony_anc_optimize_confirmation_title">Optimizador de cancelamento de ruído</string>
<string name="pref_anc_optimizer_state_pressure">Pressão atmosférica</string>
<string name="sony_anc_optimize_confirmation_description">Coloque os headphones como os utilizaria normalmente. Se as condições de utilização ou pressão atmosférica alterarem, execute o optimizador novamente.</string>
<string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_activity_title_banglejs_main">Acerca de Bangle.js Gadgetbridge</string>
<string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
</resources>

View File

@ -1623,12 +1623,16 @@
<string name="gadgetbridge_running_main_nopebble">Gecelik PebbleYok GB çalışıyor</string>
<string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_activity_title_banglejs_main">Bangle.js Gadgetbridge Hakkında</string>
<string name="about_description_banglejs_main">İnternet erişimi eklenen Gadgetbridge projesinin üzerine inşa edilmiş Bangle.js için Android yardımcı uygulaması.</string>
<string name="about_description_banglejs_main">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.</string>
<string name="title_activity_controlcenter_main_nightly">Gadgetbridge Gecelik</string>
<string name="application_name_main_nopebble">Gadgetbridge (Gecelik, Pebble sağlayıcısı yok)</string>
<string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_description_main_nopebble">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.</string>
<string name="about_description_banglejs_nopebble">İnternet erişimi eklenen Gadgetbridge projesinin üzerine inşa edilmiş Bangle.js için Android yardımcı uygulaması.</string>
<string name="about_description_banglejs_nopebble">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.</string>
<string name="gadgetbridge_running_banglejs_nopebble">Bangle.js çalışıyor</string>
<string name="application_name_main_nightly">Gadgetbridge (Gecelik)</string>
<string name="prefs_activate_display_on_lift_sensitivity">Hassasiyet</string>
@ -1722,4 +1726,29 @@
<string name="pref_summary_device_internet_access">Bu aygıttaki uygulamaların internete erişmesine izin ver</string>
<string name="pref_summary_banglejs_text_bitmap">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</string>
<string name="pref_title_device_internet_access">İnternet Erişimine İzin Ver</string>
<string name="pref_workout_start_on_phone_title">Fitness uygulaması izlemesi</string>
<string name="pref_workout_start_on_phone_summary">Bileklikte bir GPS egzersizi başlatıldığında telefonda fitness uygulaması izlemeyi başlat/durdur</string>
<string name="pref_workout_send_gps_summary">Egzersiz sırasında geçerli GPS konumunu bilekliğe gönder</string>
<string name="notification_channel_gps">GPS izleme</string>
<string name="notification_gps_title">Gadgetbridge GPS</string>
<string name="notification_gps_text">GPS konumu %1$d aygıta gönderiliyor</string>
<string name="pref_device_action_phone_gps_location_listener_stop">GPS Konumu Dinleyiciyi Durdur</string>
<string name="pref_workout_send_gps_title">Egzersiz sırasında GPS gönder</string>
<string name="permission_notification_listener">%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</string>
<string name="permission_notification_policy_access">%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</string>
<string name="discovery_scanning_intensity">Tarama yoğunluğu</string>
<string name="discovery_scanning_intensity_warning">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.</string>
<string name="quick_alarm">Hızlı alarm</string>
<string name="quick_alarm_description">Widget alarmı</string>
<string name="portuguese_pt">Portekizce (Portekiz)</string>
<string name="portuguese_br">Portekizce (Brezilya)</string>
<string name="pref_title_banglejs_webview_url">Uygulama yükleyici URL\'si</string>
<string name="pref_summary_banglejs_webview_url">Ö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</string>
<string name="permission_location">%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.</string>
</resources>

View File

@ -356,7 +356,7 @@
<string name="pref_summary_low_latency_fw_update">Це може допомогти на пристроях, на які не вдається встановити мікропрограми.</string>
<string name="you_did_not_sleep">Ви не спали</string>
<string name="miband_prefs_reserve_alarm_calendar">Сигнали для резервування для майбутніх подій</string>
<string name="miband_prefs_hr_sleep_detection">Використовувати датчик пульсу для поліпшення виявлення сну</string>
<string name="miband_prefs_hr_sleep_detection">Використовувати датник пульсу для поліпшення виявлення сну</string>
<string name="dateformat_time">Час</string>
<string name="dateformat_date_time">Дата та час</string>
<string name="pref_title_unit_system">Одиниці вимірювання</string>
@ -529,7 +529,7 @@
<string name="pref_summary_pebble_gatt_clientonly">Це експериментальне налаштування виключно для Pebble 2, застосуйте якщо є проблеми зі з\'єднанням</string>
<string name="pref_auto_fetch">Автозавантаження даних про діяльність</string>
<string name="pref_auto_fetch_limit_fetches">Найменший час між отриманнями</string>
<string name="pref_auto_fetch_limit_fetches_summary">Отримати кожні %d хвилин(и)</string>
<string name="pref_auto_fetch_limit_fetches_summary">Отримувати що %d хвилин</string>
<string name="russian">Російська</string>
<string name="german">Німецька</string>
<string name="italian">Італійська</string>
@ -1603,7 +1603,9 @@
<string name="watchface_setting_light_up_on_notification">Засвітлювати за нового сповіщення</string>
<string name="menuitem_email">Е-пошта</string>
<string name="application_name_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_description_banglejs_main">Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету.</string>
<string name="about_description_banglejs_main">Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету.
\n
\nЗгідно з правилами Google Play Маркету, нам не дозволено вказувати посилання на допомогу в самому застосунку, але якщо він вам до вподоби, ви можете посприяти розвитку проєкту через домашню сторінку Gadgetbridge нижче.</string>
<string name="gadgetbridge_running_banglejs_main">Bangle.js працює</string>
<string name="application_name_banglejs_nopebble">Bangle.js Gadgetbridge</string>
<string name="title_activity_controlcenter_banglejs_nopebble">Bangle.js Gadgetbridge</string>
@ -1618,7 +1620,9 @@
<string name="about_activity_title_banglejs_nopebble">Про Bangle.js Gadgetbridge</string>
<string name="about_activity_title_banglejs_main">Про Bangle.js Gadgetbridge</string>
<string name="title_activity_controlcenter_banglejs_main">Bangle.js Gadgetbridge</string>
<string name="about_description_banglejs_nopebble">Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету.</string>
<string name="about_description_banglejs_nopebble">Засосунок-компаньйон Android для Bangle.js побудований на основі проєкту Gadgetbridge, з додаванням доступу до інтернету.
\n
\nЗгідно з правилами Google Play Маркету, нам не дозволено вказувати посилання на допомогу в самому застосунку, але якщо він вам до вподоби, ви можете посприяти розвитку проєкту через домашню сторінку Gadgetbridge нижче.</string>
<string name="title_activity_controlcenter_main_nightly">Gadgetbridge Nightly</string>
<string name="about_description_main_nightly">Безхмарна вільна з копілефт ліцензією заміна власницьких застосунків для пристроїв Android. Випуски Gadgetbridge Nightly. Його не можна встановити, якщо у вас уже встановлено застосунок Gadgetbridge або Pebble через конфлікт у постачальника Pebble.</string>
<string name="about_description_main_nopebble">Безхмарна вільна з копілефт ліцензією заміна власницьких застосунків для пристроїв Android. Випуски Gadgetbridge Nightly. У цій версії постачальник Pebble перейменований для запобігання конфліктів, тому деякі інтеграції, пов\'язані з Pebble, не працюватимуть, але його можна встановити разом із наявним установленим Gadgetbridge.</string>
@ -1713,4 +1717,29 @@
<string name="pref_title_device_intents">Дозволити наміри</string>
<string name="pref_title_banglejs_text_bitmap">Текст у вигляді растрових зображень</string>
<string name="pref_summary_banglejs_text_bitmap">Якщо слово не може бути відтворено шрифтом годинника, перетворювати його на растрове зображення в Gadgetbridge і показувати растрове зображення на годиннику</string>
<string name="pref_workout_start_on_phone_title">Відстеження фітнес-застосунку</string>
<string name="pref_workout_start_on_phone_summary">Запускати/припиняти відстеження фітнес-застосунку на телефоні, коли GPS-тренування розпочато на браслеті</string>
<string name="pref_workout_send_gps_title">Надсилати GPS під час тренування</string>
<string name="pref_workout_send_gps_summary">Надсилати поточне місцеперебування GPS на годинник під час тренування</string>
<string name="notification_channel_gps">GPS-стеження</string>
<string name="notification_gps_title">Gadgetbridge GPS</string>
<string name="notification_gps_text">Надсилання місцеперебування GPS на пристрій %1$d</string>
<string name="permission_notification_listener">%1$s потребує доступу до сповіщень, щоб показувати їх на годиннику коли екран телефону вимкнено.
\n
\nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити доступ до сповіщень», а потім торкніться «Назад», щоб повернутися до %1$s</string>
<string name="permission_notification_policy_access">%1$s потребує доступу до налаштувань «Не турбувати», щоб дотримуватися їх на годиннику коли екран телефону вимкнено.
\n
\nТоркніться «%2$s», потім «%1$s» і увімкніть «Дозволити не турбувати», а потім торкніться «Назад», щоб повернутися до %1$s</string>
<string name="pref_device_action_phone_gps_location_listener_stop">Зупинка відстежувача GPS-розташування</string>
<string name="discovery_scanning_intensity_warning">Якщо ви бачите заморожування або несприйняття, спробуйте встановити нижчий рівень інтенсивності сканування. Якщо пристрій не виявлено, спробуйте встановити інтенсивність сканування на вищий рівень.</string>
<string name="discovery_scanning_intensity">Інтенсивність сканування</string>
<string name="portuguese_br">Португальська (Бразилія)</string>
<string name="quick_alarm">Швидкий будильник</string>
<string name="portuguese_pt">Португальська (Португалія)</string>
<string name="quick_alarm_description">Будильник з віджета</string>
<string name="pref_title_banglejs_webview_url">URL-адреса завантажувача застосунку</string>
<string name="pref_summary_banglejs_webview_url">Якщо ви хочете, щоб спеціальний завантажувач застосунків помістив вашу https://…/android.html URL-адресу тут. В іншому випадку залиште порожнім для https://banglejs.com/apps</string>
<string name="permission_location">%1$s потрібен доступ до вашого місцеперебування у фоновому режимі, щоб дозволити йому залишатися на зв\'язку з годинником, навіть коли екран вимкнено.
\n
\nТоркніться «%2$s», щоб погодитися.</string>
</resources>

View File

@ -1711,4 +1711,24 @@
<string name="pref_title_device_intents">允许意向</string>
<string name="pref_title_device_internet_access">允许互联网访问</string>
<string name="pref_summary_device_internet_access">允许此设备上的应用访问互联网</string>
<string name="pref_workout_start_on_phone_title">健身应用追踪</string>
<string name="pref_workout_start_on_phone_summary">在手环上开始 GPS 锻炼时,在手机上开始/停止健身应用跟踪</string>
<string name="notification_channel_gps">GPS跟踪</string>
<string name="permission_notification_listener">%1$s需要访问通知以便在你的手表上显示这些通知。
\n
\n请点击\'%2$s\'然后\'%1$s\'并启用\'允许通知访问\',然后点击\'返回\'以返回到%1$s</string>
<string name="permission_notification_policy_access">%1$s 需要访问“请勿打扰”设置才能在您的手表上使用它们。
\n
\n请点击“%2$s”然后点击“%1$s”并启用“允许请勿打扰”然后点击“返回”返回到%1$s</string>
<string name="pref_workout_send_gps_title">在锻炼期间发送 GPS</string>
<string name="pref_workout_send_gps_summary">锻炼时将当前的GPS位置发送到手环上</string>
<string name="notification_gps_title">Gadgetbridge GPS</string>
<string name="notification_gps_text">将 GPS 位置发送到 %1$d 设备</string>
<string name="pref_device_action_phone_gps_location_listener_stop">GPS 位置监听器停止</string>
<string name="discovery_scanning_intensity">扫描强度</string>
<string name="discovery_scanning_intensity_warning">如果您遇到卡住或无反应的情况,尝试将扫描强度设置为较低水平。如果你的设备没有被发现,尝试将扫描强度设置到更高的水平。</string>
<string name="portuguese_br">葡萄牙语(巴西)</string>
<string name="quick_alarm">快速闹钟</string>
<string name="portuguese_pt">葡萄牙语 (葡萄牙)</string>
<string name="quick_alarm_description">从小工具发出的闹钟</string>
</resources>

View File

@ -1666,4 +1666,7 @@
<string name="pref_summary_opentracks_packagename">Used for starting/stopping GPS track recording in external fitness app.</string>
<string name="watchface_dialog_pre_setting_position">pre-setting position to %s</string>
<string name="watchface_setting_light_up_on_notification">Light up on new notification</string>
<string name="info_no_devices_connected">no devices connected</string>
<string name="info_connected_count">%d devices connected</string>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="Connection over Bluetooth classic" />
<CheckBoxPreference
android:key="prefs_key_device_reconnect_on_acl"
android:title="connect on connection from device"
android:summary="establish a connection when connection is initiated by device, like headphones"
android:layout="@layout/preference_checkbox" />
</PreferenceScreen>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="Connection over BLE" />
<CheckBoxPreference
android:key="prefs_key_device_auto_reconnect"
android:title="auto-reconnect to device"
android:summary="proactively try to connect to device periodically"
android:layout="@layout/preference_checkbox" />
</PreferenceScreen>

View File

@ -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"/>
<CheckBoxPreference
android:layout="@layout/preference_checkbox"
android:defaultValue="true"