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

multi-device-support (#2526)

this PR aims to add device for multiple connected devices at once.

A lot of stuff already works, some things need to be done:

- [x] change DeviceCommunicationService to hold multiple devices and supports
- [x] implement connect / disconnect logic
- [x] widgets, not really suited for multiple devices, so far
- [x] change the notification to show multiple devices
- [ ] change GBDeviceService#onFindDevice and similar API functions to target individual devices, not all connected.
- [x] move auto-reconnect setting to device settings
- [x] fix music event crash
- [x] work out behaviour when pressing "connect" from notification
- [ ] handle service crashes
- [ ] suit coordinator methods for multiple devices of same kind
- [x] change ACL_CONNECTED receiver to connect to devices that are not currently registered in DeviceCommunicationService
- [ ] adjust after-boot auto-connection logic
- [ ] fix hanging device support. Device says disconnected, GB says connected
- [x] firmware updater doesn't work

My attempt to make onFindDevice work was to change the arguments to ```EventHandler#onFindDevice(GBDevice device, boolean start)```.
The Problem is that this forces the device-specific implementations to also accept GBDevice as an argument.

Co-authored-by: Daniel Dakhno <dakhnod@gmail.com>
Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org>
Co-authored-by: dakhnod <dakhnod@gmail.com>
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2526
Co-authored-by: dakhnod <dakhnod@noreply.codeberg.org>
Co-committed-by: dakhnod <dakhnod@noreply.codeberg.org>
This commit is contained in:
dakhnod 2022-06-14 18:05:41 +02:00 committed by Andreas Shimokawa
parent e683a5bc56
commit 4a8523f790
40 changed files with 1036 additions and 561 deletions

View File

@ -68,6 +68,7 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -272,26 +273,28 @@ public class DebugActivity extends AbstractGBActivity {
if (context instanceof GBApplication) { if (context instanceof GBApplication) {
GBApplication gbApp = (GBApplication) context; GBApplication gbApp = (GBApplication) context;
final GBDevice device = gbApp.getDeviceManager().getSelectedDevice(); final List<GBDevice> devices = gbApp.getDeviceManager().getSelectedDevices();
if (device != null) { if(devices.size() == 0){
new DatePickerDialog(DebugActivity.this, new DatePickerDialog.OnDateSetListener() { GB.toast("Device not selected/connected", Toast.LENGTH_LONG, GB.INFO);
@Override return;
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { }
Calendar date = Calendar.getInstance(); new DatePickerDialog(DebugActivity.this, new DatePickerDialog.OnDateSetListener() {
date.set(year, monthOfYear, dayOfMonth); @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; long timestamp = date.getTimeInMillis() - 1000;
GB.toast("Setting lastSyncTimeMillis: " + timestamp, Toast.LENGTH_LONG, GB.INFO); GB.toast("Setting lastSyncTimeMillis: " + timestamp, Toast.LENGTH_LONG, GB.INFO);
for(GBDevice device : devices){
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit(); SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit();
editor.remove("lastSyncTimeMillis"); //FIXME: key reconstruction is BAD editor.remove("lastSyncTimeMillis"); //FIXME: key reconstruction is BAD
editor.putLong("lastSyncTimeMillis", timestamp); editor.putLong("lastSyncTimeMillis", timestamp);
editor.apply(); editor.apply();
} }
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show(); }
} else { }, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
GB.toast("Device not selected/connected", Toast.LENGTH_LONG, GB.INFO);
}
} }
@ -411,8 +414,8 @@ public class DebugActivity extends AbstractGBActivity {
public void onClick(View v) { public void onClick(View v) {
Context context = getApplicationContext(); Context context = getApplicationContext();
GBApplication gbApp = (GBApplication) context; GBApplication gbApp = (GBApplication) context;
final GBDevice device = gbApp.getDeviceManager().getSelectedDevice(); List<GBDevice> devices = gbApp.getDeviceManager().getSelectedDevices();
if (device != null) { for(GBDevice device : devices){
GBApplication.deleteDeviceSpecificSharedPrefs(device.getAddress()); GBApplication.deleteDeviceSpecificSharedPrefs(device.getAddress());
} }
} }

View File

@ -214,6 +214,19 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
} else { } else {
setInfoText(getString(R.string.installer_activity_wait_while_determining_status)); 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 // needed to get the device
if (device == null || !device.isConnected()) { if (device == null || !device.isConnected()) {
connect(); connect();
@ -245,14 +258,17 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
List<DeviceCoordinator> allCoordinators = DeviceHelper.getInstance().getAllCoordinators(); List<DeviceCoordinator> allCoordinators = DeviceHelper.getInstance().getAllCoordinators();
List<DeviceCoordinator> sortedCoordinators = new ArrayList<>(allCoordinators.size()); List<DeviceCoordinator> sortedCoordinators = new ArrayList<>(allCoordinators.size());
GBDevice connectedDevice = deviceManager.getSelectedDevice(); List<GBDevice> devices = deviceManager.getSelectedDevices();
if (connectedDevice != null && connectedDevice.isConnected()) { for(GBDevice connectedDevice : devices){
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice); if (connectedDevice.isConnected()) {
if (coordinator != null) { DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice);
connectedCoordinators.add(coordinator); if (coordinator != null) {
connectedCoordinators.add(coordinator);
}
} }
} }
sortedCoordinators.addAll(connectedCoordinators); sortedCoordinators.addAll(connectedCoordinators);
for (DeviceCoordinator coordinator : allCoordinators) { for (DeviceCoordinator coordinator : allCoordinators) {
if (!connectedCoordinators.contains(coordinator)) { if (!connectedCoordinators.contains(coordinator)) {

View File

@ -811,6 +811,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device); int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device);
String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device); String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device);
supportedSettings = ArrayUtils.insert(0, supportedSettings, coordinator.getSupportedDeviceSpecificConnectionSettings());
if (supportedLanguages != null) { if (supportedLanguages != null) {
supportedSettings = ArrayUtils.insert(0, supportedSettings, R.xml.devicesettings_language_generic); 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) { public boolean onLongClick(View v) {
if (device.getState() != GBDevice.State.NOT_CONNECTED) { if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting); showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect(); GBApplication.deviceService().disconnect(device);
} }
return true; return true;
} }
@ -261,7 +261,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
); );
//device specific settings //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() 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,6 +38,7 @@ import java.util.Collections;
import de.greenrobot.dao.query.QueryBuilder; import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
@ -61,6 +63,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return getSupportedType(candidate).isSupported(); return getSupportedType(candidate).isSupported();
} }
@Override
public ConnectionType getConnectionType() {
return ConnectionType.BOTH;
}
@Override @Override
public boolean supports(GBDevice device) { public boolean supports(GBDevice device) {
return getDeviceType().equals(device.getType()); return getDeviceType().equals(device.getType());
@ -86,7 +93,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public void deleteDevice(final GBDevice gbDevice) throws GBException { public void deleteDevice(final GBDevice gbDevice) throws GBException {
LOG.info("will try to delete device: " + gbDevice.getName()); LOG.info("will try to delete device: " + gbDevice.getName());
if (gbDevice.isConnected() || gbDevice.isConnecting()) { if (gbDevice.isConnected() || gbDevice.isConnecting()) {
GBApplication.deviceService().disconnect(); GBApplication.deviceService().disconnect(gbDevice);
} }
Prefs prefs = getPrefs(); Prefs prefs = getPrefs();
@ -258,9 +265,24 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false; 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 @Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) { public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return null; return new int[0];
} }
@Override @Override

View File

@ -77,6 +77,27 @@ public interface DeviceCoordinator {
*/ */
int BONDING_STYLE_LAZY = 4; 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. * Checks whether this coordinator handles the given candidate.
* Returns the supported device type for the given candidate or * Returns the supported device type for the given candidate or
@ -88,6 +109,13 @@ public interface DeviceCoordinator {
@NonNull @NonNull
DeviceType getSupportedType(GBDeviceCandidate candidate); DeviceType getSupportedType(GBDeviceCandidate candidate);
/**
* Returns the type of connection, Classic of BLE
*
* @return ConnectionType
*/
ConnectionType getConnectionType();
/** /**
* Checks whether this coordinator handles the given candidate. * Checks whether this coordinator handles the given candidate.
* *
@ -363,6 +391,13 @@ public interface DeviceCoordinator {
*/ */
boolean supportsUnicodeEmojis(); 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). * 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. * This allows direct access to the list from ListAdapters.
*/ */
private final List<GBDevice> deviceList = new ArrayList<>(); private final List<GBDevice> deviceList = new ArrayList<>();
private GBDevice selectedDevice = null; private List<GBDevice> selectedDevices = new ArrayList<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
@ -137,22 +137,13 @@ public class DeviceManager {
} }
private void updateSelectedDevice(GBDevice dev) { private void updateSelectedDevice(GBDevice dev) {
if (selectedDevice == null) { selectedDevices.clear();
selectedDevice = dev; for(GBDevice device : deviceList){
} else { if(device.isInitialized()){
if (selectedDevice.equals(dev)) { selectedDevices.add(device);
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
}
} }
} }
GB.updateNotification(selectedDevice, context); GB.updateNotification(selectedDevices, context);
} }
private void refreshPairedDevices() { private void refreshPairedDevices() {
@ -184,9 +175,8 @@ public class DeviceManager {
return Collections.unmodifiableList(deviceList); return Collections.unmodifiableList(deviceList);
} }
@Nullable public List<GBDevice> getSelectedDevices() {
public GBDevice getSelectedDevice() { return selectedDevices;
return selectedDevice;
} }
private void notifyDevicesChanged() { private void notifyDevicesChanged() {

View File

@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder; import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -168,18 +169,27 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supportsAppListFetching() { public boolean supportsAppListFetching() {
GBDevice mGBDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (mGBDevice != null && mGBDevice.getFirmwareVersion() != null) { for(GBDevice device : devices){
return PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) < 3; if(device.getType() == DeviceType.PEBBLE){
if (device.getFirmwareVersion() != null) {
return PebbleUtils.getFwMajor(device.getFirmwareVersion()) < 3;
}
}
} }
return false; return false;
} }
@Override @Override
public boolean supportsAppReordering() { public boolean supportsAppReordering() {
GBDevice mGBDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (mGBDevice != null && mGBDevice.getFirmwareVersion() != null) { for(GBDevice device : devices){
return PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3; if(device.getType() == DeviceType.PEBBLE){
if (device.getFirmwareVersion() != null) {
return PebbleUtils.getFwMajor(device.getFirmwareVersion()) >= 3;
}
}
} }
return false; return false;
} }

View File

@ -25,7 +25,7 @@ import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; 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.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; 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.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class QC35Coordinator extends AbstractDeviceCoordinator { public class QC35Coordinator extends AbstractBLClassicDeviceCoordinator {
@Override @Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { 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() { private void refreshInstalledApps() {
try { try {
GBDevice selected = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (selected.getType() != DeviceType.FOSSILQHYBRID || !selected.isConnected() || !selected.getModel().startsWith("DN") || selected.getState() != GBDevice.State.INITIALIZED) { boolean deviceFound = false;
throw new RuntimeException("Device not connected"); 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(); } catch (JSONException e) {
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) {
toast(e.getMessage()); toast(e.getMessage());
finish(); finish();
return; return;
} }
throw new RuntimeException("Device not connected");
} }
class AppsListAdapter extends ArrayAdapter<String> { class AppsListAdapter extends ArrayAdapter<String> {

View File

@ -29,6 +29,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
@ -85,9 +87,16 @@ public class CalibrationActivity extends AbstractGBActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qhybrid_calibration); 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(); Toast.makeText(this, R.string.watch_not_connected, Toast.LENGTH_LONG).show();
finish(); finish();
return; return;

View File

@ -303,12 +303,14 @@ public class ConfigActivity extends AbstractGBActivity {
} }
}); });
device = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (device == null || device.getType() != DeviceType.FOSSILQHYBRID || device.getFirmwareVersion().charAt(2) != '0') { for(GBDevice device : devices){
setSettingsError(getString(R.string.watch_not_connected)); if (device.getType() == DeviceType.FOSSILQHYBRID && device.getFirmwareVersion().charAt(2) == '0') {
} else { updateSettings();
updateSettings(); return;
}
} }
setSettingsError(getString(R.string.watch_not_connected));
} }
private void updateTimeOffset() { private void updateTimeOffset() {

View File

@ -46,6 +46,8 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; 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.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement; 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) // 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(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
fwVersion_str = fwVersion_str.replaceFirst("^DN", "").replaceFirst("r\\.v.*", ""); for(GBDevice device : devices){
Version fwVersion = new Version(fwVersion_str); if(device.getType() == DeviceType.FOSSILQHYBRID){
if (fwVersion.compareTo(new Version("1.0.2.20")) >= 0) { String fwVersion_str = device.getFirmwareVersion();
findViewById(R.id.qhybrid_widget_add).setEnabled(false); fwVersion_str = fwVersion_str.replaceFirst("^DN", "").replaceFirst("r\\.v.*", "");
for (int i = 0; i < widgetButtonsMapping.size(); i++) { Version fwVersion = new Version(fwVersion_str);
final int widgetButtonId = widgetButtonsMapping.keyAt(i); if (fwVersion.compareTo(new Version("1.0.2.20")) >= 0) {
findViewById(widgetButtonId).setEnabled(false); 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 @Override

View File

@ -35,6 +35,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -42,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; 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.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Version; 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); private static final Logger LOG = LoggerFactory.getLogger(QHybridCoordinator.class);
@NonNull @NonNull
@ -89,8 +91,13 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supportsActivityDataFetching() { public boolean supportsActivityDataFetching() {
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
return connectedDevice != null && connectedDevice.getType() == DeviceType.FOSSILQHYBRID && connectedDevice.getState() == GBDevice.State.INITIALIZED; for(GBDevice device : devices){
if(isFossilHybrid(device) && device.getState() == GBDevice.State.INITIALIZED){
return true;
}
}
return false;
} }
@Override @Override
@ -129,11 +136,14 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
} }
private boolean supportsAlarmConfiguration() { private boolean supportsAlarmConfiguration() {
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if(connectedDevice == null || connectedDevice.getType() != DeviceType.FOSSILQHYBRID || connectedDevice.getState() != GBDevice.State.INITIALIZED){ LOG.debug("devices count: " + devices.size());
return false; for(GBDevice device : devices){
if(isFossilHybrid(device) && device.getState() == GBDevice.State.INITIALIZED){
return true;
}
} }
return true; return false;
} }
@Override @Override
@ -268,22 +278,34 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
} }
private boolean isHybridHR() { private boolean isHybridHR() {
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (connectedDevice != null) { for(GBDevice device : devices){
return connectedDevice.getName().startsWith("Hybrid HR"); if(isFossilHybrid(device) && device.getName().startsWith("Hybrid HR")){
return true;
}
} }
return false; return false;
} }
private Version getFirmwareVersion() { private Version getFirmwareVersion() {
String firmware = GBApplication.app().getDeviceManager().getSelectedDevice().getFirmwareVersion(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (firmware != null) { for(GBDevice device : devices){
Matcher matcher = Pattern.compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+").matcher(firmware); // DN1.0.2.19r.v5 if(isFossilHybrid(device)){
if (matcher.find()) { String firmware = device.getFirmwareVersion();
firmware = matcher.group(0); if (firmware != null) {
return new Version(firmware); 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; 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.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; 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.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support;
public class UM25Coordinator extends AbstractDeviceCoordinator { public class UM25Coordinator extends AbstractBLEDeviceCoordinator {
@Override @Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { 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.R;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; 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.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; 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_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_TX_HM10 = "0000ffe1-0000-1000-8000-00805f9b34fb";
public final static String UUID_CHARACTERISTIC_SERIAL_RX_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.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
public class BluetoothConnectReceiver extends BroadcastReceiver { public class BluetoothConnectReceiver extends BroadcastReceiver {
@ -46,14 +50,30 @@ public class BluetoothConnectReceiver extends BroadcastReceiver {
} }
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 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(); GBDevice gbDevice = getKnownDeviceByAddressOrNull(device.getAddress());
if (gbDevice != null) { if(gbDevice == null){
if (device.getAddress().equals(gbDevice.getAddress()) && gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { LOG.info("connected device {} unknown", device.getAddress());
LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); return;
GBApplication.deviceService().connect(); }
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)) { if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
return; return;
} }
GBDevice gbDevice = service.getGBDevice();
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (gbDevice == null || device == null) { if (device == null) {
return; return;
} }
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); GBDevice gbDevice = null;
try { try {
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_NONE) { gbDevice = service.getDeviceByAddress(device.getAddress());
LOG.info("Aborting unwanted pairing request");
abortBroadcast(); 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) { } catch (DeviceCommunicationService.DeviceNotFoundException e) {
LOG.warn("Could not abort pairing request process"); e.printStackTrace();
} }
} }
} }

View File

@ -767,9 +767,9 @@ public class NotificationListener extends NotificationListenerService {
notificationsActive.removeAll(notificationsToRemove); notificationsActive.removeAll(notificationsToRemove);
// Send notification remove request to device // Send notification remove request to device
GBDevice connectedDevice = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (connectedDevice != null) { for(GBDevice device : devices){
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(connectedDevice.getAddress())); Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
if (prefs.getBoolean("autoremove_notifications", true)) { if (prefs.getBoolean("autoremove_notifications", true)) {
for (int id : notificationsToRemove) { for (int id : notificationsToRemove) {
LOG.info("Notification " + id + " removed, will ask device to delete it"); LOG.info("Notification " + id + " removed, will ask device to delete it");

View File

@ -118,6 +118,13 @@ public class GBDeviceService implements DeviceService {
invokeService(intent); invokeService(intent);
} }
@Override
public void disconnect(@Nullable GBDevice device) {
Intent intent = createIntent().setAction(ACTION_DISCONNECT)
.putExtra(GBDevice.EXTRA_DEVICE, device);
invokeService(intent);
}
@Override @Override
public void disconnect() { public void disconnect() {
Intent intent = createIntent().setAction(ACTION_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 connect(@Nullable GBDevice device, boolean firstTime);
void disconnect(@Nullable GBDevice device);
void disconnect(); void disconnect();
void quit(); void quit();

View File

@ -44,9 +44,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; 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; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WORLD_CLOCKS;
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener { 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); private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
@SuppressLint("StaticFieldLeak") // only used for test cases @SuppressLint("StaticFieldLeak") // only used for test cases
private static DeviceSupportFactory DEVICE_SUPPORT_FACTORY = null; private static DeviceSupportFactory DEVICE_SUPPORT_FACTORY = null;
@ -196,9 +300,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private boolean mStarted = false; private boolean mStarted = false;
private DeviceSupportFactory mFactory; private DeviceSupportFactory mFactory;
private GBDevice mGBDevice = null; private final ArrayList<DeviceStruct> deviceStructs = new ArrayList<>(1);
private DeviceSupport mDeviceSupport;
private DeviceCoordinator mCoordinator = null;
private PhoneCallReceiver mPhoneCallReceiver = null; private PhoneCallReceiver mPhoneCallReceiver = null;
private SMSReceiver mSMSReceiver = null; private SMSReceiver mSMSReceiver = null;
@ -236,6 +338,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
* *
* @param factory * @param factory
*/ */
@SuppressWarnings("JavaDoc")
public static void setDeviceSupportFactory(DeviceSupportFactory factory) { public static void setDeviceSupportFactory(DeviceSupportFactory factory) {
DEVICE_SUPPORT_FACTORY = factory; DEVICE_SUPPORT_FACTORY = factory;
} }
@ -248,20 +351,45 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); 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); GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (mGBDevice != null && mGBDevice.equals(device)) {
mGBDevice = device; // create a new instance of the changed devices coordinator, in case it's capabilities changed
mCoordinator = DeviceHelper.getInstance().getCoordinator(device); DeviceStruct cachedStruct = getDeviceStructOrNull(device);
boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized()); if(cachedStruct != null) {
setReceiversEnableState(enableReceivers, mGBDevice.isInitialized(), mCoordinator); cachedStruct.setDevice(device);
} else { DeviceCoordinator newCoordinator = DeviceHelper.getInstance().getCoordinator(device);
LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + 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 @Override
public void onCreate() { public void onCreate() {
LOG.debug("DeviceCommunicationService is being created"); 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)); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
mFactory = getDeviceSupportFactory(); mFactory = getDeviceSupportFactory();
mBlueToothConnectReceiver = new BluetoothConnectReceiver(this);
registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
if (hasPrefs()) { if (hasPrefs()) {
getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this); getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
} }
@ -306,14 +437,15 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
return START_NOT_STICKY; 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 // trying to send notification without valid Bluetooth connection
if (mGBDevice != null) { if (mGBDevice != null) {
// at least send back the current device state // at least send back the current device state
mGBDevice.sendDeviceUpdateIntent(this); mGBDevice.sendDeviceUpdateIntent(this);
} }
return START_STICKY; return START_STICKY;
} }*/
} }
// when we get past this, we should have valid mDeviceSupport and mGBDevice instances // 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(); btDeviceAddress = gbDevice.getAddress();
} }
if(gbDevice == null){
return START_NOT_STICKY;
}
boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT; boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT;
if (prefs != null && prefs.getPreferences() != null) { if (prefs != null && prefs.getPreferences() != null) {
prefs.getPreferences().edit().putString("last_device_address", btDeviceAddress).apply(); prefs.getPreferences().edit().putString("last_device_address", btDeviceAddress).apply();
autoReconnect = getGBPrefs().getAutoReconnect(); autoReconnect = getGBPrefs().getAutoReconnect(gbDevice);
} }
if (gbDevice != null && !isConnecting() && !isConnected()) { DeviceStruct registeredStruct = getDeviceStructOrNull(gbDevice);
setDeviceSupport(null); if(registeredStruct != null){
try { boolean deviceAlreadyConnected = isDeviceConnecting(registeredStruct.getDevice()) || isDeviceConnected(registeredStruct.getDevice());
DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice); if(deviceAlreadyConnected){
if (deviceSupport != null) { break;
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);
} }
} else if (mGBDevice != null) { try {
// send an update at least removeDeviceSupport(gbDevice);
mGBDevice.sendDeviceUpdateIntent(this); } 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; break;
default: default:
if (mDeviceSupport == null || mGBDevice == null) { GBDevice targetedDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
LOG.warn("device support:" + mDeviceSupport + ", device: " + mGBDevice + ", aborting"); ArrayList<GBDevice> targetedDevices = new ArrayList<>();
} else { if(targetedDevice != null){
handleAction(intent, action, prefs); 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; break;
} }
@ -383,21 +547,35 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
* @param text original text * @param text original text
* @return 'text' or a new String without non supported chars like emoticons, etc. * @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) if (text == null || text.length() == 0)
return text; return text;
text = mDeviceSupport.customStringFilter(text); text = getDeviceSupport(device).customStringFilter(text);
if (!mCoordinator.supportsUnicodeEmojis()) { if (!getDeviceCoordinator(device).supportsUnicodeEmojis()) {
return EmojiConverter.convertUnicodeEmojiToAscii(text, getApplicationContext()); return EmojiConverter.convertUnicodeEmojiToAscii(text, getApplicationContext());
} }
return text; return text;
} }
private void handleAction(Intent intent, String action, Prefs prefs) { private DeviceCoordinator getDeviceCoordinator(GBDevice device) throws DeviceNotFoundException {
Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress())); 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); boolean transliterate = devicePrefs.getBoolean(PREF_TRANSLITERATION_ENABLED, false);
if (transliterate) { if (transliterate) {
@ -410,16 +588,16 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
switch (action) { switch (action) {
case ACTION_REQUEST_DEVICEINFO: case ACTION_REQUEST_DEVICEINFO:
mGBDevice.sendDeviceUpdateIntent(this); device.sendDeviceUpdateIntent(this);
break; break;
case ACTION_NOTIFICATION: { case ACTION_NOTIFICATION: {
int desiredId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); int desiredId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
NotificationSpec notificationSpec = new NotificationSpec(desiredId); NotificationSpec notificationSpec = new NotificationSpec(desiredId);
notificationSpec.phoneNumber = intent.getStringExtra(EXTRA_NOTIFICATION_PHONENUMBER); notificationSpec.phoneNumber = intent.getStringExtra(EXTRA_NOTIFICATION_PHONENUMBER);
notificationSpec.sender = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SENDER)); notificationSpec.sender = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SENDER), device);
notificationSpec.subject = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT)); notificationSpec.subject = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT), device);
notificationSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE)); notificationSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE), device);
notificationSpec.body = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_BODY)); notificationSpec.body = sanitizeNotifText(intent.getStringExtra(EXTRA_NOTIFICATION_BODY), device);
notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME);
notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE); notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE);
notificationSpec.attachedActions = (ArrayList<NotificationSpec.Action>) intent.getSerializableExtra(EXTRA_NOTIFICATION_ACTIONS); 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]); notificationSpec.cannedReplies = replies.toArray(new String[0]);
} }
mDeviceSupport.onNotification(notificationSpec); deviceSupport.onNotification(notificationSpec);
break; break;
} }
case ACTION_DELETE_NOTIFICATION: { case ACTION_DELETE_NOTIFICATION: {
mDeviceSupport.onDeleteNotification(intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)); deviceSupport.onDeleteNotification(intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1));
break; break;
} }
case ACTION_ADD_CALENDAREVENT: { 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.type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1);
calendarEventSpec.timestamp = intent.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1); calendarEventSpec.timestamp = intent.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1);
calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1); calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1);
calendarEventSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE)); calendarEventSpec.title = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE), device);
calendarEventSpec.description = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION)); calendarEventSpec.description = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION), device);
calendarEventSpec.location = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_LOCATION)); calendarEventSpec.location = sanitizeNotifText(intent.getStringExtra(EXTRA_CALENDAREVENT_LOCATION), device);
mDeviceSupport.onAddCalendarEvent(calendarEventSpec); deviceSupport.onAddCalendarEvent(calendarEventSpec);
break; break;
} }
case ACTION_DELETE_CALENDAREVENT: { case ACTION_DELETE_CALENDAREVENT: {
long id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1); long id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1);
byte type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1); byte type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1);
mDeviceSupport.onDeleteCalendarEvent(type, id); deviceSupport.onDeleteCalendarEvent(type, id);
break; break;
} }
case ACTION_RESET: { case ACTION_RESET: {
int flags = intent.getIntExtra(EXTRA_RESET_FLAGS, 0); int flags = intent.getIntExtra(EXTRA_RESET_FLAGS, 0);
mDeviceSupport.onReset(flags); deviceSupport.onReset(flags);
break; break;
} }
case ACTION_HEARTRATE_TEST: { case ACTION_HEARTRATE_TEST: {
mDeviceSupport.onHeartRateTest(); deviceSupport.onHeartRateTest();
break; break;
} }
case ACTION_FETCH_RECORDED_DATA: { case ACTION_FETCH_RECORDED_DATA: {
int dataTypes = intent.getIntExtra(EXTRA_RECORDED_DATA_TYPES, 0); if(!getDeviceCoordinator(device).supportsActivityDataFetching()){
mDeviceSupport.onFetchRecordedData(dataTypes); break;
break;
}
case ACTION_DISCONNECT: {
mDeviceSupport.dispose();
if (mGBDevice != null) {
mGBDevice.setState(GBDevice.State.NOT_CONNECTED);
mGBDevice.sendDeviceUpdateIntent(this);
} }
setReceiversEnableState(false, false, null); int dataTypes = intent.getIntExtra(EXTRA_RECORDED_DATA_TYPES, 0);
mGBDevice = null; deviceSupport.onFetchRecordedData(dataTypes);
mDeviceSupport = null;
mCoordinator = null;
break; 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: { case ACTION_FIND_DEVICE: {
boolean start = intent.getBooleanExtra(EXTRA_FIND_START, false); boolean start = intent.getBooleanExtra(EXTRA_FIND_START, false);
mDeviceSupport.onFindDevice(start); deviceSupport.onFindDevice(start);
break; break;
} }
case ACTION_SET_CONSTANT_VIBRATION: { case ACTION_SET_CONSTANT_VIBRATION: {
int intensity = intent.getIntExtra(EXTRA_VIBRATION_INTENSITY, 0); int intensity = intent.getIntExtra(EXTRA_VIBRATION_INTENSITY, 0);
mDeviceSupport.onSetConstantVibration(intensity); deviceSupport.onSetConstantVibration(intensity);
break; break;
} }
case ACTION_CALLSTATE: case ACTION_CALLSTATE:
CallSpec callSpec = new CallSpec(); CallSpec callSpec = new CallSpec();
callSpec.command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED); callSpec.command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED);
callSpec.number = intent.getStringExtra(EXTRA_CALL_PHONENUMBER); 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); callSpec.dndSuppressed = intent.getIntExtra(EXTRA_CALL_DNDSUPPRESSED, 0);
mDeviceSupport.onSetCallState(callSpec); deviceSupport.onSetCallState(callSpec);
break; break;
case ACTION_SETCANNEDMESSAGES: case ACTION_SETCANNEDMESSAGES:
int type = intent.getIntExtra(EXTRA_CANNEDMESSAGES_TYPE, -1); int type = intent.getIntExtra(EXTRA_CANNEDMESSAGES_TYPE, -1);
@ -525,24 +704,24 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec(); CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec();
cannedMessagesSpec.type = type; cannedMessagesSpec.type = type;
cannedMessagesSpec.cannedMessages = cannedMessages; cannedMessagesSpec.cannedMessages = cannedMessages;
mDeviceSupport.onSetCannedMessages(cannedMessagesSpec); deviceSupport.onSetCannedMessages(cannedMessagesSpec);
break; break;
case ACTION_SETTIME: case ACTION_SETTIME:
mDeviceSupport.onSetTime(); deviceSupport.onSetTime();
break; break;
case ACTION_SETMUSICINFO: case ACTION_SETMUSICINFO:
MusicSpec musicSpec = new MusicSpec(); MusicSpec musicSpec = new MusicSpec();
musicSpec.artist = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ARTIST)); musicSpec.artist = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ARTIST), device);
musicSpec.album = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ALBUM)); musicSpec.album = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_ALBUM), device);
musicSpec.track = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_TRACK)); musicSpec.track = sanitizeNotifText(intent.getStringExtra(EXTRA_MUSIC_TRACK), device);
musicSpec.duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0); musicSpec.duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0);
musicSpec.trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0); musicSpec.trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0);
musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0); musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0);
mDeviceSupport.onSetMusicInfo(musicSpec); deviceSupport.onSetMusicInfo(musicSpec);
break; break;
case ACTION_SET_PHONE_VOLUME: case ACTION_SET_PHONE_VOLUME:
float phoneVolume = intent.getFloatExtra(EXTRA_PHONE_VOLUME, 0); float phoneVolume = intent.getFloatExtra(EXTRA_PHONE_VOLUME, 0);
mDeviceSupport.onSetPhoneVolume(phoneVolume); deviceSupport.onSetPhoneVolume(phoneVolume);
break; break;
case ACTION_SETMUSICSTATE: case ACTION_SETMUSICSTATE:
MusicStateSpec stateSpec = new MusicStateSpec(); MusicStateSpec stateSpec = new MusicStateSpec();
@ -551,23 +730,23 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
stateSpec.position = intent.getIntExtra(EXTRA_MUSIC_POSITION, 0); stateSpec.position = intent.getIntExtra(EXTRA_MUSIC_POSITION, 0);
stateSpec.playRate = intent.getIntExtra(EXTRA_MUSIC_RATE, 0); stateSpec.playRate = intent.getIntExtra(EXTRA_MUSIC_RATE, 0);
stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte) 0); stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte) 0);
mDeviceSupport.onSetMusicState(stateSpec); deviceSupport.onSetMusicState(stateSpec);
break; break;
case ACTION_REQUEST_APPINFO: case ACTION_REQUEST_APPINFO:
mDeviceSupport.onAppInfoReq(); deviceSupport.onAppInfoReq();
break; break;
case ACTION_REQUEST_SCREENSHOT: case ACTION_REQUEST_SCREENSHOT:
mDeviceSupport.onScreenshotReq(); deviceSupport.onScreenshotReq();
break; break;
case ACTION_STARTAPP: { case ACTION_STARTAPP: {
UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID);
boolean start = intent.getBooleanExtra(EXTRA_APP_START, true); boolean start = intent.getBooleanExtra(EXTRA_APP_START, true);
mDeviceSupport.onAppStart(uuid, start); deviceSupport.onAppStart(uuid, start);
break; break;
} }
case ACTION_DELETEAPP: { case ACTION_DELETEAPP: {
UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID);
mDeviceSupport.onAppDelete(uuid); deviceSupport.onAppDelete(uuid);
break; break;
} }
case ACTION_APP_CONFIGURE: { case ACTION_APP_CONFIGURE: {
@ -577,87 +756,87 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
if (intent.hasExtra(EXTRA_APP_CONFIG_ID)) { if (intent.hasExtra(EXTRA_APP_CONFIG_ID)) {
id = intent.getIntExtra(EXTRA_APP_CONFIG_ID, 0); id = intent.getIntExtra(EXTRA_APP_CONFIG_ID, 0);
} }
mDeviceSupport.onAppConfiguration(uuid, config, id); deviceSupport.onAppConfiguration(uuid, config, id);
break; break;
} }
case ACTION_APP_REORDER: { case ACTION_APP_REORDER: {
UUID[] uuids = (UUID[]) intent.getSerializableExtra(EXTRA_APP_UUID); UUID[] uuids = (UUID[]) intent.getSerializableExtra(EXTRA_APP_UUID);
mDeviceSupport.onAppReorder(uuids); deviceSupport.onAppReorder(uuids);
break; break;
} }
case ACTION_INSTALL: case ACTION_INSTALL:
Uri uri = intent.getParcelableExtra(EXTRA_URI); Uri uri = intent.getParcelableExtra(EXTRA_URI);
if (uri != null) { if (uri != null) {
LOG.info("will try to install app/fw"); LOG.info("will try to install app/fw");
mDeviceSupport.onInstallApp(uri); deviceSupport.onInstallApp(uri);
} }
break; break;
case ACTION_SET_ALARMS: case ACTION_SET_ALARMS:
ArrayList<? extends Alarm> alarms = (ArrayList<? extends Alarm>) intent.getSerializableExtra(EXTRA_ALARMS); ArrayList<? extends Alarm> alarms = (ArrayList<? extends Alarm>) intent.getSerializableExtra(EXTRA_ALARMS);
mDeviceSupport.onSetAlarms(alarms); deviceSupport.onSetAlarms(alarms);
break; break;
case ACTION_SET_REMINDERS: case ACTION_SET_REMINDERS:
ArrayList<? extends Reminder> reminders = (ArrayList<? extends Reminder>) intent.getSerializableExtra(EXTRA_REMINDERS); ArrayList<? extends Reminder> reminders = (ArrayList<? extends Reminder>) intent.getSerializableExtra(EXTRA_REMINDERS);
mDeviceSupport.onSetReminders(reminders); deviceSupport.onSetReminders(reminders);
break; break;
case ACTION_SET_WORLD_CLOCKS: case ACTION_SET_WORLD_CLOCKS:
ArrayList<? extends WorldClock> clocks = (ArrayList<? extends WorldClock>) intent.getSerializableExtra(EXTRA_WORLD_CLOCKS); ArrayList<? extends WorldClock> clocks = (ArrayList<? extends WorldClock>) intent.getSerializableExtra(EXTRA_WORLD_CLOCKS);
mDeviceSupport.onSetWorldClocks(clocks); deviceSupport.onSetWorldClocks(clocks);
break; break;
case ACTION_ENABLE_REALTIME_STEPS: { case ACTION_ENABLE_REALTIME_STEPS: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeSteps(enable); deviceSupport.onEnableRealtimeSteps(enable);
break; break;
} }
case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: { case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableHeartRateSleepSupport(enable); deviceSupport.onEnableHeartRateSleepSupport(enable);
break; break;
} }
case ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL: { case ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL: {
int seconds = intent.getIntExtra(EXTRA_INTERVAL_SECONDS, 0); int seconds = intent.getIntExtra(EXTRA_INTERVAL_SECONDS, 0);
mDeviceSupport.onSetHeartRateMeasurementInterval(seconds); deviceSupport.onSetHeartRateMeasurementInterval(seconds);
break; break;
} }
case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: { case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable); deviceSupport.onEnableRealtimeHeartRateMeasurement(enable);
break; break;
} }
case ACTION_SEND_CONFIGURATION: { case ACTION_SEND_CONFIGURATION: {
String config = intent.getStringExtra(EXTRA_CONFIG); String config = intent.getStringExtra(EXTRA_CONFIG);
mDeviceSupport.onSendConfiguration(config); deviceSupport.onSendConfiguration(config);
break; break;
} }
case ACTION_READ_CONFIGURATION: { case ACTION_READ_CONFIGURATION: {
String config = intent.getStringExtra(EXTRA_CONFIG); String config = intent.getStringExtra(EXTRA_CONFIG);
mDeviceSupport.onReadConfiguration(config); deviceSupport.onReadConfiguration(config);
break; break;
} }
case ACTION_TEST_NEW_FUNCTION: { case ACTION_TEST_NEW_FUNCTION: {
mDeviceSupport.onTestNewFunction(); deviceSupport.onTestNewFunction();
break; break;
} }
case ACTION_SEND_WEATHER: { case ACTION_SEND_WEATHER: {
WeatherSpec weatherSpec = intent.getParcelableExtra(EXTRA_WEATHER); WeatherSpec weatherSpec = intent.getParcelableExtra(EXTRA_WEATHER);
if (weatherSpec != null) { if (weatherSpec != null) {
mDeviceSupport.onSendWeather(weatherSpec); deviceSupport.onSendWeather(weatherSpec);
} }
break; break;
} }
case ACTION_SET_LED_COLOR: case ACTION_SET_LED_COLOR:
int color = intent.getIntExtra(EXTRA_LED_COLOR, 0); int color = intent.getIntExtra(EXTRA_LED_COLOR, 0);
if (color != 0) { if (color != 0) {
mDeviceSupport.onSetLedColor(color); deviceSupport.onSetLedColor(color);
} }
break; break;
case ACTION_POWER_OFF: case ACTION_POWER_OFF:
mDeviceSupport.onPowerOff(); deviceSupport.onPowerOff();
break; break;
case ACTION_SET_FM_FREQUENCY: case ACTION_SET_FM_FREQUENCY:
float frequency = intent.getFloatExtra(EXTRA_FM_FREQUENCY, -1); float frequency = intent.getFloatExtra(EXTRA_FM_FREQUENCY, -1);
if (frequency != -1) { if (frequency != -1) {
mDeviceSupport.onSetFmFrequency(frequency); deviceSupport.onSetFmFrequency(frequency);
} }
break; break;
case ACTION_SET_GPS_LOCATION: case ACTION_SET_GPS_LOCATION:
@ -671,18 +850,79 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
* Disposes the current DeviceSupport instance (if any) and sets a new device support instance * Disposes the current DeviceSupport instance (if any) and sets a new device support instance
* (if not null). * (if not null).
* *
* @param deviceSupport * @param deviceSupport deviceSupport to reokace/add
*/ */
private void setDeviceSupport(@Nullable DeviceSupport deviceSupport) { private void setDeviceSupport(GBDevice device, DeviceSupport deviceSupport) throws DeviceNotFoundException {
if (deviceSupport != mDeviceSupport && mDeviceSupport != null) { DeviceStruct deviceStruct = getDeviceStruct(device);
mDeviceSupport.dispose(); DeviceSupport cachedDeviceSupport = deviceStruct.getDeviceSupport();
mDeviceSupport = null; if (deviceSupport != cachedDeviceSupport && cachedDeviceSupport != null) {
mGBDevice = null; cachedDeviceSupport.dispose();
mCoordinator = null; }
deviceStruct.setDeviceSupport(deviceSupport);
}
private void removeDeviceSupport(GBDevice device) throws DeviceNotFoundException {
DeviceStruct struct = getDeviceStruct(device);
if(struct.getDeviceSupport() != null){
struct.getDeviceSupport().dispose();
} }
mDeviceSupport = deviceSupport; struct.setDeviceSupport(null);
mGBDevice = mDeviceSupport != null ? mDeviceSupport.getDevice() : null; }
mCoordinator = mGBDevice != null ? DeviceHelper.getInstance().getCoordinator(mGBDevice) : 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() { private void start() {
@ -697,30 +937,49 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
return mStarted; return mStarted;
} }
private boolean isConnected() { private boolean isDeviceConnected(GBDevice device) {
return mGBDevice != null && mGBDevice.isConnected(); for(DeviceStruct struct : deviceStructs){
if(struct.getDevice().equals(device) ){
return struct.getDevice().isConnected();
}
}
return false;
} }
private boolean isConnecting() { private boolean isDeviceConnecting(GBDevice device) {
return mGBDevice != null && mGBDevice.isConnecting(); for(DeviceStruct struct : deviceStructs){
if(struct.getDevice().equals(device) ){
return struct.getDevice().isConnecting();
}
}
return false;
} }
private boolean isInitialized() { private boolean isDeviceInitialized(GBDevice device) {
return mGBDevice != null && mGBDevice.isInitialized(); 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); 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 (mCalendarReceiver == null && getPrefs().getBoolean("enable_calendar_sync", true)) {
if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) { if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) {
IntentFilter calendarIntentFilter = new IntentFilter(); IntentFilter calendarIntentFilter = new IntentFilter();
calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED"); calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED");
calendarIntentFilter.addDataScheme("content"); calendarIntentFilter.addDataScheme("content");
calendarIntentFilter.addDataAuthority("com.android.calendar", null); calendarIntentFilter.addDataAuthority("com.android.calendar", null);
mCalendarReceiver = new CalendarReceiver(mGBDevice); mCalendarReceiver = new CalendarReceiver(null);
registerReceiver(mCalendarReceiver, calendarIntentFilter); registerReceiver(mCalendarReceiver, calendarIntentFilter);
} }
} }
@ -756,7 +1015,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mPebbleReceiver = new PebbleReceiver(); mPebbleReceiver = new PebbleReceiver();
registerReceiver(mPebbleReceiver, new IntentFilter("com.getpebble.action.SEND_NOTIFICATION")); registerReceiver(mPebbleReceiver, new IntentFilter("com.getpebble.action.SEND_NOTIFICATION"));
} }
if (mMusicPlaybackReceiver == null && coordinator != null && coordinator.supportsMusicInfo()) { if (mMusicPlaybackReceiver == null && features.supportsMusicInfo()) {
mMusicPlaybackReceiver = new MusicPlaybackReceiver(); mMusicPlaybackReceiver = new MusicPlaybackReceiver();
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
for (String action : mMusicActions) { for (String action : mMusicActions) {
@ -771,10 +1030,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
filter.addAction("android.intent.action.TIMEZONE_CHANGED"); filter.addAction("android.intent.action.TIMEZONE_CHANGED");
registerReceiver(mTimeChangeReceiver, filter); registerReceiver(mTimeChangeReceiver, filter);
} }
if (mBlueToothConnectReceiver == null) {
mBlueToothConnectReceiver = new BluetoothConnectReceiver(this);
registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
}
if (mBlueToothPairingRequestReceiver == null) { if (mBlueToothPairingRequestReceiver == null) {
mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this); mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this);
registerReceiver(mBlueToothPairingRequestReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)); registerReceiver(mBlueToothPairingRequestReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST));
@ -790,7 +1045,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
} }
// Weather receivers // Weather receivers
if ( coordinator != null && coordinator.supportsWeather()) { if (features.supportsWeather()) {
if (GBApplication.isRunningOreoOrLater()) { if (GBApplication.isRunningOreoOrLater()) {
if (mLineageOsWeatherReceiver == null) { if (mLineageOsWeatherReceiver == null) {
mLineageOsWeatherReceiver = new LineageOsWeatherReceiver(); mLineageOsWeatherReceiver = new LineageOsWeatherReceiver();
@ -818,7 +1073,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
} }
if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) && if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) &&
coordinator != null && coordinator.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) { features.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) {
mGBAutoFetchReceiver = new GBAutoFetchReceiver(); mGBAutoFetchReceiver = new GBAutoFetchReceiver();
registerReceiver(mGBAutoFetchReceiver, new IntentFilter("android.intent.action.USER_PRESENT")); registerReceiver(mGBAutoFetchReceiver, new IntentFilter("android.intent.action.USER_PRESENT"));
} }
@ -847,10 +1102,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
unregisterReceiver(mTimeChangeReceiver); unregisterReceiver(mTimeChangeReceiver);
mTimeChangeReceiver = null; mTimeChangeReceiver = null;
} }
if (mBlueToothConnectReceiver != null) {
unregisterReceiver(mBlueToothConnectReceiver);
mBlueToothConnectReceiver = null;
}
if (mBlueToothPairingRequestReceiver != null) { if (mBlueToothPairingRequestReceiver != null) {
unregisterReceiver(mBlueToothPairingRequestReceiver); unregisterReceiver(mBlueToothPairingRequestReceiver);
@ -900,7 +1151,15 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
setReceiversEnableState(false, false, null); // disable BroadcastReceivers 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 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 @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (GBPrefs.AUTO_RECONNECT.equals(key)) { if (GBPrefs.DEVICE_AUTO_RECONNECT.equals(key)) {
boolean autoReconnect = getGBPrefs().getAutoReconnect(); for(DeviceStruct deviceStruct : deviceStructs){
if (mDeviceSupport != null) { boolean autoReconnect = getGBPrefs().getAutoReconnect(deviceStruct.getDevice());
mDeviceSupport.setAutoReconnect(autoReconnect); deviceStruct.getDeviceSupport().setAutoReconnect(autoReconnect);
} }
} }
if (GBPrefs.CHART_MAX_HEART_RATE.equals(key) || GBPrefs.CHART_MIN_HEART_RATE.equals(key)) { 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(); return GBApplication.getGBPrefs();
} }
public GBDevice getGBDevice() { public GBDevice[] getGBDevices() {
return mGBDevice; 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 { private DeviceSupport createBTDeviceSupport(GBDevice gbDevice) throws GBException {
if (mBtAdapter != null && mBtAdapter.isEnabled()) { if (mBtAdapter != null && mBtAdapter.isEnabled()) {
DeviceSupport deviceSupport = null;
try { try {
switch (gbDevice.getType()) { DeviceSupport deviceSupport = createServiceDeviceSupport(gbDevice);
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;
}
if (deviceSupport != null) { if (deviceSupport != null) {
deviceSupport.setContext(gbDevice, mBtAdapter, mContext); deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
return deviceSupport; return deviceSupport;
@ -416,7 +338,7 @@ public class DeviceSupportFactory {
private DeviceSupport createTCPDeviceSupport(GBDevice gbDevice) throws GBException { private DeviceSupport createTCPDeviceSupport(GBDevice gbDevice) throws GBException {
try { 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); deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
return deviceSupport; return deviceSupport;
} catch (Exception e) { } catch (Exception e) {

View File

@ -27,6 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.UUID; import java.util.UUID;
@ -61,9 +62,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
private String lastNotificationKind; private String lastNotificationKind;
private final EnumSet<Flags> flags; private final EnumSet<Flags> flags;
public ServiceDeviceSupport(DeviceSupport delegate, EnumSet<Flags> flags) { public ServiceDeviceSupport(DeviceSupport delegate, Flags... flags) {
this.delegate = delegate; 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 @Override

View File

@ -17,6 +17,7 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip; package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -69,8 +70,8 @@ public class AmazfitBipLiteFirmwareInfo extends HuamiFirmwareInfo {
if (searchString32BitAligned(bytes, "Amazfit Bip Lite")) { if (searchString32BitAligned(bytes, "Amazfit Bip Lite")) {
return HuamiFirmwareType.FIRMWARE; return HuamiFirmwareType.FIRMWARE;
} }
GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
if (device != null) { for(GBDevice device : devices){
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
if (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_RELAX_FIRMWARE_CHECKS, false)) { if (prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_RELAX_FIRMWARE_CHECKS, false)) {
if (searchString32BitAligned(bytes, "Amazfit Bip")) { if (searchString32BitAligned(bytes, "Amazfit Bip")) {

View File

@ -17,6 +17,7 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips; package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -66,10 +67,8 @@ public class AmazfitBipSFirmwareInfo extends HuamiFirmwareInfo {
@Override @Override
protected HuamiFirmwareType determineFirmwareType(byte[] bytes) { protected HuamiFirmwareType determineFirmwareType(byte[] bytes) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); for (GBDevice device : devices) {
if (device != null) {
if (device.getFirmwareVersion().startsWith("2.")) { 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 //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)) { if (ArrayUtils.equals(bytes, MiBand4FirmwareInfo.FW_HEADER, MiBand4FirmwareInfo.FW_HEADER_OFFSET)) {

View File

@ -242,7 +242,7 @@ class PebbleIoThread extends GBDeviceIoThread {
public void run() { public void run() {
mIsConnected = connect(); mIsConnected = connect();
if (!mIsConnected) { if (!mIsConnected) {
if (GBApplication.getGBPrefs().getAutoReconnect() && !mQuit) { if (GBApplication.getGBPrefs().getAutoReconnect(getDevice()) && !mQuit) {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
gbDevice.sendDeviceUpdateIntent(getContext()); gbDevice.sendDeviceUpdateIntent(getContext());
} }
@ -406,7 +406,7 @@ class PebbleIoThread extends GBDeviceIoThread {
enablePebbleKitSupport(false); enablePebbleKitSupport(false);
if (mQuit || !GBApplication.getGBPrefs().getAutoReconnect()) { if (mQuit || !GBApplication.getGBPrefs().getAutoReconnect(getDevice())) {
gbDevice.setState(GBDevice.State.NOT_CONNECTED); gbDevice.setState(GBDevice.State.NOT_CONNECTED);
} else { } else {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);

View File

@ -69,7 +69,7 @@ public class QC35BaseSupport extends AbstractSerialDeviceSupport {
public boolean connect() { public boolean connect() {
getDeviceProtocol(); getDeviceProtocol();
getDeviceIOThread().start(); getDeviceIOThread().start();
getDevice().setBatteryThresholdPercent((short)15); getDevice().setBatteryThresholdPercent((short)25);
return true; 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.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@ -57,6 +58,7 @@ public class QC35Protocol extends GBDeviceProtocol {
if(third == 0x03){ if(third == 0x03){
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo(); GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
batteryInfo.level = data[0]; batteryInfo.level = data[0];
batteryInfo.state = BatteryState.BATTERY_NORMAL;
events.add(batteryInfo); events.add(batteryInfo);
} }
} }

View File

@ -58,24 +58,31 @@ public class AutoConnectIntervalReceiver extends BroadcastReceiver {
return; return;
} }
GBDevice gbDevice = service.getGBDevice(); GBDevice[] devices = service.getGBDevices();
if (gbDevice == null) { if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)){
return; boolean scheduleAutoConnect = false;
} boolean allDevicesInitialized = true;
for(GBDevice device : devices){
if (action.equals(DeviceManager.ACTION_DEVICES_CHANGED)) { if(!device.isInitialized()){
if (gbDevice.isInitialized()) { allDevicesInitialized = false;
LOG.info("will reset connection delay, device is initialized!"); }else if(device.getState() == GBDevice.State.WAITING_FOR_RECONNECT){
mDelay = 4; 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(); scheduleReconnect();
} }
} }else if (action.equals("GB_RECONNECT")){
else if (action.equals("GB_RECONNECT")) { for(GBDevice device : devices){
if (gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { if(device.getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
LOG.info("Will re-connect to " + gbDevice.getAddress() + "(" + gbDevice.getName() + ")"); LOG.info("Will re-connect to " + device.getAddress() + "(" + device.getName() + ")");
GBApplication.deviceService().connect(); GBApplication.deviceService().connect(device);
}
} }
} }
} }

View File

@ -180,7 +180,7 @@ public class BondingUtil {
public static void connectThenComplete(BondingInterface bondingInterface, GBDevice device) { 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); 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 // 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); GBApplication.deviceService().connect(device, true);
bondingInterface.onBondingComplete(true); bondingInterface.onBondingComplete(true);
} }

View File

@ -29,6 +29,8 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.Html;
import android.text.SpannableString;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -44,6 +46,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment; import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
@ -145,44 +148,102 @@ public class GB {
return pendingIntent; return pendingIntent;
} }
public static Notification createNotification(GBDevice device, Context context) { public static Notification createNotification(List<GBDevice> devices, 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();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setContentTitle(deviceName) if(devices.size() == 0){
.setTicker(deviceName + " - " + text) builder.setContentTitle(context.getString(R.string.info_no_devices_connected))
.setContentText(text) .setSmallIcon(R.drawable.ic_notification_disconnected)
.setSmallIcon(connected ? device.getNotificationIconConnected() : device.getNotificationIconDisconnected()) .setContentIntent(getContentIntent(context))
.setContentIntent(getContentIntent(context)) .setShowWhen(false)
.setShowWhen(false) .setOngoing(true);
.setOngoing(true);
if (!GBApplication.isRunningTwelveOrLater()) { if (!GBApplication.isRunningTwelveOrLater()) {
builder.setColor(context.getResources().getColor(R.color.accent)); 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); boolean connected = device.isInitialized();
if (connected) { builder.setContentTitle(deviceName)
deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_DISCONNECT); .setTicker(deviceName + " - " + text)
PendingIntent disconnectPendingIntent = PendingIntent.getService(context, 0, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); .setContentText(text)
builder.addAction(R.drawable.ic_notification_disconnected, context.getString(R.string.controlcenter_disconnect), disconnectPendingIntent); .setSmallIcon(connected ? device.getNotificationIconConnected() : device.getNotificationIconDisconnected())
if (GBApplication.isRunningLollipopOrLater() && DeviceHelper.getInstance().getCoordinator(device).supportsActivityDataFetching()) { //for some reason this fails on KK .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.setAction(DeviceService.ACTION_FETCH_RECORDED_DATA);
deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY); deviceCommunicationServiceIntent.putExtra(EXTRA_RECORDED_DATA_TYPES, ActivityKind.TYPE_ACTIVITY);
PendingIntent fetchPendingIntent = PendingIntent.getService(context, 1, deviceCommunicationServiceIntent, PendingIntent.FLAG_ONE_SHOT); 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); 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()) { if (GBApplication.isRunningLollipopOrLater()) {
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
} }
@ -220,8 +281,8 @@ public class GB {
return builder.build(); return builder.build();
} }
public static void updateNotification(GBDevice device, Context context) { public static void updateNotification(List<GBDevice> devices, Context context) {
Notification notification = createNotification(device, context); Notification notification = createNotification(devices, context);
notify(NOTIFICATION_ID, notification, context); notify(NOTIFICATION_ID, notification, context);
} }

View File

@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.util;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.location.Criteria; import android.location.Criteria;
import android.location.Location; import android.location.Location;
@ -33,6 +34,7 @@ import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBPrefs { public class GBPrefs {
// Since this class must not log to slf4j, we use plain android.util.Log // 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_BLACKLIST = "package_blacklist";
public static final String PACKAGE_PEBBLEMSG_BLACKLIST = "package_pebblemsg_blacklist"; public static final String PACKAGE_PEBBLEMSG_BLACKLIST = "package_pebblemsg_blacklist";
public static final String CALENDAR_BLACKLIST = "calendar_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"; private static final String AUTO_START = "general_autostartonboot";
public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled"; public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled";
public static final String AUTO_EXPORT_LOCATION = "auto_export_location"; public static final String AUTO_EXPORT_LOCATION = "auto_export_location";
@ -67,8 +70,9 @@ public class GBPrefs {
mPrefs = prefs; mPrefs = prefs;
} }
public boolean getAutoReconnect() { public boolean getAutoReconnect(GBDevice device) {
return mPrefs.getBoolean(AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT); SharedPreferences deviceSpecificPreferences = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
return deviceSpecificPreferences.getBoolean(DEVICE_AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT);
} }
public boolean getAutoStart() { public boolean getAutoStart() {

View File

@ -1628,4 +1628,7 @@
<string name="about_activity_title_banglejs_nopebble">Über Bangle.js Gadgetbridge</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_title_banglejs_text_bitmap">Text als Bitmaps</string>
<string name="info_connected_count">%d Geräte verbunden</string>
<string name="info_no_devices_connected">Keine Geräte verbunden</string>
</resources> </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="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_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="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> </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:layout="@layout/preference_checkbox"
android:defaultValue="false" android:defaultValue="false"
android:key="general_autocreconnect" 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 <CheckBoxPreference
android:layout="@layout/preference_checkbox" android:layout="@layout/preference_checkbox"
android:defaultValue="true" android:defaultValue="true"