devices = getDeviceManager().getDevices();
+ for (GBDevice device : devices) {
+ if (device.isBusy()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static void setupLogging(boolean enabled) {
logging.setupLogging(enabled);
}
@@ -173,7 +200,7 @@ public class GBApplication extends Application {
* when that was not successful
* If acquiring was successful, callers must call #releaseDB when they
* are done (from the same thread that acquired the lock!
- *
+ *
* Callers must not hold a reference to the returned instance because it
* will be invalidated at some point.
*
@@ -241,7 +268,7 @@ public class GBApplication extends Application {
@TargetApi(Build.VERSION_CODES.M)
public static boolean isPriorityNumber(int priorityType, String number) {
NotificationManager.Policy notificationPolicy = notificationManager.getNotificationPolicy();
- if(priorityType == Policy.PRIORITY_CATEGORY_MESSAGES) {
+ if (priorityType == Policy.PRIORITY_CATEGORY_MESSAGES) {
if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) == Policy.PRIORITY_CATEGORY_MESSAGES) {
return isPrioritySender(notificationPolicy.priorityMessageSenders, number);
}
@@ -393,6 +420,7 @@ public class GBApplication extends Application {
theme.resolveAttribute(android.R.attr.textColor, typedValue, true);
return typedValue.data;
}
+
public static int getBackgroundColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
@@ -408,7 +436,7 @@ public class GBApplication extends Application {
return gbPrefs;
}
- public static DeviceManager getDeviceManager() {
+ public DeviceManager getDeviceManager() {
return deviceManager;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java
index df07dc46..e30e7c5a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBEnvironment.java
@@ -13,9 +13,8 @@ public class GBEnvironment {
return env;
}
- public static GBEnvironment createDeviceEnvironment() {
- GBEnvironment env = new GBEnvironment();
- return env;
+ static GBEnvironment createDeviceEnvironment() {
+ return new GBEnvironment();
}
public final boolean isTest() {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
index 75504d79..3d71b91f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
@@ -126,6 +126,18 @@ public abstract class Logging {
return false;
}
+ public static String formatBytes(byte[] bytes) {
+ if (bytes == null) {
+ return "(null)";
+ }
+ StringBuilder builder = new StringBuilder(bytes.length * 5);
+ for (byte b : bytes) {
+ builder.append(String.format("0x%2x", b));
+ builder.append(" ");
+ }
+ return builder.toString().trim();
+ }
+
public static void logBytes(Logger logger, byte[] value) {
if (value != null) {
for (byte b : value) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java
index 3579cf82..6b9178fb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java
@@ -98,7 +98,7 @@ public class ControlCenter extends GBActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_controlcenter);
- deviceManager = GBApplication.getDeviceManager();
+ deviceManager = ((GBApplication)getApplication()).getDeviceManager();
hintTextView = (TextView) findViewById(R.id.hintTextView);
ListView deviceListView = (ListView) findViewById(R.id.deviceListView);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java
index 509bc093..07e0d22a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java
@@ -190,7 +190,7 @@ public class DbManagementActivity extends GBActivity {
}
private void selectDeviceForMergingActivityDatabaseInto(final DeviceSelectionCallback callback) {
- GBDevice connectedDevice = GBApplication.getDeviceManager().getSelectedDevice();
+ GBDevice connectedDevice = ((GBApplication)getApplication()).getDeviceManager().getSelectedDevice();
if (connectedDevice == null) {
callback.invoke(null);
return;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java
index 6dd5cbc9..b883fc1c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java
@@ -7,6 +7,8 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
@@ -32,6 +34,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
+import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
@@ -39,6 +42,8 @@ import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -91,6 +96,14 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
handleDeviceFound(device, rssi);
break;
}
+ case BluetoothDevice.ACTION_UUID: {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, GBDevice.RSSI_UNKNOWN);
+ Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
+ ParcelUuid[] uuids2 = AndroidUtils.toParcelUUids(uuids);
+ handleDeviceFound(device, rssi, uuids2);
+ break;
+ }
case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null && device.getAddress().equals(bondingAddress)) {
@@ -115,10 +128,10 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
};
- // why use a method to to get callback?
+ // why use a method to get callback?
// because this callback need API >= 21
// we cant add @TARGETAPI("Lollipop") at class header
- // so use a method woth SDK check to return this callback
+ // so use a method with SDK check to return this callback
private ScanCallback getScanCallback() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
newLeScanCallback = new ScanCallback() {
@@ -127,10 +140,18 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
try {
+ ScanRecord scanRecord = result.getScanRecord();
+ ParcelUuid[] uuids = null;
+ if (scanRecord != null) {
+ //logMessageContent(scanRecord.getBytes());
+ List serviceUuids = scanRecord.getServiceUuids();
+ if (serviceUuids != null) {
+ uuids = serviceUuids.toArray(new ParcelUuid[0]);
+ }
+ }
LOG.warn(result.getDevice().getName() + ": " +
- ((result.getScanRecord() != null) ? result.getScanRecord().getBytes().length : -1));
- //logMessageContent(result.getScanRecord().getBytes());
- handleDeviceFound(result.getDevice(), (short) result.getRssi());
+ ((scanRecord != null) ? scanRecord.getBytes().length : -1));
+ handleDeviceFound(result.getDevice(), (short) result.getRssi(), uuids);
} catch (NullPointerException e) {
LOG.warn("Error handling scan result", e);
}
@@ -195,6 +216,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
IntentFilter bluetoothIntents = new IntentFilter();
bluetoothIntents.addAction(BluetoothDevice.ACTION_FOUND);
+ bluetoothIntents.addAction(BluetoothDevice.ACTION_UUID);
bluetoothIntents.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
bluetoothIntents.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
bluetoothIntents.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
@@ -243,9 +265,20 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
}
private void handleDeviceFound(BluetoothDevice device, short rssi) {
+ ParcelUuid[] uuids = device.getUuids();
+ if (uuids == null) {
+ if (device.fetchUuidsWithSdp()) {
+ return;
+ }
+ }
+
+ handleDeviceFound(device, rssi, uuids);
+ }
+
+
+ private void handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) {
LOG.debug("found device: " + device.getName() + ", " + device.getAddress());
if (LOG.isDebugEnabled()) {
- ParcelUuid[] uuids = device.getUuids();
if (uuids != null && uuids.length > 0) {
for (ParcelUuid uuid : uuids) {
LOG.debug(" supports uuid: " + uuid.toString());
@@ -256,8 +289,10 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
return; // ignore already bonded devices
}
- GBDeviceCandidate candidate = new GBDeviceCandidate(device, rssi);
- if (DeviceHelper.getInstance().isSupported(candidate)) {
+ GBDeviceCandidate candidate = new GBDeviceCandidate(device, rssi, uuids);
+ DeviceType deviceType = DeviceHelper.getInstance().getSupportedType(candidate);
+ if (deviceType.isSupported()) {
+ candidate.setDeviceType(deviceType);
int index = deviceCandidates.indexOf(candidate);
if (index >= 0) {
deviceCandidates.set(index, candidate); // replace
@@ -403,14 +438,22 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
// New BTLE Discovery use startScan (List filters,
// ScanSettings settings,
// ScanCallback callback)
- // Its added on API21
+ // It's added on API21
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void startNEWBTLEDiscovery() {
- // Only use new APi when user use Lollipop+ device
+ // Only use new API when user uses Lollipop+ device
LOG.info("Start New BTLE Discovery");
handler.removeMessages(0, stopRunnable);
handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION);
- adapter.getBluetoothLeScanner().startScan(null, getScanSettings(), getScanCallback());
+ adapter.getBluetoothLeScanner().startScan(getScanFilters(), getScanSettings(), getScanCallback());
+ }
+
+ private List getScanFilters() {
+ List allFilters = new ArrayList<>();
+ for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
+ allFilters.addAll(coordinator.createBLEScanFilters());
+ }
+ return allFilters;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java
index 44d5d93a..856fe0ad 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java
@@ -24,7 +24,10 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Scanner;
import java.util.UUID;
@@ -69,6 +72,8 @@ public class ExternalPebbleJSActivity extends GBActivity {
webSettings.setJavaScriptEnabled(true);
//needed to access the DOM
webSettings.setDomStorageEnabled(true);
+ //needed for localstorage
+ webSettings.setDatabaseEnabled(true);
JSInterface gbJSInterface = new JSInterface(this);
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
@@ -272,9 +277,28 @@ public class ExternalPebbleJSActivity extends GBActivity {
return appUuid.toString();
}
+ @JavascriptInterface
+ public String getAppLocalstoragePrefix() {
+ String prefix = mGBDevice.getAddress() + appUuid.toString();
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ byte[] bytes = prefix.getBytes("UTF-8");
+ digest.update(bytes, 0, bytes.length);
+ bytes = digest.digest();
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ sb.append(String.format("%02X", bytes[i]));
+ }
+ return sb.toString().toLowerCase();
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ e.printStackTrace();
+ return prefix;
+ }
+ }
+
@JavascriptInterface
public String getWatchToken() {
- //specification says: A string that is is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/
+ //specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/
return "gb" + appUuid.toString();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java
index 18be286e..3c0de5ae 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java
@@ -46,7 +46,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
= "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class);
- protected abstract void refreshList();
+ protected abstract List getSystemAppsInCategory();
protected abstract String getSortFilename();
@@ -62,6 +62,23 @@ public abstract class AbstractAppManagerFragment extends Fragment {
AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuidList);
}
+ protected void refreshList() {
+ appList.clear();
+ ArrayList uuids = AppManagerActivity.getUuidsFromFile(getSortFilename());
+ List systemApps = getSystemAppsInCategory();
+ boolean needsRewrite = false;
+ for (GBDeviceApp systemApp : systemApps) {
+ if (!uuids.contains(systemApp.getUUID())) {
+ uuids.add(systemApp.getUUID());
+ needsRewrite = true;
+ }
+ }
+ if (needsRewrite) {
+ AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids);
+ }
+ appList.addAll(getCachedApps(uuids));
+ }
+
private void refreshListFromPebble(Intent intent) {
appList.clear();
int appCount = intent.getIntExtra("app_count", 0);
@@ -103,29 +120,6 @@ public abstract class AbstractAppManagerFragment extends Fragment {
private GBDeviceAppAdapter mGBDeviceAppAdapter;
protected GBDevice mGBDevice = null;
- protected List getSystemApps() {
- List systemApps = new ArrayList<>();
- //systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- //systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- systemApps.add(new GBDeviceApp(UUID.fromString("1f03293d-47af-4f28-b960-f2b02a6dd757"), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- systemApps.add(new GBDeviceApp(UUID.fromString("b2cae818-10f8-46df-ad2b-98ad2254a3c1"), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- systemApps.add(new GBDeviceApp(UUID.fromString("67a32d95-ef69-46d4-a0b9-854cc62f97f9"), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- systemApps.add(new GBDeviceApp(UUID.fromString("18e443ce-38fd-47c8-84d5-6d0c775fbe55"), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
-
- if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getModel()))) {
- systemApps.add(new GBDeviceApp(UUID.fromString("0863fc6a-66c5-4f62-ab8a-82ed00a98b5d"), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- }
-
- return systemApps;
- }
-
- protected List getSystemWatchfaces() {
- List systemWatchfaces = new ArrayList<>();
- systemWatchfaces.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
- return systemWatchfaces;
- }
-
protected List getCachedApps(List uuids) {
List cachedAppList = new ArrayList<>();
File cachePath;
@@ -188,10 +182,23 @@ public abstract class AbstractAppManagerFragment extends Fragment {
cachedAppList.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
*/
- if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getModel()))) {
- if (baseName.equals(PebbleProtocol.UUID_PEBBLE_HEALTH.toString())) {
- cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- continue;
+ if (mGBDevice != null) {
+ if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
+ if (baseName.equals(PebbleProtocol.UUID_PEBBLE_HEALTH.toString())) {
+ cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ continue;
+ }
+ }
+ if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
+ if (baseName.equals(PebbleProtocol.UUID_WORKOUT.toString())) {
+ cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ continue;
+ }
+ }
+ if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
+ if (baseName.equals("3af858c3-16cb-4561-91e7-f1ad2df8725f")) {
+ cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+ }
}
}
if (uuids == null) {
@@ -281,6 +288,10 @@ public abstract class AbstractAppManagerFragment extends Fragment {
menu.removeItem(R.id.appmanager_health_activate);
menu.removeItem(R.id.appmanager_health_deactivate);
}
+ if (!PebbleProtocol.UUID_WORKOUT.equals(selectedApp.getUUID())) {
+ menu.removeItem(R.id.appmanager_hrm_activate);
+ menu.removeItem(R.id.appmanager_hrm_deactivate);
+ }
if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM || selectedApp.getType() == GBDeviceApp.Type.WATCHFACE_SYSTEM) {
menu.removeItem(R.id.appmanager_app_delete);
}
@@ -310,7 +321,6 @@ public abstract class AbstractAppManagerFragment extends Fragment {
public boolean onContextItemSelected(MenuItem item, GBDeviceApp selectedApp) {
switch (item.getItemId()) {
- case R.id.appmanager_health_deactivate:
case R.id.appmanager_app_delete_cache:
String baseName;
try {
@@ -354,6 +364,13 @@ public abstract class AbstractAppManagerFragment extends Fragment {
case R.id.appmanager_health_activate:
GBApplication.deviceService().onInstallApp(Uri.parse("fake://health"));
return true;
+ case R.id.appmanager_hrm_activate:
+ GBApplication.deviceService().onInstallApp(Uri.parse("fake://hrm"));
+ return true;
+ case R.id.appmanager_health_deactivate:
+ case R.id.appmanager_hrm_deactivate:
+ GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
+ return true;
case R.id.appmanager_app_configure:
GBApplication.deviceService().onAppStart(selectedApp.getUUID(), true);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java
index fbf42e94..8423ffbe 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java
@@ -1,5 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
+import java.util.List;
+
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class AppManagerFragmentCache extends AbstractAppManagerFragment {
@@ -14,6 +16,11 @@ public class AppManagerFragmentCache extends AbstractAppManagerFragment {
return true;
}
+ @Override
+ protected List getSystemAppsInCategory() {
+ return null;
+ }
+
@Override
public String getSortFilename() {
return "pbwcacheorder.txt";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java
index d968f713..6cf8bfbb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java
@@ -1,23 +1,36 @@
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment {
+
@Override
- protected void refreshList() {
- appList.clear();
- ArrayList uuids = AppManagerActivity.getUuidsFromFile(getSortFilename());
- if (uuids.isEmpty()) {
- appList.addAll(getSystemApps());
- for (GBDeviceApp gbDeviceApp : appList) {
- uuids.add(gbDeviceApp.getUUID());
+ protected List getSystemAppsInCategory() {
+ List systemApps = new ArrayList<>();
+ //systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ //systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ systemApps.add(new GBDeviceApp(UUID.fromString("1f03293d-47af-4f28-b960-f2b02a6dd757"), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ systemApps.add(new GBDeviceApp(UUID.fromString("b2cae818-10f8-46df-ad2b-98ad2254a3c1"), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ systemApps.add(new GBDeviceApp(UUID.fromString("67a32d95-ef69-46d4-a0b9-854cc62f97f9"), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ systemApps.add(new GBDeviceApp(UUID.fromString("18e443ce-38fd-47c8-84d5-6d0c775fbe55"), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+
+ if (mGBDevice != null) {
+ if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
+ systemApps.add(new GBDeviceApp(UUID.fromString("0863fc6a-66c5-4f62-ab8a-82ed00a98b5d"), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
+ }
+ if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
+ systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
- AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids);
- } else {
- appList.addAll(getCachedApps(uuids));
}
+
+ return systemApps;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java
index 289c6656..b41fad04 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java
@@ -1,23 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class AppManagerFragmentInstalledWatchfaces extends AbstractAppManagerFragment {
+
@Override
- protected void refreshList() {
- appList.clear();
- ArrayList uuids = AppManagerActivity.getUuidsFromFile(getSortFilename());
- if (uuids.isEmpty()) {
- appList.addAll(getSystemWatchfaces());
- for (GBDeviceApp gbDeviceApp : appList) {
- uuids.add(gbDeviceApp.getUUID());
- }
- AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids);
- } else {
- appList.addAll(getCachedApps(uuids));
- }
+ protected List getSystemAppsInCategory() {
+ List systemWatchfaces = new ArrayList<>();
+ systemWatchfaces.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+ systemWatchfaces.add(new GBDeviceApp(UUID.fromString("3af858c3-16cb-4561-91e7-f1ad2df8725f"), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
+ return systemWatchfaces;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
index e314023d..b1beeb77 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
@@ -322,10 +322,6 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return provider.getAllActivitySamples(tsFrom, tsTo);
}
- private int getTSLast24Hours(int tsTo) {
- return (tsTo) - (24 * 60 * 60); // -24 hours
- }
-
protected List extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider extends AbstractActivitySample> provider = getProvider(db, device);
return provider.getActivitySamples(tsFrom, tsTo);
@@ -337,18 +333,6 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return provider.getSleepSamples(tsFrom, tsTo);
}
- protected List extends ActivitySample> getTestSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
- Calendar cal = Calendar.getInstance();
- cal.clear();
- cal.set(2015, Calendar.JUNE, 10, 6, 40);
- // ignore provided date ranges
- tsTo = (int) ((cal.getTimeInMillis() / 1000));
- tsFrom = tsTo - (24 * 60 * 60);
-
- SampleProvider extends ActivitySample> provider = getProvider(db, device);
- return provider.getAllActivitySamples(tsFrom, tsTo);
- }
-
protected void configureChartDefaults(Chart> chart) {
chart.getXAxis().setValueFormatter(new TimestampValueFormatter());
chart.getDescription().setText("");
@@ -362,7 +346,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// enable touch gestures
chart.setTouchEnabled(true);
- chart.getXAxis().setGranularity(60*5);
+// commented out: this has weird bugs/sideeffects at least on WeekStepsCharts
+// where only the first Day-label is drawn, because AxisRenderer.computeAxisValues(float,float)
+// appears to have an overflow when calculating 'n' (number of entries)
+// chart.getXAxis().setGranularity(60*5);
setupLegend(chart);
}
@@ -709,7 +696,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
protected List extends ActivitySample> getSamples(DBHandler db, GBDevice device) {
- List extends ActivitySample> samples = getSamples(db, device, getTSStart(), getTSEnd());
+ int tsStart = getTSStart();
+ int tsEnd = getTSEnd();
+ List samples = (List) getSamples(db, device, tsStart, tsEnd);
+ ensureStartAndEndSamples(samples, tsStart, tsEnd);
// List samples2 = new ArrayList<>();
// int min = Math.min(samples.size(), 10);
// int min = Math.min(samples.size(), 10);
@@ -720,6 +710,33 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return samples;
}
+ protected void ensureStartAndEndSamples(List samples, int tsStart, int tsEnd) {
+ if (samples == null || samples.isEmpty()) {
+ return;
+ }
+ ActivitySample lastSample = samples.get(samples.size() - 1);
+ if (lastSample.getTimestamp() < tsEnd) {
+ samples.add(createTrailingActivitySample(lastSample, tsEnd));
+ }
+
+ ActivitySample firstSample = samples.get(0);
+ if (firstSample.getTimestamp() > tsStart) {
+ samples.add(createTrailingActivitySample(firstSample, tsStart));
+ }
+ }
+
+ private ActivitySample createTrailingActivitySample(ActivitySample referenceSample, int timestamp) {
+ TrailingActivitySample sample = new TrailingActivitySample();
+ if (referenceSample instanceof AbstractActivitySample) {
+ AbstractActivitySample reference = (AbstractActivitySample) referenceSample;
+ sample.setUserId(reference.getUserId());
+ sample.setDeviceId(reference.getDeviceId());
+ sample.setProvider(reference.getProvider());
+ }
+ sample.setTimestamp(timestamp);
+ return sample;
+ }
+
private int getTSEnd() {
return toTimestamp(getEndDate());
}
@@ -770,11 +787,6 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
String dateString = annotationDateFormat.format(date);
return dateString;
}
-
- @Override
- public int getDecimalDigits() {
- return 0;
- }
}
protected static class PreformattedXIndexLabelFormatter implements IAxisValueFormatter {
@@ -792,11 +804,6 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
return xLabels.get(index);
}
-
- @Override
- public int getDecimalDigits() {
- return 0;
- }
}
/**
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
index 927dab5b..543ed42e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
@@ -1,6 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
-import android.content.res.Resources;
import android.view.ViewGroup;
import java.util.Date;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java
index 0f4c592c..437330cb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TimestampValueFormatter.java
@@ -32,9 +32,4 @@ public class TimestampValueFormatter implements IAxisValueFormatter {
String dateString = dateFormat.format(date);
return dateString;
}
-
- @Override
- public int getDecimalDigits() {
- return 0;
- }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java
new file mode 100644
index 00000000..4c2fc77a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/TrailingActivitySample.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.charts;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+
+public class TrailingActivitySample extends AbstractActivitySample {
+ private int timestamp;
+ private long userId;
+ private long deviceId;
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public void setUserId(long userId) {
+ this.userId = userId;
+ }
+
+ @Override
+ public void setDeviceId(long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ @Override
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ @Override
+ public long getUserId() {
+ return userId;
+ }
+
+ @Override
+ public int getTimestamp() {
+ return timestamp;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java
index e20c310d..3d08ed5e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java
@@ -192,6 +192,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
y.setTextColor(CHART_TEXT_COLOR);
y.setDrawZeroLine(true);
y.setSpaceBottom(0);
+ y.setAxisMinimum(0);
y.setEnabled(true);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
index 0794131d..35f0efbf 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
@@ -344,6 +344,9 @@ public class DBHelper {
if (!Objects.equals(attr.getFirmwareVersion2(), gbDevice.getFirmwareVersion2())) {
return false;
}
+ if (!Objects.equals(attr.getVolatileIdentifier(), gbDevice.getVolatileAddress())) {
+ return false;
+ }
return true;
}
@@ -454,6 +457,7 @@ public class DBHelper {
attributes.setValidFromUTC(now.getTime());
attributes.setFirmwareVersion1(gbDevice.getFirmwareVersion());
attributes.setFirmwareVersion2(gbDevice.getFirmwareVersion2());
+ attributes.setVolatileIdentifier(gbDevice.getVolatileAddress());
DeviceAttributesDao attributesDao = session.getDeviceAttributesDao();
attributesDao.insert(attributes);
@@ -685,4 +689,13 @@ public class DBHelper {
}
return cursor.getInt(columnIndex);
}
+
+ public static void clearSession() {
+ try (DBHandler dbHandler = GBApplication.acquireDB()) {
+ DaoSession session = dbHandler.getDaoSession();
+ session.clear();
+ } catch (Exception e) {
+ LOG.warn("Unable to acquire database to clear the session", e);
+ }
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java
new file mode 100644
index 00000000..f7c64ebe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_14.java
@@ -0,0 +1,26 @@
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySampleDao;
+
+/*
+ * adds heart rate column to health table
+ */
+
+public class GadgetbridgeUpdate_14 implements DBUpdateScript {
+ @Override
+ public void upgradeSchema(SQLiteDatabase db) {
+ if (!DBHelper.existsColumn(PebbleHealthActivitySampleDao.TABLENAME, PebbleHealthActivitySampleDao.Properties.HeartRate.columnName, db)) {
+ String ADD_COLUMN_HEART_RATE = "ALTER TABLE " + PebbleHealthActivitySampleDao.TABLENAME + " ADD COLUMN "
+ + PebbleHealthActivitySampleDao.Properties.HeartRate.columnName + " INTEGER NOT NULL DEFAULT 0;";
+ db.execSQL(ADD_COLUMN_HEART_RATE);
+ }
+ }
+
+ @Override
+ public void downgradeSchema(SQLiteDatabase db) {
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java
new file mode 100644
index 00000000..615f1b8d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/GadgetbridgeUpdate_15.java
@@ -0,0 +1,26 @@
+package nodomain.freeyourgadget.gadgetbridge.database.schema;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
+
+/*
+ * adds heart rate column to health table
+ */
+
+public class GadgetbridgeUpdate_15 implements DBUpdateScript {
+ @Override
+ public void upgradeSchema(SQLiteDatabase db) {
+ if (!DBHelper.existsColumn(DeviceAttributesDao.TABLENAME, DeviceAttributesDao.Properties.VolatileIdentifier.columnName, db)) {
+ String ADD_COLUMN_VOLATILE_IDENTIFIER = "ALTER TABLE " + DeviceAttributesDao.TABLENAME + " ADD COLUMN "
+ + DeviceAttributesDao.Properties.VolatileIdentifier.columnName + " TEXT;";
+ db.execSQL(ADD_COLUMN_VOLATILE_IDENTIFIER);
+ }
+ }
+
+ @Override
+ public void downgradeSchema(SQLiteDatabase db) {
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java
index fe0e6ea5..40c6df15 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/SchemaMigration.java
@@ -53,7 +53,7 @@ public class SchemaMigration {
private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) {
try {
- Class> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + ".schema." + classNamePrefix + version);
+ Class> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + "." + classNamePrefix + version);
return (DBUpdateScript) updateClass.newInstance();
} catch (ClassNotFoundException e) {
return null;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java
index 99850c5f..41617f55 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppManagement.java
@@ -12,12 +12,14 @@ public class GBDeviceEventAppManagement extends GBDeviceEvent {
UNKNOWN,
INSTALL,
DELETE,
+ START,
+ STOP,
}
public enum Event {
UNKNOWN,
SUCCESS,
- ACKNOLEDGE,
+ ACKNOWLEDGE,
FAILURE,
REQUEST,
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index c403e6ec..46a65e09 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -2,11 +2,15 @@ package nodomain.freeyourgadget.gadgetbridge.devices;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collection;
+import java.util.Collections;
+
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
@@ -21,12 +25,22 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class);
+ @Override
+ public final boolean supports(GBDeviceCandidate candidate) {
+ return getSupportedType(candidate).isSupported();
+ }
@Override
public boolean supports(GBDevice device) {
return getDeviceType().equals(device.getType());
}
+ @NonNull
+ @Override
+ public Collection extends ScanFilter> createBLEScanFilters() {
+ return Collections.emptyList();
+ }
+
@Override
public GBDevice createDevice(GBDeviceCandidate candidate) {
return new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), getDeviceType());
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java
index 4748ea47..781548f1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java
@@ -74,6 +74,26 @@ public abstract class AbstractSampleProvider i
getSampleDao().insertOrReplaceInTx(activitySamples);
}
+ @Nullable
+ @Override
+ public T getLatestActivitySample() {
+ QueryBuilder qb = getSampleDao().queryBuilder();
+ Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
+ if (dbDevice == null) {
+ // no device, no sample
+ return null;
+ }
+ Property deviceProperty = getDeviceIdentifierSampleProperty();
+ qb.where(deviceProperty.eq(dbDevice.getId())).orderDesc(getTimestampSampleProperty()).limit(1);
+ List samples = qb.build().list();
+ if (samples.isEmpty()) {
+ return null;
+ }
+ T sample = samples.get(0);
+ sample.setProvider(this);
+ return sample;
+ }
+
protected List getGBActivitySamples(int timestamp_from, int timestamp_to, int activityType) {
if (getRawKindSampleProperty() == null && activityType != ActivityKind.TYPE_ALL) {
// if we do not have a raw kind property we cannot query anything else then TYPE_ALL
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index c2eace40..6f0fb39d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -1,8 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
+import android.annotation.TargetApi;
import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+import java.util.Collection;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
@@ -24,7 +30,18 @@ public interface DeviceCoordinator {
String EXTRA_DEVICE_MAC_ADDRESS = "nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate.EXTRA_MAC_ADDRESS";
/**
- * Checks whether this candidate handles the given candidate.
+ * Checks whether this coordinator handles the given candidate.
+ * Returns the supported device type for the given candidate or
+ * DeviceType.UNKNOWN
+ *
+ * @param candidate
+ * @return the supported device type for the given candidate.
+ */
+ @NonNull
+ DeviceType getSupportedType(GBDeviceCandidate candidate);
+
+ /**
+ * Checks whether this coordinator handles the given candidate.
*
* @param candidate
* @return true if this coordinator handles the given candidate.
@@ -39,6 +56,15 @@ public interface DeviceCoordinator {
*/
boolean supports(GBDevice device);
+ /**
+ * Returns a list of scan filters that shall be used to discover devices supported
+ * by this coordinator.
+ * @return the list of scan filters, may be empty
+ */
+ @NonNull
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ Collection extends ScanFilter> createBLEScanFilters();
+
GBDevice createDevice(GBDeviceCandidate candidate);
/**
@@ -122,7 +148,7 @@ public interface DeviceCoordinator {
boolean supportsScreenshots();
/**
- * Returns true if this device/coordinator supports settig alarms.
+ * Returns true if this device/coordinator supports setting alarms.
*
* @return
*/
@@ -154,5 +180,4 @@ public interface DeviceCoordinator {
* @return
*/
Class extends Activity> getAppsManagementActivity();
-
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java
index 2482d65a..1db080a2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceManager.java
@@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory;
import java.text.Collator;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
index a3af9b20..2e652788 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
@@ -14,7 +14,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
/**
- * Specifies all events that GadgetBridge intends to send to the gadget device.
+ * Specifies all events that Gadgetbridge intends to send to the gadget device.
* Implementations can decide to ignore events that they do not support.
* Implementations need to send/encode event to the connected device.
*/
@@ -67,5 +67,12 @@ public interface EventHandler {
void onDeleteCalendarEvent(byte type, long id);
+ /**
+ * Sets the given option in the device, typically with values from the preferences.
+ * The config name is device specific.
+ * @param config the device specific option to set on the device
+ */
+ void onSendConfiguration(String config);
+
void onTestNewFunction();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java
index df91696e..fca1710e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/SampleProvider.java
@@ -1,6 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import java.util.List;
@@ -22,6 +23,7 @@ public interface SampleProvider {
int PROVIDER_PEBBLE_GADGETBRIDGE = 2; // removed
int PROVIDER_PEBBLE_MISFIT = 3;
int PROVIDER_PEBBLE_HEALTH = 4;
+ int PROVIDER_MIBAND2 = 5;
int PROVIDER_UNKNOWN = 100;
// TODO: can also be removed
@@ -87,4 +89,11 @@ public interface SampleProvider {
* @return the newly created "empty" sample
*/
T createActivitySample();
+
+ /**
+ * Returns the activity sample with the highest timestamp. or null if none
+ * @return the latest sample or null
+ */
+ @Nullable
+ T getLatestActivitySample();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java
index 06fc3d4a..6a50ba0f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java
@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import java.util.List;
@@ -64,6 +65,12 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
return null;
}
+ @Nullable
+ @Override
+ public AbstractActivitySample getLatestActivitySample() {
+ return null;
+ }
+
@Override
public int getID() {
return PROVIDER_UNKNOWN;
@@ -75,8 +82,8 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
}
@Override
- public boolean supports(GBDeviceCandidate candidate) {
- return false;
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
+ return DeviceType.UNKNOWN;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java
new file mode 100644
index 00000000..48786c24
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewConstants.java
@@ -0,0 +1,110 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.liveview;
+//Changed by Renze: Fixed brightness constants
+
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Message constants reverse-engineered by Andrew de Quincey (http://adq.livejournal.com).
+ *
+ * @author Robert <xperimental@solidproject.de>
+ */
+public final class LiveviewConstants {
+
+ public static Charset ENCODING = StandardCharsets.ISO_8859_1;
+ public static ByteOrder BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+ public static final byte CLOCK_24H = 0;
+ public static final byte CLOCK_12H = 1;
+
+ public static final byte MSG_GETCAPS = 1;
+ public static final byte MSG_GETCAPS_RESP = 2;
+
+ public static final byte MSG_DISPLAYTEXT = 3;
+ public static final byte MSG_DISPLAYTEXT_ACK = 4;
+
+ public static final byte MSG_DISPLAYPANEL = 5;
+ public static final byte MSG_DISPLAYPANEL_ACK = 6;
+
+ public static final byte MSG_DEVICESTATUS = 7;
+ public static final byte MSG_DEVICESTATUS_ACK = 8;
+
+ public static final byte MSG_DISPLAYBITMAP = 19;
+ public static final byte MSG_DISPLAYBITMAP_ACK = 20;
+
+ public static final byte MSG_CLEARDISPLAY = 21;
+ public static final byte MSG_CLEARDISPLAY_ACK = 22;
+
+ public static final byte MSG_SETMENUSIZE = 23;
+ public static final byte MSG_SETMENUSIZE_ACK = 24;
+
+ public static final byte MSG_GETMENUITEM = 25;
+ public static final byte MSG_GETMENUITEM_RESP = 26;
+
+ public static final byte MSG_GETALERT = 27;
+ public static final byte MSG_GETALERT_RESP = 28;
+
+ public static final byte MSG_NAVIGATION = 29;
+ public static final byte MSG_NAVIGATION_RESP = 30;
+
+ public static final byte MSG_SETSTATUSBAR = 33;
+ public static final byte MSG_SETSTATUSBAR_ACK = 34;
+
+ public static final byte MSG_GETMENUITEMS = 35;
+
+ public static final byte MSG_SETMENUSETTINGS = 36;
+ public static final byte MSG_SETMENUSETTINGS_ACK = 37;
+
+ public static final byte MSG_GETTIME = 38;
+ public static final byte MSG_GETTIME_RESP = 39;
+
+ public static final byte MSG_SETLED = 40;
+ public static final byte MSG_SETLED_ACK = 41;
+
+ public static final byte MSG_SETVIBRATE = 42;
+ public static final byte MSG_SETVIBRATE_ACK = 43;
+
+ public static final byte MSG_ACK = 44;
+
+ public static final byte MSG_SETSCREENMODE = 64;
+ public static final byte MSG_SETSCREENMODE_ACK = 65;
+
+ public static final byte MSG_GETSCREENMODE = 66;
+ public static final byte MSG_GETSCREENMODE_RESP = 67;
+
+ public static final int DEVICESTATUS_OFF = 0;
+ public static final int DEVICESTATUS_ON = 1;
+ public static final int DEVICESTATUS_MENU = 2;
+
+ public static final byte RESULT_OK = 0;
+ public static final byte RESULT_ERROR = 1;
+ public static final byte RESULT_OOM = 2;
+ public static final byte RESULT_EXIT = 3;
+ public static final byte RESULT_CANCEL = 4;
+
+ public static final int NAVACTION_PRESS = 0;
+ public static final int NAVACTION_LONGPRESS = 1;
+ public static final int NAVACTION_DOUBLEPRESS = 2;
+
+ public static final int NAVTYPE_UP = 0;
+ public static final int NAVTYPE_DOWN = 1;
+ public static final int NAVTYPE_LEFT = 2;
+ public static final int NAVTYPE_RIGHT = 3;
+ public static final int NAVTYPE_SELECT = 4;
+ public static final int NAVTYPE_MENUSELECT = 5;
+
+ public static final int ALERTACTION_CURRENT = 0;
+ public static final int ALERTACTION_FIRST = 1;
+ public static final int ALERTACTION_LAST = 2;
+ public static final int ALERTACTION_NEXT = 3;
+ public static final int ALERTACTION_PREV = 4;
+
+ public static final int BRIGHTNESS_OFF = 49;
+ public static final int BRIGHTNESS_DIM = 50;
+ public static final int BRIGHTNESS_MAX = 51;
+
+ public static final String CLIENT_SOFTWARE_VERSION = "0.0.3";
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java
new file mode 100644
index 00000000..338137ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/liveview/LiveviewCoordinator.java
@@ -0,0 +1,105 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.liveview;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class LiveviewCoordinator extends AbstractDeviceCoordinator {
+ @Override
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
+ String name = candidate.getDevice().getName();
+ if (name != null && name.startsWith("LiveView")) {
+ return DeviceType.LIVEVIEW;
+ }
+ return DeviceType.UNKNOWN;
+ }
+
+ @Override
+ public DeviceType getDeviceType() {
+ return DeviceType.LIVEVIEW;
+ }
+
+ @Override
+ public Class extends Activity> getPairingActivity() {
+ return null;
+ }
+
+ @Override
+ public Class extends Activity> getPrimaryActivity() {
+ return null;
+ }
+
+ @Override
+ public InstallHandler findInstallHandler(Uri uri, Context context) {
+ return null;
+ }
+
+ @Override
+ public boolean supportsActivityDataFetching() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsActivityTracking() {
+ return false;
+ }
+
+ @Override
+ public SampleProvider extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+ return null;
+ }
+
+ @Override
+ public boolean supportsScreenshots() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsAlarmConfiguration() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsHeartRateMeasurement(GBDevice device) {
+ return false;
+ }
+
+ @Override
+ public int getTapString() {
+ //TODO: changeme
+ return R.string.tap_connected_device_for_activity;
+ }
+
+ @Override
+ public String getManufacturer() {
+ return "Sony Ericsson";
+ }
+
+ @Override
+ public boolean supportsAppsManagement() {
+ return false;
+ }
+
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return null;
+ }
+
+ @Override
+ protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+ // nothing to delete, yet
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java
new file mode 100644
index 00000000..b328bafa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java
@@ -0,0 +1,100 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+/**
+ * Also see Mi1SFirmwareInfo.
+ */
+public abstract class AbstractMiBandFWHelper {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractMiBandFWHelper.class);
+
+ @NonNull
+ private final byte[] fw;
+
+ public AbstractMiBandFWHelper(Uri uri, Context context) throws IOException {
+ String pebblePattern = ".*\\.(pbw|pbz|pbl)";
+ if (uri.getPath().matches(pebblePattern)) {
+ throw new IOException("Firmware has a filename that looks like a Pebble app/firmware.");
+ }
+
+ try (InputStream in = new BufferedInputStream(context.getContentResolver().openInputStream(uri))) {
+ this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB
+ determineFirmwareInfo(fw);
+ } catch (IOException ex) {
+ throw ex; // pass through
+ } catch (IllegalArgumentException ex) {
+ throw new IOException("This doesn't seem to be a Mi Band firmware: " + ex.getLocalizedMessage(), ex);
+ } catch (Exception e) {
+ throw new IOException("Error reading firmware file: " + uri.toString(), e);
+ }
+ }
+
+ public abstract int getFirmwareVersion();
+
+ public abstract int getFirmware2Version();
+
+ public static String formatFirmwareVersion(int version) {
+ if (version == -1)
+ return GBApplication.getContext().getString(R.string._unknown_);
+
+ return String.format("%d.%d.%d.%d",
+ version >> 24 & 255,
+ version >> 16 & 255,
+ version >> 8 & 255,
+ version & 255);
+ }
+
+ public String getHumanFirmwareVersion() {
+ return format(getFirmwareVersion());
+ }
+
+ public abstract String getHumanFirmwareVersion2();
+
+ public String format(int version) {
+ return formatFirmwareVersion(version);
+ }
+
+ @NonNull
+ public byte[] getFw() {
+ return fw;
+ }
+
+ public boolean isFirmwareWhitelisted() {
+ for (int wlf : getWhitelistedFirmwareVersions()) {
+ if (wlf == getFirmwareVersion()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected abstract int[] getWhitelistedFirmwareVersions();
+
+ public abstract boolean isFirmwareGenerallyCompatibleWith(GBDevice device);
+
+ public abstract boolean isSingleFirmware();
+
+ /**
+ * @param wholeFirmwareBytes
+ * @return
+ * @throws IllegalArgumentException when the data is not recognized as firmware data
+ */
+ @NonNull
+ protected abstract void determineFirmwareInfo(byte[] wholeFirmwareBytes);
+
+ public abstract void checkValid() throws IllegalArgumentException;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java
new file mode 100644
index 00000000..977ad87f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWInstallHandler.java
@@ -0,0 +1,103 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+
+public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractMiBandFWInstallHandler.class);
+
+ private final Context mContext;
+ private AbstractMiBandFWHelper helper;
+ private String errorMessage;
+
+ public AbstractMiBandFWInstallHandler(Uri uri, Context context) {
+ mContext = context;
+
+ try {
+ helper = createHelper(uri, context);
+ } catch (IOException e) {
+ errorMessage = e.getMessage();
+ LOG.warn(errorMessage, e);
+ }
+ }
+
+ protected abstract AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException;
+
+
+ @Override
+ public void validateInstallation(InstallActivity installActivity, GBDevice device) {
+ if (device.isBusy()) {
+ installActivity.setInfoText(device.getBusyTask());
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+
+ if (!isSupportedDeviceType(device) || !device.isInitialized()) {
+ installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+
+ try {
+ helper.checkValid();
+ } catch (IllegalArgumentException ex) {
+ installActivity.setInfoText(ex.getLocalizedMessage());
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+
+ GenericItem fwItem = new GenericItem(mContext.getString(R.string.miband_installhandler_miband_firmware, helper.getHumanFirmwareVersion()));
+ fwItem.setIcon(R.drawable.ic_device_miband);
+
+ if (!helper.isFirmwareGenerallyCompatibleWith(device)) {
+ fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
+ installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
+ installActivity.setInstallEnabled(false);
+ return;
+ }
+ StringBuilder builder = new StringBuilder();
+ if (helper.isSingleFirmware()) {
+ builder.append(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion()));
+ } else {
+ builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
+ }
+
+
+ if (helper.isFirmwareWhitelisted()) {
+ builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
+ fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
+ // TODO: set a CHECK (OKAY) button
+ } else {
+ builder.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n")
+ .append(mContext.getString(R.string.miband_firmware_suggest_whitelist, helper.getFirmwareVersion()));
+ fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
+ // TODO: set a UNKNOWN (question mark) button
+ }
+ installActivity.setInfoText(builder.toString());
+ installActivity.setInstallItem(fwItem);
+ installActivity.setInstallEnabled(true);
+ }
+
+ protected abstract boolean isSupportedDeviceType(GBDevice device);
+
+ @Override
+ public void onStartInstall(GBDevice device) {
+
+ }
+
+ @Override
+ public boolean isValid() {
+ return helper != null;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java
new file mode 100644
index 00000000..36a87b2e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandSampleProvider.java
@@ -0,0 +1,57 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import android.support.annotation.NonNull;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+/**
+ * Base class for Mi1 and Mi2 sample providers. At the moment they both share the
+ * same activity sample class.
+ */
+public abstract class AbstractMiBandSampleProvider extends AbstractSampleProvider {
+
+ // maybe this should be configurable 256 seems way off, though.
+ private final float movementDivisor = 180.0f; //256.0f;
+
+ public AbstractMiBandSampleProvider(GBDevice device, DaoSession session) {
+ super(device, session);
+ }
+
+ @Override
+ public float normalizeIntensity(int rawIntensity) {
+ return rawIntensity / movementDivisor;
+ }
+
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getMiBandActivitySampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return MiBandActivitySampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return MiBandActivitySampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ protected Property getRawKindSampleProperty() {
+ return MiBandActivitySampleDao.Properties.RawKind;
+ }
+
+ @Override
+ public MiBandActivitySample createActivitySample() {
+ return new MiBandActivitySample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java
new file mode 100644
index 00000000..40857aa4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/DateTimeDisplay.java
@@ -0,0 +1,6 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+public enum DateTimeDisplay {
+ TIME,
+ DATE_TIME
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java
index 32957537..01538e09 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java
@@ -1,16 +1,31 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelUuid;
+import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collection;
+import java.util.Collections;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2FWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class MiBand2Coordinator extends MiBandCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(MiBand2Coordinator.class);
@@ -20,25 +35,41 @@ public class MiBand2Coordinator extends MiBandCoordinator {
return DeviceType.MIBAND2;
}
+ @NonNull
@Override
- public boolean supports(GBDeviceCandidate candidate) {
- // and a heuristic
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public Collection extends ScanFilter> createBLEScanFilters() {
+ ParcelUuid mi2Service = new ParcelUuid(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
+ ScanFilter filter = new ScanFilter.Builder().setServiceUuid(mi2Service).build();
+ return Collections.singletonList(filter);
+ }
+
+ @NonNull
+ @Override
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
+ if (candidate.supportsService(MiBand2Service.UUID_SERVICE_MIBAND2_SERVICE)) {
+ return DeviceType.MIBAND2;
+ }
+
+ // and a heuristic for now
try {
BluetoothDevice device = candidate.getDevice();
if (isHealthWearable(device)) {
String name = device.getName();
- return name != null && name.equalsIgnoreCase(MiBandConst.MI_BAND2_NAME);
+ if (name != null && name.equalsIgnoreCase(MiBandConst.MI_BAND2_NAME)) {
+ return DeviceType.MIBAND2;
+ }
}
} catch (Exception ex) {
LOG.error("unable to check device support", ex);
}
- return false;
+ return DeviceType.UNKNOWN;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
- return false; // not yet
+ return true;
}
@Override
@@ -48,11 +79,31 @@ public class MiBand2Coordinator extends MiBandCoordinator {
@Override
public boolean supportsActivityDataFetching() {
- return false; // not yet
+ return true;
+ }
+
+ @Override
+ public SampleProvider extends AbstractActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+ return new MiBand2SampleProvider(device, session);
+ }
+
+ public static DateTimeDisplay getDateDisplay(Context context) throws IllegalArgumentException {
+ Prefs prefs = GBApplication.getPrefs();
+ String dateFormatTime = context.getString(R.string.p_dateformat_time);
+ if (dateFormatTime.equals(prefs.getString(MiBandConst.PREF_MI2_DATEFORMAT, dateFormatTime))) {
+ return DateTimeDisplay.TIME;
+ }
+ return DateTimeDisplay.DATE_TIME;
+ }
+
+ public static boolean getActivateDisplayOnLiftWrist() {
+ Prefs prefs = GBApplication.getPrefs();
+ return prefs.getBoolean(MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT, true);
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
- return null; // not supported at the moment
+ MiBand2FWInstallHandler handler = new MiBand2FWInstallHandler(uri, context);
+ return handler.isValid() ? handler : null;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
new file mode 100644
index 00000000..c7debdcf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
@@ -0,0 +1,138 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+
+import java.util.List;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class MiBand2SampleProvider extends AbstractMiBandSampleProvider {
+// these come from Mi1
+// public static final int TYPE_LIGHT_SLEEP = 5;
+// public static final int TYPE_ACTIVITY = -1;
+// public static final int TYPE_UNKNOWN = -1;
+// public static final int TYPE_NONWEAR = 3;
+// public static final int TYPE_CHARGING = 6;
+
+
+ // observed the following values so far:
+ // 00 01 02 09 0a 0b 0c 10 11
+
+ // 0 = same activity kind as before
+ // 1 = light activity walking?
+ // 3 = definitely non-wear
+ // 9 = probably light sleep, definitely some kind of sleep
+ // 10 = ignore, except for hr (if valid)
+ // 11 = probably deep sleep
+ // 12 = definitely wake up
+ // 17 = definitely not sleep related
+
+ public static final int TYPE_UNSET = -1;
+ public static final int TYPE_NO_CHANGE = 0;
+ public static final int TYPE_ACTIVITY = 1;
+ public static final int TYPE_NONWEAR = 3;
+ public static final int TYPE_CHARGING = 6;
+ public static final int TYPE_LIGHT_SLEEP = 9;
+ public static final int TYPE_DEEP_SLEEP = 11;
+ public static final int TYPE_WAKE_UP = 12;
+ // appears to be a measurement problem resulting in type = 10 and intensity = 20, at least with fw 1.0.0.39
+ public static final int TYPE_IGNORE = 10;
+
+ public MiBand2SampleProvider(GBDevice device, DaoSession session) {
+ super(device, session);
+ }
+
+ @Override
+ public int getID() {
+ return SampleProvider.PROVIDER_MIBAND2;
+ }
+
+
+ @Override
+ protected List getGBActivitySamples(int timestamp_from, int timestamp_to, int activityType) {
+ List samples = super.getGBActivitySamples(timestamp_from, timestamp_to, activityType);
+ postprocess(samples);
+ return samples;
+ }
+
+ /**
+ * "Temporary" runtime post processing of activity kinds.
+ * @param samples
+ */
+ private void postprocess(List samples) {
+ if (samples.isEmpty()) {
+ return;
+ }
+
+ int lastValidKind = determinePreviousValidActivityType(samples.get(0));
+ for (MiBandActivitySample sample : samples) {
+ int rawKind = sample.getRawKind();
+ switch (rawKind) {
+ case TYPE_IGNORE:
+ case TYPE_NO_CHANGE:
+ if (lastValidKind != TYPE_UNSET) {
+ sample.setRawKind(lastValidKind);
+ }
+ break;
+ default:
+ lastValidKind = rawKind;
+ break;
+ }
+ }
+ }
+
+ private int determinePreviousValidActivityType(MiBandActivitySample sample) {
+ QueryBuilder qb = getSampleDao().queryBuilder();
+ qb.where(MiBandActivitySampleDao.Properties.DeviceId.eq(sample.getDeviceId()),
+ MiBandActivitySampleDao.Properties.UserId.eq(sample.getUserId()),
+ MiBandActivitySampleDao.Properties.Timestamp.lt(sample.getTimestamp()),
+ MiBandActivitySampleDao.Properties.RawKind.notIn(TYPE_IGNORE, TYPE_NO_CHANGE));
+ qb.limit(1);
+ List result = qb.build().list();
+ if (result.size() > 0) {
+ return result.get(0).getRawKind();
+ }
+ return TYPE_UNSET;
+ }
+
+ @Override
+ public int normalizeType(int rawType) {
+ switch (rawType) {
+ case TYPE_DEEP_SLEEP:
+ return ActivityKind.TYPE_DEEP_SLEEP;
+ case TYPE_LIGHT_SLEEP:
+ return ActivityKind.TYPE_LIGHT_SLEEP;
+ case TYPE_ACTIVITY:
+ return ActivityKind.TYPE_ACTIVITY;
+ case TYPE_NONWEAR:
+ return ActivityKind.TYPE_NOT_WORN;
+ case TYPE_CHARGING:
+ return ActivityKind.TYPE_NOT_WORN; //I believe it's a safe assumption
+ case TYPE_IGNORE:
+ default:
+ case TYPE_UNSET: // fall through
+ return ActivityKind.TYPE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int toRawActivityKind(int activityKind) {
+ switch (activityKind) {
+ case ActivityKind.TYPE_ACTIVITY:
+ return TYPE_ACTIVITY;
+ case ActivityKind.TYPE_DEEP_SLEEP:
+ return TYPE_DEEP_SLEEP;
+ case ActivityKind.TYPE_LIGHT_SLEEP:
+ return TYPE_LIGHT_SLEEP;
+ case ActivityKind.TYPE_NOT_WORN:
+ return TYPE_NONWEAR;
+ case ActivityKind.TYPE_UNKNOWN: // fall through
+ default:
+ return TYPE_UNSET;
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java
index 064ac7b6..0b8f5623 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java
@@ -12,14 +12,21 @@ public class MiBand2Service {
public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
public static final UUID UUID_SERVICE_MIBAND2_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE1"));
public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString(String.format(BASE_UUID, "180D"));
- public static final UUID UUID_SERVICE_WEIGHT_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700");
+ public static final UUID UUID_SERVICE_FIRMWARE_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700");
+
+ public static final UUID UUID_CHARACTERISTIC_FIRMWARE = UUID.fromString("00001531-0000-3512-2118-0009af100700");
+ public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString("00001532-0000-3512-2118-0009af100700");
+
public static final UUID UUID_UNKNOWN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700");
public static final UUID UUID_UNKNOWN_CHARACTERISTIC1 = UUID.fromString("00000001-0000-3512-2118-0009af100700");
public static final UUID UUID_UNKNOWN_CHARACTERISTIC2 = UUID.fromString("00000002-0000-3512-2118-0009af100700");
- public static final UUID UUID_UNKNOWN_CHARACTERISTIC3 = UUID.fromString("00000003-0000-3512-2118-0009af100700"); // Alarm related
+ /**
+ * Alarms, Display and other configuration.
+ */
+ public static final UUID UUID_CHARACTERISTIC_3_CONFIGURATION = UUID.fromString("00000003-0000-3512-2118-0009af100700");
public static final UUID UUID_UNKNOWN_CHARACTERISTIC4 = UUID.fromString("00000004-0000-3512-2118-0009af100700");
- public static final UUID UUID_UNKNOWN_CHARACTERISTIC5 = UUID.fromString("00000005-0000-3512-2118-0009af100700");
- public static final UUID UUID_UNKNOWN_CHARACTERISTIC6 = UUID.fromString("00000006-0000-3512-2118-0009af100700");
+ public static final UUID UUID_CHARACTERISTIC_5_ACTIVITY_DATA = UUID.fromString("00000005-0000-3512-2118-0009af100700");
+ public static final UUID UUID_CHARACTERISTIC_6_BATTERY_INFO = UUID.fromString("00000006-0000-3512-2118-0009af100700");
public static final UUID UUID_UNKNOWN_CHARACTERISTIC7 = UUID.fromString("00000007-0000-3512-2118-0009af100700");
public static final UUID UUID_UNKNOWN_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700");
// service uuid fee1
@@ -35,206 +42,6 @@ public class MiBand2Service {
// set 12 hour time mode
-// public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01"));
-//
-// public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = UUID.fromString(String.format(BASE_UUID, "FF02"));
-//
-// public static final UUID UUID_CHARACTERISTIC_NOTIFICATION = UUID.fromString(String.format(BASE_UUID, "FF03"));
-//
-// public static final UUID UUID_CHARACTERISTIC_USER_INFO = UUID.fromString(String.format(BASE_UUID, "FF04"));
-//
-// public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID, "FF05"));
-//
-// public static final UUID UUID_CHARACTERISTIC_REALTIME_STEPS = UUID.fromString(String.format(BASE_UUID, "FF06"));
-//
-// public static final UUID UUID_CHARACTERISTIC_ACTIVITY_DATA = UUID.fromString(String.format(BASE_UUID, "FF07"));
-//
-// public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString(String.format(BASE_UUID, "FF08"));
-//
-// public static final UUID UUID_CHARACTERISTIC_LE_PARAMS = UUID.fromString(String.format(BASE_UUID, "FF09"));
-//
-// public static final UUID UUID_CHARACTERISTIC_DATE_TIME = UUID.fromString(String.format(BASE_UUID, "FF0A"));
-//
-// public static final UUID UUID_CHARACTERISTIC_STATISTICS = UUID.fromString(String.format(BASE_UUID, "FF0B"));
-//
-// public static final UUID UUID_CHARACTERISTIC_BATTERY = UUID.fromString(String.format(BASE_UUID, "FF0C"));
-//
-// public static final UUID UUID_CHARACTERISTIC_TEST = UUID.fromString(String.format(BASE_UUID, "FF0D"));
-//
-// public static final UUID UUID_CHARACTERISTIC_SENSOR_DATA = UUID.fromString(String.format(BASE_UUID, "FF0E"));
-//
-// public static final UUID UUID_CHARACTERISTIC_PAIR = UUID.fromString(String.format(BASE_UUID, "FF0F"));
-//
-// public static final UUID UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID, "2A39"));
-// public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT = UUID.fromString(String.format(BASE_UUID, "2A37"));
-//
-//
-//
-// /* FURTHER UUIDS that were mixed with the other params below. The base UUID for these is unknown */
-//
-// public static final byte ALIAS_LEN = 0xa;
-//
-// /*NOTIFICATIONS: usually received on the UUID_CHARACTERISTIC_NOTIFICATION characteristic */
-//
-// public static final byte NOTIFY_NORMAL = 0x0;
-//
-// public static final byte NOTIFY_FIRMWARE_UPDATE_FAILED = 0x1;
-//
-// public static final byte NOTIFY_FIRMWARE_UPDATE_SUCCESS = 0x2;
-//
-// public static final byte NOTIFY_CONN_PARAM_UPDATE_FAILED = 0x3;
-//
-// public static final byte NOTIFY_CONN_PARAM_UPDATE_SUCCESS = 0x4;
-//
-// public static final byte NOTIFY_AUTHENTICATION_SUCCESS = 0x5;
-//
-// public static final byte NOTIFY_AUTHENTICATION_FAILED = 0x6;
-//
-// public static final byte NOTIFY_FITNESS_GOAL_ACHIEVED = 0x7;
-//
-// public static final byte NOTIFY_SET_LATENCY_SUCCESS = 0x8;
-//
-// public static final byte NOTIFY_RESET_AUTHENTICATION_FAILED = 0x9;
-//
-// public static final byte NOTIFY_RESET_AUTHENTICATION_SUCCESS = 0xa;
-//
-// public static final byte NOTIFY_FW_CHECK_FAILED = 0xb;
-//
-// public static final byte NOTIFY_FW_CHECK_SUCCESS = 0xc;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_NOTIFY = 0xd;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_CALL = 0xe;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_DISCONNECT = 0xf;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_SMART_ALARM = 0x10;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_ALARM = 0x11;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_GOAL = 0x12;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_AUTH = 0x13;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_SHUTDOWN = 0x14;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_AUTH_SUCCESS = 0x15;
-//
-// public static final byte NOTIFY_STATUS_MOTOR_TEST = 0x16;
-//
-// // 0x18 is returned when we cancel data sync, perhaps is an ack for this message
-//
-// public static final byte NOTIFY_UNKNOWN = -0x1;
-//
-// public static final int NOTIFY_PAIR_CANCEL = 0xef;
-//
-// public static final int NOTIFY_DEVICE_MALFUNCTION = 0xff;
-//
-//
-// /* MESSAGES: unknown */
-//
-// public static final byte MSG_CONNECTED = 0x0;
-//
-// public static final byte MSG_DISCONNECTED = 0x1;
-//
-// public static final byte MSG_CONNECTION_FAILED = 0x2;
-//
-// public static final byte MSG_INITIALIZATION_FAILED = 0x3;
-//
-// public static final byte MSG_INITIALIZATION_SUCCESS = 0x4;
-//
-// public static final byte MSG_STEPS_CHANGED = 0x5;
-//
-// public static final byte MSG_DEVICE_STATUS_CHANGED = 0x6;
-//
-// public static final byte MSG_BATTERY_STATUS_CHANGED = 0x7;
-//
-// /* COMMANDS: usually sent to UUID_CHARACTERISTIC_CONTROL_POINT characteristic */
-//
-// public static final byte COMMAND_SET_TIMER = 0x4;
-//
-// public static final byte COMMAND_SET_FITNESS_GOAL = 0x5;
-//
-// public static final byte COMMAND_FETCH_DATA = 0x6;
-//
-// public static final byte COMMAND_SEND_FIRMWARE_INFO = 0x7;
-//
-// public static final byte COMMAND_SEND_NOTIFICATION = 0x8;
-//
-// public static final byte COMMAND_CONFIRM_ACTIVITY_DATA_TRANSFER_COMPLETE = 0xa;
-//
-// public static final byte COMMAND_SYNC = 0xb;
-//
-// public static final byte COMMAND_REBOOT = 0xc;
-//
-// public static final byte COMMAND_SET_WEAR_LOCATION = 0xf;
-//
-// public static final byte COMMAND_STOP_SYNC_DATA = 0x11;
-//
-// public static final byte COMMAND_STOP_MOTOR_VIBRATE = 0x13;
-//
-// public static final byte COMMAND_SET_REALTIME_STEPS_NOTIFICATION = 0x3;
-//
-// public static final byte COMMAND_SET_REALTIME_STEP = 0x10;
-//
-// // Test HR
-// public static final byte COMMAND_SET_HR_SLEEP = 0x0;
-// public static final byte COMMAND_SET__HR_CONTINUOUS = 0x1;
-// public static final byte COMMAND_SET_HR_MANUAL = 0x2;
-//
-//
-// /* FURTHER COMMANDS: unchecked therefore left commented
-//
-//
-// public static final byte COMMAND_FACTORY_RESET = 0x9t;
-//
-// public static final int COMMAND_SET_COLOR_THEME = et;
-//
-// public static final byte COMMAND_GET_SENSOR_DATA = 0x12t
-//
-// */
-//
-// /* CONNECTION: unknown
-//
-// public static final CONNECTION_LATENCY_LEVEL_LOW = 0x0t;
-//
-// public static final CONNECTION_LATENCY_LEVEL_MEDIUM = 0x1t;
-//
-// public static final CONNECTION_LATENCY_LEVEL_HIGH = 0x2t;
-//
-// */
-//
-// /* MODES: probably related to the sample data structure
-// */
-//
-// public static final byte MODE_REGULAR_DATA_LEN_BYTE = 0x0;
-//
-// // was MODE_REGULAR_DATA_LEN_MINITE
-// public static final byte MODE_REGULAR_DATA_LEN_MINUTE = 0x1;
-//
-// /* PROFILE: unknown
-//
-// public static final PROFILE_STATE_UNKNOWN:I = 0x0
-//
-// public static final PROFILE_STATE_INITIALIZATION_SUCCESS:I = 0x1
-//
-// public static final PROFILE_STATE_INITIALIZATION_FAILED:I = 0x2
-//
-// public static final PROFILE_STATE_AUTHENTICATION_SUCCESS:I = 0x3
-//
-// public static final PROFILE_STATE_AUTHENTICATION_FAILED:I = 0x4
-//
-// */
-//
-// // TEST_*: sent to UUID_CHARACTERISTIC_TEST characteristic
-//
-// public static final byte TEST_DISCONNECTED_REMINDER = 0x5;
-//
-// public static final byte TEST_NOTIFICATION = 0x3;
-//
-// public static final byte TEST_REMOTE_DISCONNECT = 0x1;
-//
-// public static final byte TEST_SELFTEST = 0x2;
private static final Map MIBAND_DEBUG;
@@ -265,7 +72,7 @@ public class MiBand2Service {
*/
public static final byte AUTH_RESPONSE = 0x10;
/**
- * Receeived in response to any authentication requests (byte 2 in the byte[] value.
+ * Received in response to any authentication requests (byte 2 in the byte[] value.
* 0x01 means success.
*/
public static final byte AUTH_SUCCESS = 0x01;
@@ -279,6 +86,49 @@ public class MiBand2Service {
*/
public static final byte AUTH_BYTE = 0x8;
+ // maybe not really activity data, but steps?
+ public static final byte COMMAND_FETCH_ACTIVITY_DATA = 0x02;
+ public static final byte COMMAND_XXXX_ACTIVITY_DATA = 0x03; // maybe delete/drop activity data?
+
+ public static final byte[] COMMAND_SET_FITNESS_GOAL_START = new byte[] { 0x10, 0x0, 0x0 };
+ public static final byte[] COMMAND_SET_FITNESS_GOAL_END = new byte[] { 0, 0 };
+
+
+ public static byte COMMAND_DATEFORMAT = 0x06;
+
+ public static final byte[] DATEFORMAT_DATE_TIME = new byte[] { COMMAND_DATEFORMAT, 0x0a, 0x0, 0x03 };
+ public static final byte[] DATEFORMAT_TIME = new byte[] { COMMAND_DATEFORMAT, 0x0a, 0x0, 0x0 };
+
+ public static final byte RESPONSE = 0x10;
+
+ public static final byte SUCCESS = 0x01;
+ public static final byte COMMAND_ACTIVITY_DATA_START_DATE = 0x01;
+ public static final byte COMMAND_ACTIVITY_DATA_XXX_DATE = 0x02; // issued on first connect, followd by COMMAND_XXXX_ACTIVITY_DATA instead of COMMAND_FETCH_ACTIVITY_DATA
+
+ public static final byte COMMAND_FIRMWARE_INIT = 0x01; // to UUID_CHARACTERISTIC_FIRMWARE, followed by fw file size in bytes
+ public static final byte COMMAND_FIRMWARE_START_DATA = 0x03; // to UUID_CHARACTERISTIC_FIRMWARE
+ public static final byte COMMAND_FIRMWARE_UPDATE_SYNC = 0x00; // to UUID_CHARACTERISTIC_FIRMWARE
+ public static final byte COMMAND_FIRMWARE_CHECKSUM = 0x04; // to UUID_CHARACTERISTIC_FIRMWARE
+ public static final byte COMMAND_FIRMWARE_APPLY_REBOOT = 0x05; // or is it REBOOT? to UUID_CHARACTERISTIC_FIRMWARE
+
+ public static final byte[] RESPONSE_FINISH_SUCCESS = new byte[] {RESPONSE, 2, SUCCESS };
+ public static final byte[] RESPONSE_FIRMWARE_DATA_SUCCESS = new byte[] {RESPONSE, COMMAND_FIRMWARE_START_DATA, SUCCESS };
+ /**
+ * Received in response to any dateformat configuration request (byte 0 in the byte[] value.
+ */
+ public static final byte[] RESPONSE_DATEFORMAT_SUCCESS = new byte[] { RESPONSE, COMMAND_DATEFORMAT, 0x0a, 0x0, 0x01 };
+ public static final byte[] RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS = new byte[] { RESPONSE, COMMAND_ACTIVITY_DATA_START_DATE, SUCCESS};
+
+ public static final byte[] WEAR_LOCATION_LEFT_WRIST = new byte[] { 0x20, 0x00, 0x00, 0x02 };
+ public static final byte[] WEAR_LOCATION_RIGHT_WRIST = new byte[] { 0x20, 0x00, 0x00, (byte) 0x82};
+
+ public static final byte[] COMMAND_ENABLE_HR_SLEEP_MEASUREMENT = new byte[]{0x15, 0x00, 0x01};
+ public static final byte[] COMMAND_DISABLE_HR_SLEEP_MEASUREMENT = new byte[]{0x15, 0x00, 0x00};
+
+ public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{0x06, 0x05, 0x00, 0x01};
+ public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{0x06, 0x05, 0x00, 0x00};
+
+
static {
MIBAND_DEBUG = new HashMap<>();
MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
index fce2f84d..f4d5ec9f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
@@ -17,8 +17,8 @@ public final class MiBandConst {
public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar";
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";
-
- public static final String PREF_TRY_SMS = "mi_try_sms";
+ public static final String PREF_MI2_DATEFORMAT = "mi2_dateformat";
+ public static final String PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT = "mi2_activate_display_on_lift_wrist";
public static final String ORIGIN_INCOMING_CALL = "incoming_call";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
index 069c9ba1..abff3bc1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
@@ -1,13 +1,21 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelUuid;
+import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collection;
+import java.util.Collections;
+
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
@@ -32,28 +40,40 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
public MiBandCoordinator() {
}
+ @NonNull
@Override
- public boolean supports(GBDeviceCandidate candidate) {
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public Collection extends ScanFilter> createBLEScanFilters() {
+ ParcelUuid mi1Service = new ParcelUuid(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
+ ScanFilter filter = new ScanFilter.Builder().setServiceUuid(mi1Service).build();
+ return Collections.singletonList(filter);
+ }
+
+ @NonNull
+ @Override
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String macAddress = candidate.getMacAddress().toUpperCase();
if (macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1_1A)
|| macAddress.startsWith(MiBandService.MAC_ADDRESS_FILTER_1S)) {
- return true;
+ return DeviceType.MIBAND;
}
if (candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND_SERVICE)
&& !candidate.supportsService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE)) {
- return true;
+ return DeviceType.MIBAND;
}
// and a heuristic
try {
BluetoothDevice device = candidate.getDevice();
if (isHealthWearable(device)) {
String name = device.getName();
- return name != null && name.toUpperCase().startsWith(MiBandConst.MI_GENERAL_NAME_PREFIX.toUpperCase());
+ if (name != null && name.toUpperCase().startsWith(MiBandConst.MI_GENERAL_NAME_PREFIX.toUpperCase())) {
+ return DeviceType.MIBAND;
+ }
}
} catch (Exception ex) {
LOG.error("unable to check device support", ex);
}
- return false;
+ return DeviceType.UNKNOWN;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java
index 04f87c71..71fd485a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java
@@ -7,20 +7,15 @@ import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import nodomain.freeyourgadget.gadgetbridge.GBApplication;
-import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
-import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
/**
* Also see Mi1SFirmwareInfo.
*/
-public class MiBandFWHelper {
+public class MiBandFWHelper extends AbstractMiBandFWHelper {
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
/**
@@ -29,9 +24,7 @@ public class MiBandFWHelper {
* attempting to flash it.
*/
@NonNull
- private final AbstractMiFirmwareInfo firmwareInfo;
- @NonNull
- private final byte[] fw;
+ private AbstractMiFirmwareInfo firmwareInfo;
/**
* Provides a different notification API which is also used on Mi1A devices.
@@ -54,77 +47,55 @@ public class MiBandFWHelper {
};
public MiBandFWHelper(Uri uri, Context context) throws IOException {
- String pebblePattern = ".*\\.(pbw|pbz|pbl)";
- if (uri.getPath().matches(pebblePattern)) {
- throw new IOException("Firmware has a filename that looks like a Pebble app/firmware.");
- }
-
- try (InputStream in = new BufferedInputStream(context.getContentResolver().openInputStream(uri))) {
- this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB
- this.firmwareInfo = determineFirmwareInfoFor(fw);
- } catch (IOException ex) {
- throw ex; // pass through
- } catch (IllegalArgumentException ex) {
- throw new IOException("This doesn't seem to be a Mi Band firmware: " + ex.getLocalizedMessage(), ex);
- } catch (Exception e) {
- throw new IOException("Error reading firmware file: " + uri.toString(), e);
- }
+ super(uri, context);
}
+ @Override
public int getFirmwareVersion() {
// FIXME: UnsupportedOperationException!
return firmwareInfo.getFirst().getFirmwareVersion();
}
+ @Override
public int getFirmware2Version() {
return firmwareInfo.getFirst().getFirmwareVersion();
}
- public static String formatFirmwareVersion(int version) {
- if (version == -1)
- return GBApplication.getContext().getString(R.string._unknown_);
-
- return String.format("%d.%d.%d.%d",
- version >> 24 & 255,
- version >> 16 & 255,
- version >> 8 & 255,
- version & 255);
- }
-
- public String getHumanFirmwareVersion() {
- return format(getFirmwareVersion());
- }
-
+ @Override
public String getHumanFirmwareVersion2() {
return format(firmwareInfo.getSecond().getFirmwareVersion());
}
- public String format(int version) {
- return formatFirmwareVersion(version);
- }
-
- @NonNull
- public byte[] getFw() {
- return fw;
- }
-
- public boolean isFirmwareWhitelisted() {
- for (int wlf : whitelistedFirmwareVersion) {
- if (wlf == getFirmwareVersion()) {
- return true;
- }
- }
- return false;
+ @Override
+ protected int[] getWhitelistedFirmwareVersions() {
+ return whitelistedFirmwareVersion;
}
+ @Override
public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
return firmwareInfo.isGenerallyCompatibleWith(device);
}
+ @Override
public boolean isSingleFirmware() {
return firmwareInfo.isSingleMiBandFirmware();
}
+ /**
+ * @param wholeFirmwareBytes
+ * @return
+ * @throws IllegalArgumentException when the data is not recognized as firmware data
+ */
+ @Override
+ protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+ firmwareInfo = AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes);
+ }
+
+ @Override
+ public void checkValid() throws IllegalArgumentException {
+ firmwareInfo.checkValid();
+ }
+
/**
* @param wholeFirmwareBytes
* @return
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java
index 8fcc91a4..ef428efa 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWInstallHandler.java
@@ -8,91 +8,23 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
-import nodomain.freeyourgadget.gadgetbridge.R;
-import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
-import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
-import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
-public class MiBandFWInstallHandler implements InstallHandler {
+public class MiBandFWInstallHandler extends AbstractMiBandFWInstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWInstallHandler.class);
- private final Context mContext;
- private MiBandFWHelper helper;
- private String errorMessage;
-
public MiBandFWInstallHandler(Uri uri, Context context) {
- mContext = context;
-
- try {
- helper = new MiBandFWHelper(uri, mContext);
- } catch (IOException e) {
- errorMessage = e.getMessage();
- LOG.warn(errorMessage, e);
- }
+ super(uri, context);
}
@Override
- public void validateInstallation(InstallActivity installActivity, GBDevice device) {
- if (device.isBusy()) {
- installActivity.setInfoText(device.getBusyTask());
- installActivity.setInstallEnabled(false);
- return;
- }
-
- if (device.getType() != DeviceType.MIBAND || !device.isInitialized()) {
- installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
- installActivity.setInstallEnabled(false);
- return;
- }
-
- try {
- helper.getFirmwareInfo().checkValid();
- } catch (IllegalArgumentException ex) {
- installActivity.setInfoText(ex.getLocalizedMessage());
- installActivity.setInstallEnabled(false);
- return;
- }
-
- GenericItem fwItem = new GenericItem(mContext.getString(R.string.miband_installhandler_miband_firmware, helper.getHumanFirmwareVersion()));
- fwItem.setIcon(R.drawable.ic_device_miband);
-
- if (!helper.isFirmwareGenerallyCompatibleWith(device)) {
- fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
- installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
- installActivity.setInstallEnabled(false);
- return;
- }
- StringBuilder builder = new StringBuilder();
- if (helper.isSingleFirmware()) {
- builder.append(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion()));
- } else {
- builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
- }
-
-
- if (helper.isFirmwareWhitelisted()) {
- builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
- fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
- // TODO: set a CHECK (OKAY) button
- } else {
- builder.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n")
- .append(mContext.getString(R.string.miband_firmware_suggest_whitelist, helper.getFirmwareVersion()));
- fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
- // TODO: set a UNKNOWN (question mark) button
- }
- installActivity.setInfoText(builder.toString());
- installActivity.setInstallItem(fwItem);
- installActivity.setInstallEnabled(true);
+ protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
+ return new MiBandFWHelper(uri, context);
}
@Override
- public void onStartInstall(GBDevice device) {
-
- }
-
- public boolean isValid() {
- return helper != null;
+ protected boolean isSupportedDeviceType(GBDevice device) {
+ return device.getType() == DeviceType.MIBAND;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java
index 14472ff5..89424a1f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java
@@ -160,7 +160,7 @@ public class MiBandPairingActivity extends GBActivity {
private void startPairing() {
isPairing = true;
- message.setText(getString(R.string.miband_pairing, macAddress));
+ message.setText(getString(R.string.pairing, macAddress));
IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter);
@@ -209,20 +209,20 @@ public class MiBandPairingActivity extends GBActivity {
protected void performBluetoothPair(BluetoothDevice device) {
int bondState = device.getBondState();
if (bondState == BluetoothDevice.BOND_BONDED) {
- GB.toast(getString(R.string.miband_pairing_already_bonded, device.getName(), device.getAddress()), Toast.LENGTH_SHORT, GB.INFO);
+ GB.toast(getString(R.string.pairing_already_bonded, device.getName(), device.getAddress()), Toast.LENGTH_SHORT, GB.INFO);
performPair();
return;
}
bondingMacAddress = device.getAddress();
if (bondState == BluetoothDevice.BOND_BONDING) {
- GB.toast(this, getString(R.string.miband_pairing_in_progress, device.getName(), bondingMacAddress), Toast.LENGTH_LONG, GB.INFO);
+ GB.toast(this, getString(R.string.pairing_in_progress, device.getName(), bondingMacAddress), Toast.LENGTH_LONG, GB.INFO);
return;
}
- GB.toast(this, getString(R.string.miband_pairing_creating_bond_with, device.getName(), bondingMacAddress), Toast.LENGTH_LONG, GB.INFO);
+ GB.toast(this, getString(R.string.pairing_creating_bond_with, device.getName(), bondingMacAddress), Toast.LENGTH_LONG, GB.INFO);
if (!device.createBond()) {
- GB.toast(this, getString(R.string.miband_pairing_unable_to_pair_with, device.getName(), bondingMacAddress), Toast.LENGTH_LONG, GB.ERROR);
+ GB.toast(this, getString(R.string.pairing_unable_to_pair_with, device.getName(), bondingMacAddress), Toast.LENGTH_LONG, GB.ERROR);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java
index 4c8de0aa..00b77c06 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java
@@ -18,6 +18,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
@@ -44,8 +46,58 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
return true;
}
});
+
+ final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
+ setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newVal) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ GBApplication.deviceService().onSendConfiguration(PREF_MI2_DATEFORMAT);
+ }
+ });
+ return true;
+ }
+ });
+
+ final Preference activateDisplayOnLift = findPreference(PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT);
+ activateDisplayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newVal) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ GBApplication.deviceService().onSendConfiguration(PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT);
+ }
+ });
+ return true;
+ }
+ });
+
+ final Preference fitnessGoal = findPreference(PREF_MIBAND_FITNESS_GOAL);
+ fitnessGoal.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newVal) {
+ invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ GBApplication.deviceService().onSendConfiguration(PREF_MIBAND_FITNESS_GOAL);
+ }
+ });
+ return true;
+ }
+ });
}
+ /**
+ * delayed execution so that the preferences are applied first
+ */
+ private void invokeLater(Runnable runnable) {
+ getListView().post(runnable);
+ }
+
+ @Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
final Preference developmentMiaddr = findPreference(PREF_MIBAND_ADDRESS);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java
index 13f77224..8f2a95c7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java
@@ -1,16 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
-import de.greenrobot.dao.AbstractDao;
-import de.greenrobot.dao.Property;
-import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
-import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
-import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
-public class MiBandSampleProvider extends AbstractSampleProvider {
+public class MiBandSampleProvider extends AbstractMiBandSampleProvider {
public static final int TYPE_DEEP_SLEEP = 4;
public static final int TYPE_LIGHT_SLEEP = 5;
public static final int TYPE_ACTIVITY = -1;
@@ -26,13 +21,15 @@ public class MiBandSampleProvider extends AbstractSampleProvider getSampleDao() {
- return getSession().getMiBandActivitySampleDao();
- }
-
- @Override
- protected Property getTimestampSampleProperty() {
- return MiBandActivitySampleDao.Properties.Timestamp;
- }
-
- @Override
- protected Property getDeviceIdentifierSampleProperty() {
- return MiBandActivitySampleDao.Properties.DeviceId;
- }
-
- @Override
- protected Property getRawKindSampleProperty() {
- return MiBandActivitySampleDao.Properties.RawKind;
- }
-
- @Override
- public MiBandActivitySample createActivitySample() {
- return new MiBandActivitySample();
- }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java
index 8ea54c8a..b23342bc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/VibrationProfile.java
@@ -50,7 +50,7 @@ public class VibrationProfile {
*
* @param id the ID, used as preference key.
* @param onOffSequence a sequence of alternating on and off durations, in milliseconds
- * @param repeat how ofoften the sequence shall be repeated
+ * @param repeat how often the sequence shall be repeated
*/
public VibrationProfile(String id, int[] onOffSequence, short repeat) {
this.id = id;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2FWHelper.java
new file mode 100644
index 00000000..207aceef
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2FWHelper.java
@@ -0,0 +1,73 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband2;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2FirmwareInfo;
+
+public class MiBand2FWHelper extends AbstractMiBandFWHelper {
+ private Mi2FirmwareInfo firmwareInfo;
+
+ public MiBand2FWHelper(Uri uri, Context context) throws IOException {
+ super(uri, context);
+ }
+
+ @Override
+ public String format(int version) {
+ return Mi2FirmwareInfo.toVersion(version);
+ }
+
+ @Override
+ public int getFirmwareVersion() {
+ return firmwareInfo.getFirmwareVersion();
+ }
+
+ @Override
+ public int getFirmware2Version() {
+ return 0;
+ }
+
+ @Override
+ public String getHumanFirmwareVersion2() {
+ return "";
+ }
+
+ @Override
+ protected int[] getWhitelistedFirmwareVersions() {
+ return Mi2FirmwareInfo.getWhitelistedVersions();
+ }
+
+ @Override
+ public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
+ return firmwareInfo.isGenerallyCompatibleWith(device);
+ }
+
+ @Override
+ public boolean isSingleFirmware() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
+ firmwareInfo = new Mi2FirmwareInfo(wholeFirmwareBytes);
+ if (!firmwareInfo.isHeaderValid()) {
+ throw new IllegalArgumentException("Not a Mi Band 2 firmware");
+ }
+ }
+
+ @Override
+ public void checkValid() throws IllegalArgumentException {
+ firmwareInfo.checkValid();
+ }
+
+ public Mi2FirmwareInfo getFirmwareInfo() {
+ return firmwareInfo;
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2FWInstallHandler.java
new file mode 100644
index 00000000..ef84dee1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2FWInstallHandler.java
@@ -0,0 +1,32 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband2;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class MiBand2FWInstallHandler extends AbstractMiBandFWInstallHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(MiBand2FWInstallHandler.class);
+
+ public MiBand2FWInstallHandler(Uri uri, Context context) {
+ super(uri, context);
+ }
+
+ @Override
+ protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
+ return new MiBand2FWHelper(uri, context);
+ }
+
+ @Override
+ protected boolean isSupportedDeviceType(GBDevice device) {
+ return device.getType() == DeviceType.MIBAND2;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java
index 9670851b..70ed7f37 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java
@@ -181,8 +181,8 @@ public class PBWInstallHandler implements InstallHandler {
}
public boolean isValid() {
- // always pretend it is valid, as we cant know yet about hw/fw version
+ // always pretend it is valid, as we can't know yet about hw/fw version
return true;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java
index d6babc62..3e3b4710 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java
@@ -232,7 +232,7 @@ public class PBWReader {
byte[] tmp_buf = new byte[32];
ByteBuffer buf = ByteBuffer.wrap(buffer);
buf.order(ByteOrder.LITTLE_ENDIAN);
- buf.getLong(); // header, TODO: verifiy
+ buf.getLong(); // header, TODO: verify
buf.getShort(); // struct version, TODO: verify
mSdkVersion = buf.getShort();
mAppVersion = buf.getShort();
@@ -342,4 +342,4 @@ public class PBWReader {
public JSONObject getAppKeysJSON() {
return mAppKeys;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
index 48cbbb88..5953113d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
@@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PebbleCoordinator extends AbstractDeviceCoordinator {
@@ -29,9 +30,12 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
}
@Override
- public boolean supports(GBDeviceCandidate candidate) {
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
- return name != null && name.startsWith("Pebble");
+ if (name != null && name.startsWith("Pebble")) {
+ return DeviceType.PEBBLE;
+ }
+ return DeviceType.UNKNOWN;
}
@Override
@@ -41,9 +45,10 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
@Override
public Class extends Activity> getPairingActivity() {
- return null;
+ return PebblePairingActivity.class;
}
+ @Override
public Class extends Activity> getPrimaryActivity() {
return AppManagerActivity.class;
}
@@ -105,7 +110,7 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
- return false;
+ return PebbleUtils.hasHRM(device.getModel());
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java
new file mode 100644
index 00000000..2cf3b545
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java
@@ -0,0 +1,242 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import de.greenrobot.dao.query.Query;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
+import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.GBActivity;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class PebblePairingActivity extends GBActivity {
+ private static final Logger LOG = LoggerFactory.getLogger(PebblePairingActivity.class);
+ private TextView message;
+ private boolean isPairing;
+ private boolean isLEPebble;
+ private String macAddress;
+ private BluetoothDevice mBtDevice;
+
+ private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
+ GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+ LOG.debug("pairing activity: device changed: " + device);
+ if (macAddress.equals(device.getAddress()) || macAddress.equals(device.getVolatileAddress())) {
+ if (device.isInitialized()) {
+ pairingFinished(true);
+ } else if (device.isConnecting() || device.isInitializing()) {
+ LOG.info("still connecting/initializing device...");
+ }
+ }
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBondingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ LOG.info("Bond state changed: " + device + ", state: " + device.getBondState() + ", expected address: " + macAddress);
+ if (macAddress != null && macAddress.equals(device.getAddress())) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ LOG.info("Bonded with " + device.getAddress());
+ if (!isLEPebble) {
+ performConnect(null);
+ }
+ } else if (bondState == BluetoothDevice.BOND_BONDING) {
+ LOG.info("Bonding in progress with " + device.getAddress());
+ } else if (bondState == BluetoothDevice.BOND_NONE) {
+ LOG.info("Not bonded with " + device.getAddress() + ", attempting to connect anyway.");
+ } else {
+ LOG.warn("Unknown bond state for device " + device.getAddress() + ": " + bondState);
+ pairingFinished(false);
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_pebble_pairing);
+
+ message = (TextView) findViewById(R.id.pebble_pair_message);
+ Intent intent = getIntent();
+ macAddress = intent.getStringExtra(DeviceCoordinator.EXTRA_DEVICE_MAC_ADDRESS);
+ if (macAddress == null) {
+ Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
+ returnToPairingActivity();
+ return;
+ }
+
+ mBtDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
+ if (mBtDevice == null) {
+ GB.toast(this, "No such Bluetooth Device: " + macAddress, Toast.LENGTH_LONG, GB.ERROR);
+ returnToPairingActivity();
+ return;
+ }
+
+ isLEPebble = mBtDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE;
+
+ GBDevice gbDevice = null;
+ if (isLEPebble) {
+ if (mBtDevice.getName().startsWith("Pebble-LE ") || mBtDevice.getName().startsWith("Pebble Time LE ")) {
+ if (!GBApplication.getPrefs().getBoolean("pebble_force_le", false)) {
+ GB.toast(this, "Please switch on \"Always prefer BLE\" option in Pebble settings before pairing you Pebble LE", Toast.LENGTH_LONG, GB.ERROR);
+ returnToPairingActivity();
+ return;
+ }
+ gbDevice = getMatchingParentDeviceFromDB(mBtDevice);
+ if (gbDevice == null) {
+ return;
+ }
+ }
+ }
+ startPairing(gbDevice);
+ }
+
+ @Override
+ protected void onDestroy() {
+ try {
+ // just to be sure, remove the receivers -- might actually be already unregistered
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver);
+ unregisterReceiver(mBondingReceiver);
+ } catch (IllegalArgumentException ex) {
+ // already unregistered, ignore
+ }
+ if (isPairing) {
+ stopPairing();
+ }
+
+ super.onDestroy();
+ }
+
+ private void startPairing(GBDevice gbDevice) {
+ isPairing = true;
+ message.setText(getString(R.string.pairing, macAddress));
+
+ IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter);
+ filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ registerReceiver(mBondingReceiver, filter);
+
+ performPair(gbDevice);
+ }
+
+ private void pairingFinished(boolean pairedSuccessfully) {
+ LOG.debug("pairingFinished: " + pairedSuccessfully);
+ if (!isPairing) {
+ // already gone?
+ return;
+ }
+
+ isPairing = false;
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver);
+ unregisterReceiver(mBondingReceiver);
+
+ if (pairedSuccessfully) {
+ Intent intent = new Intent(this, ControlCenter.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+ finish();
+ }
+
+ private void stopPairing() {
+ // TODO
+ isPairing = false;
+ }
+
+ protected void performPair(GBDevice gbDevice) {
+ int bondState = mBtDevice.getBondState();
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ GB.toast(getString(R.string.pairing_already_bonded, mBtDevice.getName(), mBtDevice.getAddress()), Toast.LENGTH_SHORT, GB.INFO);
+ return;
+ }
+
+ if (bondState == BluetoothDevice.BOND_BONDING) {
+ GB.toast(this, getString(R.string.pairing_in_progress, mBtDevice.getName(), macAddress), Toast.LENGTH_LONG, GB.INFO);
+ return;
+ }
+
+ GB.toast(this, getString(R.string.pairing_creating_bond_with, mBtDevice.getName(), macAddress), Toast.LENGTH_LONG, GB.INFO);
+ GBApplication.deviceService().disconnect(); // just to make sure...
+
+ if (isLEPebble) {
+ performConnect(gbDevice);
+ } else {
+ mBtDevice.createBond();
+ }
+ }
+
+ private void performConnect(GBDevice gbDevice) {
+ if (gbDevice == null) {
+ gbDevice = new GBDevice(mBtDevice.getAddress(), mBtDevice.getName(), DeviceType.PEBBLE);
+ }
+ GBApplication.deviceService().connect(gbDevice);
+ }
+
+ private void returnToPairingActivity() {
+ startActivity(new Intent(this, DiscoveryActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
+ finish();
+ }
+
+ private GBDevice getMatchingParentDeviceFromDB(BluetoothDevice btDevice) {
+ String expectedSuffix = btDevice.getName();
+ expectedSuffix = expectedSuffix.replace("Pebble-LE ", "");
+ expectedSuffix = expectedSuffix.replace("Pebble Time LE ", "");
+ expectedSuffix = expectedSuffix.substring(0, 2) + ":" + expectedSuffix.substring(2);
+ LOG.info("will try to find a Pebble with BT address suffix " + expectedSuffix);
+ GBDevice gbDevice = null;
+ try (DBHandler dbHandler = GBApplication.acquireDB()) {
+ DaoSession session = dbHandler.getDaoSession();
+ DeviceDao deviceDao = session.getDeviceDao();
+ Query query = deviceDao.queryBuilder().where(DeviceDao.Properties.Type.eq(1), DeviceDao.Properties.Identifier.like("%" + expectedSuffix)).build();
+ List devices = query.list();
+ if (devices.size() == 0) {
+ GB.toast("Please pair your non-LE Pebble before pairing the LE one", Toast.LENGTH_SHORT, GB.INFO);
+ returnToPairingActivity();
+ return null;
+ } else if (devices.size() > 1) {
+ GB.toast("Can not match this Pebble LE to a unique device", Toast.LENGTH_SHORT, GB.INFO);
+ returnToPairingActivity();
+ return null;
+ }
+ DeviceHelper deviceHelper = DeviceHelper.getInstance();
+ gbDevice = deviceHelper.toGBDevice(devices.get(0));
+ gbDevice.setVolatileAddress(btDevice.getAddress());
+ } catch (Exception e) {
+ GB.toast("Error retrieving devices from database", Toast.LENGTH_SHORT, GB.ERROR);
+ returnToPairingActivity();
+ return null;
+ }
+ return gbDevice;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java
index d36b0c40..8f71f234 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vibratissimo/VibratissimoCoordinator.java
@@ -20,9 +20,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
@Override
- public boolean supports(GBDeviceCandidate candidate) {
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
- return name != null && name.startsWith("Vibratissimo");
+ if (name != null && name.startsWith("Vibratissimo")) {
+ return DeviceType.VIBRATISSIMO;
+ }
+ return DeviceType.UNKNOWN;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java
index 08f09c11..03f9e442 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractActivitySample.java
@@ -74,12 +74,14 @@ public abstract class AbstractActivitySample implements ActivitySample {
@Override
public String toString() {
+ int kind = getProvider() != null ? getKind() : ActivitySample.NOT_MEASURED;
+ float intensity = getProvider() != null ? getIntensity() : ActivitySample.NOT_MEASURED;
return getClass().getSimpleName() + "{" +
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(getTimestamp())) +
- ", intensity=" + getIntensity() +
+ ", intensity=" + intensity +
", steps=" + getSteps() +
", heartrate=" + getHeartRate() +
- ", type=" + getKind() +
+ ", type=" + kind +
", userId=" + getUserId() +
", deviceId=" + getDeviceId() +
'}';
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java
index e9470d31..9c4a581a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java
@@ -1,11 +1,17 @@
package nodomain.freeyourgadget.gadgetbridge.externalevents;
+import android.Manifest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+import android.support.v4.app.ActivityCompat;
import net.e175.klaus.solarpositioning.DeltaT;
import net.e175.klaus.solarpositioning.SPA;
@@ -64,6 +70,21 @@ public class AlarmReceiver extends BroadcastReceiver {
float latitude = prefs.getFloat("location_latitude", 0);
float longitude = prefs.getFloat("location_longitude", 0);
LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude);
+
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
+ prefs.getBoolean("use_updated_location_if_available", false)) {
+ LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ Criteria criteria = new Criteria();
+ String provider = locationManager.getBestProvider(criteria, false);
+ if (provider != null) {
+ Location lastKnownLocation = locationManager.getLastKnownLocation(provider);
+ if (lastKnownLocation != null) {
+ latitude = (float) lastKnownLocation.getLatitude();
+ longitude = (float) lastKnownLocation.getLongitude();
+ LOG.info("got longitude/latitude from last known location: " + latitude + "/" + longitude);
+ }
+ }
+ }
GregorianCalendar[] sunriseTransitSetTomorrow = SPA.calculateSunriseTransitSet(dateTimeTomorrow, latitude, longitude, DeltaT.estimate(dateTimeTomorrow));
CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java
index 17eb67e9..c9431b43 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java
@@ -57,7 +57,7 @@ public class K9Receiver extends BroadcastReceiver {
notificationSpec.type = NotificationType.GENERIC_EMAIL;
/*
- * there seems to be no way to specify the the uri in the where clause.
+ * there seems to be no way to specify the uri in the where clause.
* If we do so, we just get the newest message, not the one requested.
* So, we will just search our message and match the uri manually.
* It should be the first one returned by the query in most cases,
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
index 8f925e2b..0c29c664 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@@ -189,16 +190,19 @@ public class NotificationListener extends NotificationListenerService {
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
if (powermanager.isScreenOn()) {
+ LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
return;
}
}
//don't forward group summary notifications to the wearable, they are meant for the android device only
if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) {
+ LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set");
return;
}
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
+ LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set");
return;
}
@@ -211,6 +215,7 @@ public class NotificationListener extends NotificationListenerService {
source.equals("com.android.systemui") ||
source.equals("com.android.dialer") ||
source.equals("com.cyanogenmod.eleven")) {
+ LOG.info("Not forwarding notification, is a system event");
return;
}
@@ -231,6 +236,7 @@ public class NotificationListener extends NotificationListenerService {
}
if (GBApplication.blacklist != null && GBApplication.blacklist.contains(source)) {
+ LOG.info("Not forwarding notification, application is blacklisted");
return;
}
@@ -250,48 +256,7 @@ public class NotificationListener extends NotificationListenerService {
boolean preferBigText = false;
- switch (source) {
- case "org.mariotaku.twidere":
- case "com.twitter.android":
- case "org.andstatus.app":
- case "org.mustard.android":
- notificationSpec.type = NotificationType.TWITTER;
- break;
- case "com.fsck.k9":
- case "com.android.email":
- notificationSpec.type = NotificationType.GENERIC_EMAIL;
- preferBigText = true;
- break;
- case "com.moez.QKSMS":
- case "com.android.mms":
- case "com.android.messaging":
- case "com.sonyericsson.conversations":
- case "org.smssecure.smssecure":
- notificationSpec.type = NotificationType.GENERIC_SMS;
- break;
- case "eu.siacs.conversations":
- notificationSpec.type = NotificationType.CONVERSATIONS;
- break;
- case "org.thoughtcrime.securesms":
- notificationSpec.type = NotificationType.SIGNAL;
- break;
- case "org.telegram.messenger":
- notificationSpec.type = NotificationType.TELEGRAM;
- break;
- case "me.zeeroooo.materialfb":
- case "it.rignanese.leo.slimfacebook":
- case "me.jakelane.wrapperforfacebook":
- case "com.facebook.katana":
- case "org.indywidualni.fblite":
- notificationSpec.type = NotificationType.FACEBOOK;
- break;
- case "com.facebook.orca":
- notificationSpec.type = NotificationType.FACEBOOK_MESSENGER;
- break;
- default:
- notificationSpec.type = NotificationType.UNKNOWN;
- break;
- }
+ notificationSpec.type = AppNotificationType.getInstance().get(source);
LOG.info("Processing notification from source " + source);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
index 7fbee9bf..29f61d76 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
@@ -45,8 +45,10 @@ public class GBDevice implements Parcelable {
private static final String DEVINFO_FW_VER = "FW: ";
private static final String DEVINFO_HR_VER = "HR: ";
private static final String DEVINFO_ADDR = "ADDR: ";
+ private static final String DEVINFO_ADDR2 = "ADDR2: ";
private String mName;
private final String mAddress;
+ private String mVolatileAddress;
private final DeviceType mDeviceType;
private String mFirmwareVersion;
private String mFirmwareVersion2;
@@ -60,7 +62,12 @@ public class GBDevice implements Parcelable {
private List mDeviceInfos;
public GBDevice(String address, String name, DeviceType deviceType) {
+ this(address, null, name, deviceType);
+ }
+
+ public GBDevice(String address, String address2, String name, DeviceType deviceType) {
mAddress = address;
+ mVolatileAddress = address2;
mName = (name != null) ? name : mAddress;
mDeviceType = deviceType;
validate();
@@ -69,6 +76,7 @@ public class GBDevice implements Parcelable {
private GBDevice(Parcel in) {
mName = in.readString();
mAddress = in.readString();
+ mVolatileAddress = in.readString();
mDeviceType = DeviceType.values()[in.readInt()];
mFirmwareVersion = in.readString();
mFirmwareVersion2 = in.readString();
@@ -88,6 +96,7 @@ public class GBDevice implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeString(mAddress);
+ dest.writeString(mVolatileAddress);
dest.writeInt(mDeviceType.ordinal());
dest.writeString(mFirmwareVersion);
dest.writeString(mFirmwareVersion2);
@@ -123,6 +132,10 @@ public class GBDevice implements Parcelable {
return mAddress;
}
+ public String getVolatileAddress() {
+ return mVolatileAddress;
+ }
+
public String getFirmwareVersion() {
return mFirmwareVersion;
}
@@ -142,6 +155,10 @@ public class GBDevice implements Parcelable {
mFirmwareVersion2 = firmwareVersion2;
}
+ public void setVolatileAddress(String volatileAddress) {
+ mVolatileAddress = volatileAddress;
+ }
+
/**
* Returns the specific model/hardware revision of this device.
* This information is not always available, typically only when the device is initialized
@@ -240,7 +257,7 @@ public class GBDevice implements Parcelable {
}
/**
- * for simplicity the user wont see all internal states, just connecting -> connected
+ * for simplicity the user won't see all internal states, just connecting -> connected
* instead of connecting->connected->initializing->initialized
* Set simple to true to get this behavior.
*/
@@ -416,6 +433,9 @@ public class GBDevice implements Parcelable {
if (mAddress != null) {
result.add(new GenericItem(DEVINFO_ADDR, mAddress));
}
+ if (mVolatileAddress != null) {
+ result.add(new GenericItem(DEVINFO_ADDR2, mVolatileAddress));
+ }
Collections.sort(result);
return result;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java
index 7820bc2a..3696ec5d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java
@@ -4,17 +4,22 @@ import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
/**
* A device candidate is a Bluetooth device that is not yet managed by
@@ -26,21 +31,25 @@ public class GBDeviceCandidate implements Parcelable {
private final BluetoothDevice device;
private final short rssi;
+ private final ParcelUuid[] serviceUuds;
private DeviceType deviceType = DeviceType.UNKNOWN;
- public GBDeviceCandidate(BluetoothDevice device, short rssi) {
+ public GBDeviceCandidate(BluetoothDevice device, short rssi, ParcelUuid[] serviceUuds) {
this.device = device;
this.rssi = rssi;
+ this.serviceUuds = mergeServiceUuids(serviceUuds, device.getUuids());
}
private GBDeviceCandidate(Parcel in) {
device = in.readParcelable(getClass().getClassLoader());
+ if (device == null) {
+ throw new IllegalStateException("Unable to read state from Parcel");
+ }
rssi = (short) in.readInt();
deviceType = DeviceType.valueOf(in.readString());
- if (device == null || deviceType == null) {
- throw new IllegalStateException("Unable to read state from Parcel");
- }
+ ParcelUuid[] uuids = AndroidUtils.toParcelUUids(in.readParcelableArray(getClass().getClassLoader()));
+ serviceUuds = mergeServiceUuids(uuids, device.getUuids());
}
@Override
@@ -48,12 +57,29 @@ public class GBDeviceCandidate implements Parcelable {
dest.writeParcelable(device, 0);
dest.writeInt(rssi);
dest.writeString(deviceType.name());
+ dest.writeArray(serviceUuds);
}
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public GBDeviceCandidate createFromParcel(Parcel in) {
+ return new GBDeviceCandidate(in);
+ }
+
+ @Override
+ public GBDeviceCandidate[] newArray(int size) {
+ return new GBDeviceCandidate[size];
+ }
+ };
+
public BluetoothDevice getDevice() {
return device;
}
+ public void setDeviceType(DeviceType type) {
+ deviceType = type;
+ }
+
public DeviceType getDeviceType() {
return deviceType;
}
@@ -62,9 +88,25 @@ public class GBDeviceCandidate implements Parcelable {
return device != null ? device.getAddress() : GBApplication.getContext().getString(R.string._unknown_);
}
+ private ParcelUuid[] mergeServiceUuids(ParcelUuid[] serviceUuds, ParcelUuid[] deviceUuids) {
+ Set uuids = new HashSet<>();
+ if (serviceUuds != null) {
+ uuids.addAll(Arrays.asList(serviceUuds));
+ }
+ if (deviceUuids != null) {
+ uuids.addAll(Arrays.asList(deviceUuids));
+ }
+ return uuids.toArray(new ParcelUuid[0]);
+ }
+
+ @NonNull
+ public ParcelUuid[] getServiceUuids() {
+ return serviceUuds;
+ }
+
public boolean supportsService(UUID aService) {
- ParcelUuid[] uuids = device.getUuids();
- if (uuids == null) {
+ ParcelUuid[] uuids = getServiceUuids();
+ if (uuids.length == 0) {
LOG.warn("no cached services available for " + this);
return false;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
index a4673655..8fd47d31 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
@@ -281,6 +281,13 @@ public class GBDeviceService implements DeviceService {
invokeService(intent);
}
+ @Override
+ public void onSendConfiguration(String config) {
+ Intent intent = createIntent().setAction(ACTION_SEND_CONFIGURATION)
+ .putExtra(EXTRA_CONFIG, config);
+ invokeService(intent);
+ }
+
@Override
public void onTestNewFunction() {
Intent intent = createIntent().setAction(ACTION_TEST_NEW_FUNCTION);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java
new file mode 100644
index 00000000..001bb6cf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java
@@ -0,0 +1,58 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.HashMap;
+
+public class AppNotificationType extends HashMap {
+
+ private static AppNotificationType _instance;
+
+ public static AppNotificationType getInstance() {
+ if(_instance == null) {
+ return (_instance = new AppNotificationType());
+ }
+
+ return _instance;
+ }
+
+ private AppNotificationType() {
+ // Generic Email
+ put("com.fsck.k9", NotificationType.GENERIC_EMAIL);
+ put("com.android.email", NotificationType.GENERIC_EMAIL);
+
+ // Generic SMS
+ put("com.moez.QKSMS", NotificationType.GENERIC_SMS);
+ put("com.android.mms", NotificationType.GENERIC_SMS);
+ put("com.android.messaging", NotificationType.GENERIC_SMS);
+ put("com.sonyericsson.conversations", NotificationType.GENERIC_SMS);
+ put("org.smssecure.smssecure", NotificationType.GENERIC_SMS);
+
+ // Conversations
+ put("eu.siacs.conversations", NotificationType.CONVERSATIONS);
+
+ // Signal
+ put("org.thoughtcrime.securesms", NotificationType.SIGNAL);
+
+ // Telegram
+ put("org.telegram.messenger", NotificationType.TELEGRAM);
+
+ // Twitter
+ put("org.mariotaku.twidere", NotificationType.TWITTER);
+ put("com.twitter.android", NotificationType.TWITTER);
+ put("org.andstatus.app", NotificationType.TWITTER);
+ put("org.mustard.android", NotificationType.TWITTER);
+
+ // Facebook
+ put("me.zeeroooo.materialfb", NotificationType.FACEBOOK);
+ put("it.rignanese.leo.slimfacebook", NotificationType.FACEBOOK);
+ put("me.jakelane.wrapperforfacebook", NotificationType.FACEBOOK);
+ put("com.facebook.katana", NotificationType.FACEBOOK);
+ put("org.indywidualni.fblite", NotificationType.FACEBOOK);
+
+ // Facebook Messenger
+ put("com.facebook.orca", NotificationType.FACEBOOK_MESSENGER);
+
+ // WhatsApp
+ put("com.whatsapp", NotificationType.WHATSAPP);
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
index 6afa7e34..82fd3958 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
@@ -48,6 +48,7 @@ public interface DeviceService extends EventHandler {
String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement";
String ACTION_ADD_CALENDAREVENT = PREFIX + ".action.add_calendarevent";
String ACTION_DELETE_CALENDAREVENT = PREFIX + ".action.delete_calendarevent";
+ String ACTION_SEND_CONFIGURATION = PREFIX + ".action.send_configuration";
String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function";
String EXTRA_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body";
@@ -80,6 +81,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_APP_START = "app_start";
String EXTRA_APP_CONFIG = "app_config";
String EXTRA_URI = "uri";
+ String EXTRA_CONFIG = "config";
String EXTRA_ALARMS = "alarms";
String EXTRA_PERFORM_PAIR = "perform_pair";
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index 43223637..5c9d2663 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -12,6 +12,7 @@ public enum DeviceType {
MIBAND(10),
MIBAND2(11),
VIBRATISSIMO(20),
+ LIVEVIEW(30),
TEST(1000);
private final int key;
@@ -24,6 +25,10 @@ public enum DeviceType {
return key;
}
+ public boolean isSupported() {
+ return this != UNKNOWN;
+ }
+
public static DeviceType fromKey(int key) {
for (DeviceType type : values()) {
if (type.key == key) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java
index 48b56490..e3e32070 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java
@@ -1,24 +1,35 @@
package nodomain.freeyourgadget.gadgetbridge.model;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
+import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
+
public enum NotificationType {
- UNKNOWN,
+ UNKNOWN(PebbleIconID.NOTIFICATION_GENERIC, PebbleColor.Red),
- CONVERSATIONS,
- GENERIC_EMAIL,
- GENERIC_NAVIGATION,
- GENERIC_SMS,
- FACEBOOK,
- FACEBOOK_MESSENGER,
- SIGNAL,
- TWITTER,
- TELEGRAM;
+ CONVERSATIONS(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Inchworm),
+ GENERIC_EMAIL(PebbleIconID.GENERIC_EMAIL, PebbleColor.JaegerGreen),
+ GENERIC_NAVIGATION(PebbleIconID.LOCATION, PebbleColor.Orange),
+ GENERIC_SMS(PebbleIconID.GENERIC_SMS, PebbleColor.VividViolet),
+ FACEBOOK(PebbleIconID.NOTIFICATION_FACEBOOK, PebbleColor.Liberty),
+ FACEBOOK_MESSENGER(PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER, PebbleColor.VeryLightBlue),
+ SIGNAL(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon),
+ TWITTER(PebbleIconID.NOTIFICATION_TWITTER, PebbleColor.BlueMoon),
+ TELEGRAM(PebbleIconID.NOTIFICATION_TELEGRAM, PebbleColor.PictonBlue),
+ WHATSAPP(PebbleIconID.NOTIFICATION_WHATSAPP, PebbleColor.MayGreen);
+
+ public int icon;
+ public byte color;
+
+ NotificationType(int icon, byte color) {
+ this.icon = icon;
+ this.color = color;
+ }
/**
* Returns the enum constant as a fixed String value, e.g. to be used
* as preference key. In case the keys are ever changed, this method
* may be used to bring backward compatibility.
- * @return
*/
public String getFixedValue() {
return name().toLowerCase();
@@ -37,6 +48,7 @@ public enum NotificationType {
case FACEBOOK_MESSENGER:
case SIGNAL:
case TELEGRAM:
+ case WHATSAPP:
return "generic_chat";
case UNKNOWN:
default:
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
index 82222bcf..8c25a793 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -72,6 +72,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RE
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_CONFIGURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETCANNEDMESSAGES;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICSTATE;
@@ -96,6 +97,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAL
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
@@ -167,20 +169,25 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized());
setReceiversEnableState(enableReceivers);
GB.updateNotification(mGBDevice.getName() + " " + mGBDevice.getStateString(), mGBDevice.isInitialized(), context);
- if (device.isInitialized() && (device.getType() != DeviceType.VIBRATISSIMO)) {
+
+ if (device.isInitialized()) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
- if (DBHelper.findDevice(device, session) == null) {
+ boolean askForDBMigration = false;
+ if (DBHelper.findDevice(device, session) == null && device.getType() != DeviceType.VIBRATISSIMO && (device.getType() != DeviceType.LIVEVIEW)) {
+ askForDBMigration = true;
+ }
+ DBHelper.getDevice(device, session); // implicitly creates the device in database if not present, and updates device attributes
+ if (askForDBMigration) {
DBHelper dbHelper = new DBHelper(context);
if (dbHelper.getOldActivityDatabaseHandler() != null) {
- DBHelper.getDevice(device, session); // implicitly creates it :P
Intent startIntent = new Intent(context, OnboardingActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
startActivity(startIntent);
}
}
- } catch (Exception _ignore) {
+ } catch (Exception ignore) {
}
}
} else {
@@ -424,11 +431,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
break;
case ACTION_SETMUSICSTATE:
MusicStateSpec stateSpec = new MusicStateSpec();
- stateSpec.shuffle = intent.getByteExtra(EXTRA_MUSIC_SHUFFLE, (byte)0);
- stateSpec.repeat = intent.getByteExtra(EXTRA_MUSIC_REPEAT, (byte)0);
+ stateSpec.shuffle = intent.getByteExtra(EXTRA_MUSIC_SHUFFLE, (byte) 0);
+ stateSpec.repeat = intent.getByteExtra(EXTRA_MUSIC_REPEAT, (byte) 0);
stateSpec.position = intent.getIntExtra(EXTRA_MUSIC_POSITION, 0);
stateSpec.playRate = intent.getIntExtra(EXTRA_MUSIC_RATE, 0);
- stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte)0);
+ stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte) 0);
mDeviceSupport.onSetMusicState(stateSpec);
break;
case ACTION_REQUEST_APPINFO:
@@ -485,6 +492,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable);
break;
}
+ case ACTION_SEND_CONFIGURATION: {
+ String config = intent.getStringExtra(EXTRA_CONFIG);
+ mDeviceSupport.onSendConfiguration(config);
+ break;
+ }
case ACTION_TEST_NEW_FUNCTION: {
mDeviceSupport.onTestNewFunction();
break;
@@ -632,7 +644,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
setDeviceSupport(null);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(GB.NOTIFICATION_ID); // need to do this because the updated notification wont be cancelled when service stops
+ nm.cancel(GB.NOTIFICATION_ID); // need to do this because the updated notification won't be cancelled when service stops
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
index 4b05efa6..71c3dc8d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
@@ -9,9 +9,9 @@ import java.util.EnumSet;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
-import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
@@ -93,6 +93,9 @@ public class DeviceSupportFactory {
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;
}
if (deviceSupport != null) {
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
index 0772633d..a90ee63c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -320,6 +320,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onDeleteCalendarEvent(type, id);
}
+ @Override
+ public void onSendConfiguration(String config) {
+ if (checkBusy("send configuration: " + config)) {
+ return;
+ }
+ delegate.onSendConfiguration(config);
+ }
+
@Override
public void onTestNewFunction() {
if (checkBusy("test new function event")) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
index ab41d9a7..dc8da3f8 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
@@ -59,7 +59,7 @@ public abstract class AbstractBTLEOperation
* Subclasses must implement this. When invoked, #prePerform() returned
* successfully.
* Note that subclasses HAVE TO call #operationFinished() when the entire
- * opreation is done (successful or not).
+ * operation is done (successful or not).
*
* @throws IOException
*/
@@ -67,7 +67,7 @@ public abstract class AbstractBTLEOperation
/**
* You MUST call this method when the operation has finished, either
- * successfull or unsuccessfully.
+ * successfully or unsuccessfully.
*
* @throws IOException
*/
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
index ad36def4..90ccc4db 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
@@ -52,6 +52,42 @@ public class BLETypeConversions {
};
}
+ /**
+ * Similar to calendarToRawBytes, but only up to (and including) the MINUTES.
+ * @param timestamp
+ * @param honorDeviceTimeOffset
+ * @return
+ */
+ public static byte[] shortCalendarToRawBytes(Calendar timestamp, boolean honorDeviceTimeOffset) {
+
+ // The mi-band device currently records sleep
+ // only if it happens after 10pm and before 7am.
+ // The offset is used to trick the device to record sleep
+ // in non-standard hours.
+ // If you usually sleep, say, from 6am to 2pm, set the
+ // shift to -8, so at 6am the device thinks it's still 10pm
+ // of the day before.
+ if (honorDeviceTimeOffset) {
+ int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
+ if (offsetInHours != 0) {
+ timestamp.add(Calendar.HOUR_OF_DAY, offsetInHours);
+ }
+ }
+
+ // MiBand2:
+ // year,year,month,dayofmonth,hour,minute
+
+ byte[] year = fromUint16(timestamp.get(Calendar.YEAR));
+ return new byte[] {
+ year[0],
+ year[1],
+ fromUint8(timestamp.get(Calendar.MONTH) + 1),
+ fromUint8(timestamp.get(Calendar.DATE)),
+ fromUint8(timestamp.get(Calendar.HOUR_OF_DAY)),
+ fromUint8(timestamp.get(Calendar.MINUTE))
+ };
+ }
+
private static int getMiBand2TimeZone(int rawOffset) {
int offsetMinutes = rawOffset / 1000 / 60;
rawOffset = offsetMinutes < 0 ? -1 : 1;
@@ -82,11 +118,11 @@ public class BLETypeConversions {
int year = toUint16(value[0], value[1]);
GregorianCalendar timestamp = new GregorianCalendar(
year,
- value[2],
- value[3],
- value[4],
- value[5],
- value[6]
+ (value[2] & 0xff) - 1,
+ value[3] & 0xff,
+ value[4] & 0xff,
+ value[5] & 0xff,
+ value[6] & 0xff
);
if (honorDeviceTimeOffset) {
@@ -103,7 +139,7 @@ public class BLETypeConversions {
}
public static int toUint16(byte... bytes) {
- return bytes[0] | (bytes[1] << 8);
+ return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8);
}
public static byte[] fromUint16(int value) {
@@ -112,6 +148,24 @@ public class BLETypeConversions {
(byte) ((value >> 8) & 0xff),
};
}
+
+ public static byte[] fromUint24(int value) {
+ return new byte[] {
+ (byte) (value & 0xff),
+ (byte) ((value >> 8) & 0xff),
+ (byte) ((value >> 16) & 0xff),
+ };
+ }
+
+ public static byte[] fromUint32(int value) {
+ return new byte[] {
+ (byte) (value & 0xff),
+ (byte) ((value >> 8) & 0xff),
+ (byte) ((value >> 16) & 0xff),
+ (byte) ((value >> 24) & 0xff),
+ };
+ }
+
public static byte fromUint8(int value) {
return (byte) (value & 0xff);
}
@@ -156,7 +210,7 @@ public class BLETypeConversions {
/**
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.dst_offset.xml
- * @param Calendar
+ * @param now
* @return the DST offset for the given time; 0 if none; 255 if unknown
*/
public static byte mapDstOffset(Calendar now) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java
index faf35aa6..db2587c6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEAction.java
@@ -25,7 +25,7 @@ public abstract class BtLEAction {
}
/**
- * Returns true if this actions expects an (async) result which must
+ * Returns true if this action expects an (async) result which must
* be waited for, before continuing with other actions.
*
* This is needed because the current Bluedroid stack can only deal
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
index 49038370..f81285d9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
@@ -48,7 +48,7 @@ public final class BtLEQueue {
private final InternalGattCallback internalGattCallback;
private boolean mAutoReconnect;
- private Thread dispatchThread = new Thread("GadgetBridge GATT Dispatcher") {
+ private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
@Override
public void run() {
@@ -148,7 +148,7 @@ public final class BtLEQueue {
}
synchronized (mGattMonitor) {
if (mBluetoothGatt != null) {
- // Tribal knowledge says you're better off not reusing existing BlueoothGatt connections,
+ // Tribal knowledge says you're better off not reusing existing BluetoothGatt connections,
// so create a new one.
LOG.info("connect() requested -- disconnecting previous connection: " + mGbDevice.getName());
disconnect();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
index 79773031..4e5b4eb9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
@@ -9,7 +9,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
-import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattDescriptor.UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION;
@@ -20,7 +19,7 @@ import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattDescriptor.U
*/
public class NotifyAction extends BtLEAction {
- private static final Logger LOG = LoggerFactory.getLogger(TransactionBuilder.class);
+ private static final Logger LOG = LoggerFactory.getLogger(NotifyAction.class);
protected final boolean enableFlag;
private boolean hasWrittenDescriptor = true;
@@ -49,7 +48,7 @@ public class NotifyAction extends BtLEAction {
hasWrittenDescriptor = false;
}
} else {
- LOG.warn("sleep descriptor null");
+ LOG.warn("Descriptor CLIENT_CHARACTERISTIC_CONFIGURATION for characteristic " + getCharacteristic().getUuid() + " is null");
hasWrittenDescriptor = false;
}
} else {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
index 5cecee06..95e765f9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
@@ -4,6 +4,10 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
/**
@@ -12,6 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
* {@link BluetoothGattCallback}
*/
public class WriteAction extends BtLEAction {
+ private static final Logger LOG = LoggerFactory.getLogger(WriteAction.class);
private final byte[] value;
@@ -24,7 +29,7 @@ public class WriteAction extends BtLEAction {
public boolean run(BluetoothGatt gatt) {
BluetoothGattCharacteristic characteristic = getCharacteristic();
int properties = characteristic.getProperties();
- //TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this yelds to timing issues
+ //TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this leads to timing issues
if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0)) {
return writeValue(gatt, characteristic, value);
}
@@ -32,6 +37,9 @@ public class WriteAction extends BtLEAction {
}
protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("writing to characteristic: " + characteristic.getUuid() + ": " + Logging.formatBytes(value));
+ }
if (characteristic.setValue(value)) {
return gatt.writeCharacteristic(characteristic);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java
index 9dfcc01c..393a34db 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/AlertCategory.java
@@ -29,7 +29,7 @@ public enum AlertCategory {
/**
* Returns the numerical ID value of this category
- * To be used as uin8 value
+ * To be used as uint8 value
* @return the uint8 value for this category
*/
public int getId() {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java
index 4eb53a2d..325d6daf 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/SupportedNewAlertCategory.java
@@ -17,7 +17,7 @@ public class SupportedNewAlertCategory {
/**
* Returns the numerical ID value of this category
- * To be used as uin8 value
+ * To be used as uint8 value
* @return the uint8 value for this category
*/
public int getId() {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java
index 57a484e7..4b71c507 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/BodySensorLocation.java
@@ -7,7 +7,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate;
*/
public enum BodySensorLocation {
Other(0),
- Checst(1),
+ Chest(1),
Wrist(2),
Finger(3),
Hand(4),
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java
index f5495124..ec195fce 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/heartrate/HeartRateProfile.java
@@ -44,12 +44,6 @@ public class HeartRateProfile extends Abstr
}
- // TODO: I didn't find anything in the spec to request heart rate readings, so probably this
- // should be done in a device specific way.
- public void requestHeartRateMeasurement(TransactionBuilder builder) {
- writeToControlPoint(new byte[] { 0x15, 0x02, 0x01}, builder);
- }
-
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
@@ -61,7 +55,7 @@ public class HeartRateProfile extends Abstr
format = BluetoothGattCharacteristic.FORMAT_UINT8;
}
final int heartRate = characteristic.getIntValue(format, 1);
- GB.toast(getContext(), "Heart rate: " + heartRate, Toast.LENGTH_LONG, GB.INFO);
+ LOG.info("Heart rate: " + heartRate, Toast.LENGTH_LONG, GB.INFO);
}
return false;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java
new file mode 100644
index 00000000..2034a07f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewIoThread.java
@@ -0,0 +1,220 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class LiveviewIoThread extends GBDeviceIoThread {
+ private static final Logger LOG = LoggerFactory.getLogger(LiveviewIoThread.class);
+
+ private static final UUID SERIAL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+
+ private final LiveviewProtocol mLiveviewProtocol;
+ private final LiveviewSupport mLiveviewSupport;
+
+
+ private BluetoothAdapter mBtAdapter = null;
+ private BluetoothSocket mBtSocket = null;
+ private InputStream mInStream = null;
+ private OutputStream mOutStream = null;
+ private boolean mQuit = false;
+
+ @Override
+ public void quit() {
+ mQuit = true;
+ if (mBtSocket != null) {
+ try {
+ mBtSocket.close();
+ } catch (IOException e) {
+ LOG.error(e.getMessage());
+ }
+ }
+ }
+
+ private boolean mIsConnected = false;
+
+
+ public LiveviewIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol lvProtocol, LiveviewSupport lvSupport, BluetoothAdapter lvBtAdapter) {
+ super(gbDevice, context);
+ mLiveviewProtocol = (LiveviewProtocol) lvProtocol;
+ mBtAdapter = lvBtAdapter;
+ mLiveviewSupport = lvSupport;
+ }
+
+ @Override
+ public synchronized void write(byte[] bytes) {
+ if (null == bytes)
+ return;
+ LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
+ try {
+ mOutStream.write(bytes);
+ mOutStream.flush();
+ } catch (IOException e) {
+ LOG.error("Error writing.", e);
+ }
+ }
+
+ @Override
+ public void run() {
+ mIsConnected = connect();
+ if (!mIsConnected) {
+ setUpdateState(GBDevice.State.NOT_CONNECTED);
+ return;
+ }
+ mQuit = false;
+
+ while (!mQuit) {
+ LOG.info("Ready for a new message exchange.");
+
+ try {
+ GBDeviceEvent deviceEvents[] = mLiveviewProtocol.decodeResponse(parseIncoming());
+ if (deviceEvents == null) {
+ LOG.info("unhandled message");
+ } else {
+ for (GBDeviceEvent deviceEvent : deviceEvents) {
+ if (deviceEvent == null) {
+ continue;
+ }
+ mLiveviewSupport.evaluateGBDeviceEvent(deviceEvent);
+ }
+ }
+ } catch (SocketTimeoutException ignore) {
+ LOG.debug("socket timeout, we can't help but ignore this");
+ } catch (IOException e) {
+ LOG.info(e.getMessage());
+ mIsConnected = false;
+ mBtSocket = null;
+ mInStream = null;
+ mOutStream = null;
+ LOG.info("Bluetooth socket closed, will quit IO Thread");
+ break;
+ }
+ }
+
+ mIsConnected = false;
+ if (mBtSocket != null) {
+ try {
+ mBtSocket.close();
+ } catch (IOException e) {
+ LOG.error(e.getMessage());
+ }
+ mBtSocket = null;
+ }
+ setUpdateState(GBDevice.State.NOT_CONNECTED);
+ }
+
+ @Override
+ protected boolean connect() {
+ GBDevice.State originalState = gbDevice.getState();
+ setUpdateState(GBDevice.State.CONNECTING);
+
+ try {
+ BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
+ ParcelUuid uuids[] = btDevice.getUuids();
+ if (uuids == null) {
+ return false;
+ }
+ for (ParcelUuid uuid : uuids) {
+ LOG.info("found service UUID " + uuid);
+ }
+ mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
+ mBtSocket.connect();
+ mInStream = mBtSocket.getInputStream();
+ mOutStream = mBtSocket.getOutputStream();
+ setUpdateState(GBDevice.State.CONNECTED);
+ } catch (IOException e) {
+ LOG.error("Server socket cannot be started.");
+ //LOG.error(e.getMessage());
+ setUpdateState(originalState);
+ mInStream = null;
+ mOutStream = null;
+ mBtSocket = null;
+ return false;
+ }
+
+ write(mLiveviewProtocol.encodeSetTime());
+ setUpdateState(GBDevice.State.INITIALIZED);
+
+ return true;
+ }
+
+ private void setUpdateState(GBDevice.State state) {
+ gbDevice.setState(state);
+ gbDevice.sendDeviceUpdateIntent(getContext());
+ }
+
+ private byte[] parseIncoming() throws IOException {
+ ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
+
+ boolean finished = false;
+ ReaderState state = ReaderState.ID;
+ byte[] incoming = new byte[1];
+
+ while (!finished) {
+ mInStream.read(incoming);
+ msgStream.write(incoming);
+
+ switch (state) {
+ case ID:
+ state = ReaderState.HEADER_LEN;
+ incoming = new byte[1];
+ break;
+ case HEADER_LEN:
+ int headerSize = 0xff & incoming[0];
+ if (headerSize < 0)
+ throw new IOException();
+ state = ReaderState.HEADER;
+ incoming = new byte[headerSize];
+ break;
+ case HEADER:
+ int payloadSize = getLastInt(msgStream);
+ if (payloadSize < 0 || payloadSize > 8000) //this will possibly be changed in the future
+ throw new IOException();
+ state = ReaderState.PAYLOAD;
+ incoming = new byte[payloadSize];
+ break;
+ case PAYLOAD: //read is blocking, if we are here we have all the data
+ finished = true;
+ break;
+ }
+ }
+ byte[] msgArray = msgStream.toByteArray();
+ LOG.debug("received: " + GB.hexdump(msgArray, 0, msgArray.length));
+ return msgArray;
+ }
+
+
+ /**
+ * Enumeration containing the possible internal status of the reader.
+ */
+ private enum ReaderState {
+ ID, HEADER_LEN, HEADER, PAYLOAD;
+ }
+
+ private int getLastInt(ByteArrayOutputStream stream) {
+ byte[] array = stream.toByteArray();
+ ByteBuffer buffer = ByteBuffer.wrap(array, array.length - 4, 4);
+ buffer.order(LiveviewConstants.BYTE_ORDER);
+ return buffer.getInt();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java
new file mode 100644
index 00000000..27d38d7c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewProtocol.java
@@ -0,0 +1,132 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
+
+import java.nio.ByteBuffer;
+import java.util.Calendar;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
+import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class LiveviewProtocol extends GBDeviceProtocol {
+
+ @Override
+ public byte[] encodeFindDevice(boolean start) {
+ return encodeVibrateRequest((short) 100, (short) 200);
+ }
+
+ protected LiveviewProtocol(GBDevice device) {
+ super(device);
+ }
+
+ @Override
+ public GBDeviceEvent[] decodeResponse(byte[] responseData) {
+ int length = responseData.length;
+ if (length < 4) {
+ //empty message
+ return null;
+ } else {
+ ByteBuffer buffer = ByteBuffer.wrap(responseData, 0, length);
+ byte msgId = buffer.get();
+ buffer.get();
+ int payloadLen = buffer.getInt();
+ GBDeviceEventSendBytes reply = new GBDeviceEventSendBytes();
+ if (payloadLen + 6 == length) {
+ switch (msgId) {
+ case LiveviewConstants.MSG_DEVICESTATUS:
+ reply.encodedBytes = constructMessage(LiveviewConstants.MSG_DEVICESTATUS_ACK, new byte[]{LiveviewConstants.RESULT_OK});
+ break;
+ case LiveviewConstants.MSG_DISPLAYPANEL_ACK:
+ reply.encodedBytes = encodeVibrateRequest((short) 100, (short) 200); //hack to make the notifications vibrate!
+ break;
+ default:
+ }
+ GBDeviceEventSendBytes ack = new GBDeviceEventSendBytes();
+ ack.encodedBytes = constructMessage(LiveviewConstants.MSG_ACK, new byte[]{msgId});
+
+ return new GBDeviceEvent[]{ack, reply};
+ }
+ }
+
+
+ return super.decodeResponse(responseData);
+ }
+
+ @Override
+ public byte[] encodeSetTime() {
+ int time = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
+ time += Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 1000;
+ time += Calendar.getInstance().get(Calendar.DST_OFFSET) / 1000;
+ ByteBuffer buffer = ByteBuffer.allocate(5);
+ buffer.order(LiveviewConstants.BYTE_ORDER);
+ buffer.putInt(time);
+ buffer.put(LiveviewConstants.CLOCK_24H);
+ return constructMessage(LiveviewConstants.MSG_GETTIME_RESP, buffer.array());
+ }
+
+ @Override
+ public byte[] encodeNotification(NotificationSpec notificationSpec) {
+ String headerText;
+ // for SMS and EMAIL that came in though SMS or K9 receiver
+ if (notificationSpec.sender != null) {
+ headerText = notificationSpec.sender;
+ } else {
+ headerText = notificationSpec.title;
+ }
+
+ String footerText = (null != notificationSpec.sourceName) ? notificationSpec.sourceName : "";
+ String bodyText = (null != notificationSpec.body) ? notificationSpec.body : "";
+
+ byte[] headerTextArray = headerText.getBytes(LiveviewConstants.ENCODING);
+ byte[] footerTextArray = footerText.getBytes(LiveviewConstants.ENCODING);
+ byte[] bodyTextArray = bodyText.getBytes(LiveviewConstants.ENCODING);
+ int size = 15 + headerTextArray.length + bodyTextArray.length + footerTextArray.length;
+ ByteBuffer buffer = ByteBuffer.allocate(size);
+ buffer.put((byte) 1);
+ buffer.putShort((short) 0);
+ buffer.putShort((short) 0);
+ buffer.putShort((short) 0);
+ buffer.put((byte) 80); //should alert but it doesn't make the liveview vibrate
+
+ buffer.put((byte) 0); //0 is for plaintext vs bitmapimage (1) strings
+ buffer.putShort((short) headerTextArray.length);
+ buffer.put(headerTextArray);
+ buffer.putShort((short) bodyTextArray.length);
+ buffer.put(bodyTextArray);
+ buffer.putShort((short) footerTextArray.length);
+ buffer.put(footerTextArray);
+ return constructMessage(LiveviewConstants.MSG_DISPLAYPANEL, buffer.array());
+ }
+
+
+ //specific messages
+
+ public static byte[] constructMessage(byte messageType, byte[] payload) {
+ ByteBuffer msgBuffer = ByteBuffer.allocate(payload.length + 6);
+ msgBuffer.order(LiveviewConstants.BYTE_ORDER);
+ msgBuffer.put(messageType);
+ msgBuffer.put((byte) 4);
+ msgBuffer.putInt(payload.length);
+ msgBuffer.put(payload);
+ return msgBuffer.array();
+ }
+
+ public byte[] encodeVibrateRequest(short delay, short time) {
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ buffer.order(LiveviewConstants.BYTE_ORDER);
+ buffer.putShort(delay);
+ buffer.putShort(time);
+ return constructMessage(LiveviewConstants.MSG_SETVIBRATE, buffer.array());
+ }
+
+ public byte[] encodeCapabilitiesRequest() {
+ byte[] version = LiveviewConstants.CLIENT_SOFTWARE_VERSION.getBytes(LiveviewConstants.ENCODING);
+ ByteBuffer buffer = ByteBuffer.allocate(version.length + 1);
+ buffer.order(LiveviewConstants.BYTE_ORDER);
+ buffer.put((byte) version.length);
+ buffer.put(version);
+ return constructMessage(LiveviewConstants.MSG_GETCAPS, buffer.array());
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java
new file mode 100644
index 00000000..08842be7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java
@@ -0,0 +1,106 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
+
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
+import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
+
+public class LiveviewSupport extends AbstractSerialDeviceSupport {
+
+ @Override
+ public boolean connect() {
+ getDeviceIOThread().start();
+ return true;
+ }
+
+ @Override
+ protected GBDeviceProtocol createDeviceProtocol() {
+ return new LiveviewProtocol(getDevice());
+ }
+
+ @Override
+ protected GBDeviceIoThread createDeviceIOThread() {
+ return new LiveviewIoThread(getDevice(), getContext(), getDeviceProtocol(), LiveviewSupport.this, getBluetoothAdapter());
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return false;
+ }
+
+ @Override
+ public void onInstallApp(Uri uri) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onAppConfiguration(UUID uuid, String config) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onHeartRateTest() {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onSetConstantVibration(int intensity) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public synchronized LiveviewIoThread getDeviceIOThread() {
+ return (LiveviewIoThread) super.getDeviceIOThread();
+ }
+
+ @Override
+ public void onNotification(NotificationSpec notificationSpec) {
+ super.onNotification(notificationSpec);
+ }
+
+ @Override
+ public void onSetCallState(CallSpec callSpec) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onSetMusicState(MusicStateSpec musicStateSpec) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onSetMusicInfo(MusicSpec musicSpec) {
+ //nothing to do ATM
+ }
+
+
+ @Override
+ public void onSetAlarms(ArrayList extends Alarm> alarms) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onDeleteCalendarEvent(byte type, long id) {
+ //nothing to do ATM
+ }
+
+ @Override
+ public void onTestNewFunction() {
+ //nothing to do ATM
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java
index e274c9ce..a1a622b6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java
@@ -41,10 +41,12 @@ public abstract class AbstractMi1FirmwareInfo extends AbstractMiFirmwareInfo {
return 0;
}
+ @Override
public int getFirmwareLength() {
return wholeFirmwareBytes.length;
}
+ @Override
public int getFirmwareVersion() {
return (wholeFirmwareBytes[getOffsetFirmwareVersionMajor()] << 24)
| (wholeFirmwareBytes[getOffsetFirmwareVersionMinor()] << 16)
@@ -89,9 +91,10 @@ public abstract class AbstractMi1FirmwareInfo extends AbstractMiFirmwareInfo {
return false;
}
+ @Override
protected boolean isHeaderValid() {
// TODO: not sure if this is a correct check!
- return ArrayUtils.equals(SINGLE_FW_HEADER, wholeFirmwareBytes, SINGLE_FW_HEADER_OFFSET, SINGLE_FW_HEADER_OFFSET + SINGLE_FW_HEADER.length);
+ return ArrayUtils.equals(wholeFirmwareBytes, SINGLE_FW_HEADER, SINGLE_FW_HEADER_OFFSET);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
index c1c36258..3ba149df 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
@@ -83,7 +83,7 @@ public class DeviceInfo extends AbstractInfo {
}
public boolean supportsHeartrate() {
- return isMiliPro() || isMili1S() || (test1AHRMode && isMili1A());
+ return isMili1S() || (test1AHRMode && isMili1A());
}
@Override
@@ -116,10 +116,6 @@ public class DeviceInfo extends AbstractInfo {
return hwVersion == 6;
}
- public boolean isMiliPro() {
- return hwVersion == 8 || (feature == 8 && appearance == 0);
- }
-
public String getHwVersion() {
if (isMili1()) {
return MiBandConst.MI_1;
@@ -133,9 +129,6 @@ public class DeviceInfo extends AbstractInfo {
if (isAmazFit()) {
return MiBandConst.MI_AMAZFIT;
}
- if (isMiliPro()) {
- return MiBandConst.MI_PRO;
- }
return "?";
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java
index 8ef2e65d..9a5a80ff 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java
@@ -38,9 +38,10 @@ public class Mi1SFirmwareInfo extends CompositeMiFirmwareInfo {
return false;
}
+ @Override
protected boolean isHeaderValid() {
// TODO: not sure if this is a correct check!
- return ArrayUtils.equals(DOUBLE_FW_HEADER, wholeFirmwareBytes, DOUBLE_FW_HEADER_OFFSET, DOUBLE_FW_HEADER_OFFSET + DOUBLE_FW_HEADER.length);
+ return ArrayUtils.equals(wholeFirmwareBytes, DOUBLE_FW_HEADER, DOUBLE_FW_HEADER_OFFSET);
}
@Nullable
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
index 35cf1e0e..45fcc9fd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
@@ -20,7 +20,6 @@ import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
-import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
@@ -1190,6 +1189,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
+ @Override
+ public void onSendConfiguration(String config) {
+ // nothing yet
+ }
+
@Override
public void onTestNewFunction() {
try {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java
new file mode 100644
index 00000000..00c1d7ba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBand1Operation.java
@@ -0,0 +1,17 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
+
+public abstract class AbstractMiBand1Operation extends AbstractMiBandOperation {
+ protected AbstractMiBand1Operation(MiBandSupport support) {
+ super(support);
+ }
+
+ @Override
+ protected void enableOtherNotifications(TransactionBuilder builder, boolean enable) {
+ builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
+ .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java
index 971a63d4..f30a9268 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/AbstractMiBandOperation.java
@@ -4,23 +4,23 @@ import android.widget.Toast;
import java.io.IOException;
-import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
-public abstract class AbstractMiBandOperation extends AbstractBTLEOperation {
- protected AbstractMiBandOperation(MiBandSupport support) {
+public abstract class AbstractMiBandOperation extends AbstractBTLEOperation {
+ protected AbstractMiBandOperation(T support) {
super(support);
}
@Override
protected void prePerform() throws IOException {
super.prePerform();
- getDevice().setBusyTask("fetch activity data"); // mark as busy quickly to avoid interruptions from the outside
+ getDevice().setBusyTask("Operation starting..."); // mark as busy quickly to avoid interruptions from the outside
TransactionBuilder builder = performInitialized("disabling some notifications");
enableOtherNotifications(builder, false);
+ enableNeededNotifications(builder, true);
builder.queue(getQueue());
}
@@ -31,7 +31,7 @@ public abstract class AbstractMiBandOperation extends AbstractBTLEOperation {
+ protected AbstractMiBand2Operation(MiBand2Support support) {
+ super(support);
+ }
+
+ @Override
+ protected void enableOtherNotifications(TransactionBuilder builder, boolean enable) {
+ // TODO: check which notifications we should disable and re-enable here
+// builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
+// .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/BatteryInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/BatteryInfo.java
new file mode 100644
index 00000000..8b44c874
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/BatteryInfo.java
@@ -0,0 +1,95 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractInfo;
+
+//00000006-0000-3512-2118-0009af100700
+//
+// f = ?
+// 30 = 48%
+// 00 = 00 = STATUS_NORMAL, 01 = STATUS_CHARGING
+// e0 07 = 2016
+// 0b = 11
+// 1a = 26
+// 12 = 18
+// 23 = 35
+// 2c = 44
+// 04 = 4 // num charges??
+//
+// e0 07 = 2016 // last charge time
+// 0b = 11
+// 1a = 26
+// 17 = 23
+// 2b = 43
+// 3b = 59
+// 04 = 4 // num charges??
+// 64 = 100 // how much was charged
+
+public class BatteryInfo extends AbstractInfo {
+ public static final byte DEVICE_BATTERY_NORMAL = 0;
+ public static final byte DEVICE_BATTERY_CHARGING = 1;
+// public static final byte DEVICE_BATTERY_LOW = 1;
+// public static final byte DEVICE_BATTERY_CHARGING_FULL = 3;
+// public static final byte DEVICE_BATTERY_CHARGE_OFF = 4;
+
+ public BatteryInfo(byte[] data) {
+ super(data);
+ }
+
+ public int getLevelInPercent() {
+ if (mData.length >= 2) {
+ return mData[1];
+ }
+ return 50; // actually unknown
+ }
+
+ public BatteryState getState() {
+ if (mData.length >= 3) {
+ int value = mData[2];
+ switch (value) {
+ case DEVICE_BATTERY_NORMAL:
+ return BatteryState.BATTERY_NORMAL;
+ case DEVICE_BATTERY_CHARGING:
+ return BatteryState.BATTERY_CHARGING;
+// case DEVICE_BATTERY_CHARGING:
+// return BatteryState.BATTERY_CHARGING;
+// case DEVICE_BATTERY_CHARGING_FULL:
+// return BatteryState.BATTERY_CHARGING_FULL;
+// case DEVICE_BATTERY_CHARGE_OFF:
+// return BatteryState.BATTERY_NOT_CHARGING_FULL;
+ }
+ }
+ return BatteryState.UNKNOWN;
+ }
+
+ public int getLastChargeLevelInParcent() {
+ if (mData.length >= 20) {
+ return mData[19];
+ }
+ return 50; // actually unknown
+ }
+
+ public GregorianCalendar getLastChargeTime() {
+ GregorianCalendar lastCharge = MiBandDateConverter.createCalendar();
+
+ if (mData.length >= 18) {
+ lastCharge = BLETypeConversions.rawBytesToCalendar(new byte[]{
+ mData[10], mData[11], mData[12], mData[13], mData[14], mData[15], mData[16], mData[17]
+ }, true);
+ }
+
+ return lastCharge;
+ }
+
+ public int getNumCharges() {
+// if (mData.length >= 10) {
+// return ((0xff & mData[7]) | ((0xff & mData[8]) << 8));
+//
+// }
+ return -1;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java
new file mode 100644
index 00000000..4dbf595e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java
@@ -0,0 +1,87 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
+
+public class Mi2FirmwareInfo {
+ private static final byte[] FW_HEADER = new byte[]{
+ (byte) 0xa3,
+ (byte) 0x68,
+ (byte) 0x04,
+ (byte) 0x3b,
+ (byte) 0x02,
+ (byte) 0xdb,
+ (byte) 0xc8,
+ (byte) 0x58,
+ (byte) 0xd0,
+ (byte) 0x50,
+ (byte) 0xfa,
+ (byte) 0xe7,
+ (byte) 0x0c,
+ (byte) 0x34,
+ (byte) 0xf3,
+ (byte) 0xe7,
+ };
+ private static final int FW_HEADER_OFFSET = 0x150;
+
+ private static Map crcToVersion = new HashMap<>();
+ static {
+ crcToVersion.put(41899, "1.0.0.39");
+ }
+
+ public static String toVersion(int crc16) {
+ return crcToVersion.get(crc16);
+ }
+
+ public static int[] getWhitelistedVersions() {
+ return ArrayUtils.toIntArray(crcToVersion.keySet());
+ }
+
+ private final int crc16;
+
+ private byte[] bytes;
+ private String firmwareVersion;
+
+ public Mi2FirmwareInfo(byte[] bytes) {
+ this.bytes = bytes;
+ crc16 = CheckSums.getCRC16(bytes);
+ firmwareVersion = crcToVersion.get(crc16);
+ }
+
+ public boolean isGenerallyCompatibleWith(GBDevice device) {
+ return isHeaderValid() && device.getType() == DeviceType.MIBAND2;
+ }
+
+ public boolean isHeaderValid() {
+ // TODO: this is certainly not a correct validation, but it works for now
+ return ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET);
+ }
+
+ public void checkValid() throws IllegalArgumentException {
+ }
+
+ /**
+ * Returns the size of the firmware in number of bytes.
+ * @return
+ */
+ public int getSize() {
+ return bytes.length;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public int getCrc16() {
+ return crc16;
+ }
+
+ public int getFirmwareVersion() {
+ return getCrc16(); // HACK until we know how to determine the version from the fw bytes
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2NotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2NotificationStrategy.java
index 7d00d02e..3cfed0a9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2NotificationStrategy.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2NotificationStrategy.java
@@ -7,7 +7,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSuppo
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.V2NotificationStrategy;
public class Mi2NotificationStrategy extends V2NotificationStrategy {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java
similarity index 72%
rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java
rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java
index 20de3193..e1667f6f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBand2Support.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java
@@ -1,4 +1,4 @@
-package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
@@ -10,6 +10,7 @@ import android.net.Uri;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
+import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,25 +18,35 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
-import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
-import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
@@ -53,13 +64,18 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
-import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2NotificationStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.CheckAuthenticationNeededAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.InitOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -106,6 +122,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
+ private RealtimeSamplesSupport realtimeSamplesSupport;
public MiBand2Support() {
super(LOG);
@@ -118,10 +135,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
addSupportedService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
+ addSupportedService(MiBand2Service.UUID_SERVICE_FIRMWARE_SERVICE);
deviceInfoProfile = new DeviceInfoProfile<>(this);
addSupportedProfile(deviceInfoProfile);
- heartRateProfile = new HeartRateProfile(this);
+ heartRateProfile = new HeartRateProfile<>(this);
addSupportedProfile(heartRateProfile);
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
@@ -170,19 +188,31 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
return builder;
}
-// private MiBand2Support maybeAuth(TransactionBuilder builder) {
-// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x20, 0x00});
-// builder.write(getCharacteristic(MiBand2Service.UUID_UNKNOQN_CHARACTERISTIC0), new byte[] {0x03,0x00,(byte)0x8e,(byte)0xce,0x5a,0x09,(byte)0xb3,(byte)0xd8,0x55,0x57,0x10,0x2a,(byte)0xed,0x7d,0x6b,0x78,(byte)0xc5,(byte)0xd2});
-// return this;
-// }
+ public byte[] getTimeBytes(Calendar calendar, TimeUnit precision) {
+ byte[] bytes;
+ if (precision == TimeUnit.MINUTES) {
+ bytes = BLETypeConversions.shortCalendarToRawBytes(calendar, true);
+ } else if (precision == TimeUnit.SECONDS) {
+ bytes = BLETypeConversions.calendarToRawBytes(calendar, true);
+ } else {
+ throw new IllegalArgumentException("Unsupported precision, only MINUTES and SECONDS are supported till now");
+ }
+ byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
+// byte[] tail = new byte[] { 0x2 }; // reason
+ byte[] all = BLETypeConversions.join(bytes, tail);
+ return all;
+ }
+
+ public Calendar fromTimeBytes(byte[] bytes) {
+ GregorianCalendar timestamp = BLETypeConversions.rawBytesToCalendar(bytes, true);
+ return timestamp;
+ }
public MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) {
GregorianCalendar now = BLETypeConversions.createCalendar();
- byte[] bytes = BLETypeConversions.calendarToRawBytes(now, true);
- byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(now.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
-// byte[] tail = new byte[] { 0x2 }; // reason
- byte[] all = BLETypeConversions.join(bytes, tail);
- builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), all);
+ byte[] bytes = getTimeBytes(now, TimeUnit.SECONDS);
+ builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), bytes);
+
// byte[] localtime = BLETypeConversions.calendarToLocalTimeBytes(now);
// builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_LOCAL_TIME_INFORMATION), localtime);
// builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), new byte[] {0x2, 0x00});
@@ -232,18 +262,15 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
builder.notify(getCharacteristic(GattService.UUID_SERVICE_CURRENT_TIME), enable);
// Notify CHARACTERISTIC9 to receive random auth code
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_AUTH), enable);
- builder.notify(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC3), enable);
- builder.notify(getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4), enable);
- builder.notify(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT), enable);
return this;
}
- private MiBand2Support enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
- builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
- .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA), enable)
- .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_BATTERY), enable)
- .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
- // cannot use supportsHeartrate() here because we don't have that information yet
+ public MiBand2Support enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
+// builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS), enable)
+// .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_ACTIVITY_DATA), enable)
+// .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable);
+ builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
+ builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT);
if (heartrateCharacteristic != null) {
builder.notify(heartrateCharacteristic, enable);
@@ -340,6 +367,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
return this;
}
+ private MiBand2Support requestBatteryInfo(TransactionBuilder builder) {
+ LOG.debug("Requesting Battery Info!");
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO);
+ builder.read(characteristic);
+ return this;
+ }
+
public MiBand2Support requestDeviceInfo(TransactionBuilder builder) {
LOG.debug("Requesting Device Info!");
deviceInfoProfile.requestDeviceInfo(builder);
@@ -380,15 +414,15 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
private MiBand2Support setFitnessGoal(TransactionBuilder transaction) {
LOG.info("Attempting to set Fitness Goal...");
- BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC8);
if (characteristic != null) {
int fitnessGoal = MiBandCoordinator.getFitnessGoal(getDevice().getAddress());
- transaction.write(characteristic, new byte[]{
- MiBandService.COMMAND_SET_FITNESS_GOAL,
- 0,
- (byte) (fitnessGoal & 0xff),
- (byte) ((fitnessGoal >>> 8) & 0xff)
- });
+ byte[] bytes = ArrayUtils.addAll(
+ MiBand2Service.COMMAND_SET_FITNESS_GOAL_START,
+ BLETypeConversions.fromUint16(fitnessGoal));
+ bytes = ArrayUtils.addAll(bytes,
+ MiBand2Service.COMMAND_SET_FITNESS_GOAL_END);
+ transaction.write(characteristic, bytes);
} else {
LOG.info("Unable to set Fitness Goal");
}
@@ -398,28 +432,24 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
/**
* Part of device initialization process. Do not call manually.
*
- * @param transaction
+ * @param builder
* @return
*/
- private MiBand2Support setWearLocation(TransactionBuilder transaction) {
+ private MiBand2Support setWearLocation(TransactionBuilder builder) {
LOG.info("Attempting to set wear location...");
- BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC8);
if (characteristic != null) {
- transaction.add(new ConditionalWriteAction(characteristic) {
- @Override
- protected byte[] checkCondition() {
- if (getDeviceInfo() != null && getDeviceInfo().isAmazFit()) {
- return null;
- }
- int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
- return new byte[]{
- MiBandService.COMMAND_SET_WEAR_LOCATION,
- (byte) location
- };
- }
- });
- } else {
- LOG.info("Unable to set Wear Location");
+ builder.notify(characteristic, true);
+ int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
+ switch (location) {
+ case 0: // left hand
+ builder.write(characteristic, MiBand2Service.WEAR_LOCATION_LEFT_WRIST);
+ break;
+ case 1: // right hand
+ builder.write(characteristic, MiBand2Service.WEAR_LOCATION_RIGHT_WRIST);
+ break;
+ }
+ builder.notify(characteristic, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
}
@@ -451,23 +481,18 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
* @param builder
*/
private MiBand2Support setHeartrateSleepSupport(TransactionBuilder builder) {
- BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
- if (characteristic != null) {
- builder.add(new ConditionalWriteAction(characteristic) {
- @Override
- protected byte[] checkCondition() {
- if (!supportsHeartRate()) {
- return null;
- }
- if (MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress())) {
- LOG.info("Enabling heartrate sleep support...");
- return startHeartMeasurementSleep;
- } else {
- LOG.info("Disabling heartrate sleep support...");
- return stopHeartMeasurementSleep;
- }
- }
- });
+ BluetoothGattCharacteristic characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
+ final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress());
+ if (characteristicHRControlPoint != null) {
+ builder.notify(characteristicHRControlPoint, true);
+ if (enableHrSleepSupport) {
+ LOG.info("Enabling heartrate sleep support...");
+ builder.write(characteristicHRControlPoint, MiBand2Service.COMMAND_ENABLE_HR_SLEEP_MEASUREMENT);
+ } else {
+ LOG.info("Disabling heartrate sleep support...");
+ builder.write(characteristicHRControlPoint, MiBand2Service.COMMAND_DISABLE_HR_SLEEP_MEASUREMENT);
+ }
+ builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
}
@@ -541,7 +566,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onSetAlarms(ArrayList extends Alarm> alarms) {
try {
- BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC3);
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION);
TransactionBuilder builder = performInitialized("Set alarm");
boolean anyAlarmEnabled = false;
for (Alarm alarm : alarms) {
@@ -573,46 +598,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
public void onSetTime() {
try {
TransactionBuilder builder = performInitialized("Set date and time");
- setCurrentTime(builder);
+ setCurrentTimeWithService(builder);
+ //TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
+ sendCalendarEvents(builder);
builder.queue(getQueue());
} catch (IOException ex) {
LOG.error("Unable to set time on MI device", ex);
}
- //TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
- sendCalendarEvents();
- }
-
- /**
- * Sets the current time to the Mi device using the given builder.
- *
- * @param builder
- */
- private MiBand2Support setCurrentTime(TransactionBuilder builder) {
- Calendar now = GregorianCalendar.getInstance();
- Date date = now.getTime();
- LOG.info("Sending current time to Mi Band: " + DateTimeUtils.formatDate(date) + " (" + date.toGMTString() + ")");
- byte[] nowBytes = MiBandDateConverter.calendarToRawBytes(now);
- byte[] time = new byte[]{
- nowBytes[0],
- nowBytes[1],
- nowBytes[2],
- nowBytes[3],
- nowBytes[4],
- nowBytes[5],
- (byte) 0x0f,
- (byte) 0x0f,
- (byte) 0x0f,
- (byte) 0x0f,
- (byte) 0x0f,
- (byte) 0x0f
- };
- BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_DATE_TIME);
- if (characteristic != null) {
- builder.write(characteristic, time);
- } else {
- LOG.info("Unable to set time -- characteristic not available");
- }
- return this;
}
@Override
@@ -675,45 +667,34 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onHeartRateTest() {
- if (supportsHeartRate()) {
- try {
- TransactionBuilder builder = performInitialized("HeartRateTest");
- heartRateProfile.requestHeartRateMeasurement(builder);
-// profile.resetEnergyExpended(builder);
-// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
-// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
-// builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
- builder.queue(getQueue());
- } catch (IOException ex) {
- LOG.error("Unable to read HearRate with MI2", ex);
- }
- } else {
- GB.toast(getContext(), "Heart rate is not supported on this device", Toast.LENGTH_LONG, GB.ERROR);
+ try {
+ TransactionBuilder builder = performInitialized("HeartRateTest");
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
+ builder.queue(getQueue());
+ } catch (IOException ex) {
+ LOG.error("Unable to read HearRate with MI2", ex);
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
- if (supportsHeartRate()) {
- try {
- TransactionBuilder builder = performInitialized("EnableRealtimeHeartRateMeasurement");
- if (enable) {
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
- } else {
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
- }
- builder.queue(getQueue());
- } catch (IOException ex) {
- LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
+ try {
+ TransactionBuilder builder = performInitialized("Enable realtime heart rateM measurement");
+ if (enable) {
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
+ } else {
+ builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
}
+ builder.queue(getQueue());
+ enableRealtimeSamplesTimer(enable);
+ } catch (IOException ex) {
+ LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
}
}
- public boolean supportsHeartRate() {
- return true;
- }
-
@Override
public void onFindDevice(boolean start) {
isLocatingDevice = start;
@@ -736,28 +717,28 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onFetchActivityData() {
-// TODO: onFetchActivityData
-// try {
-// new FetchActivityOperation(this).perform();
-// } catch (IOException ex) {
-// LOG.error("Unable to fetch MI activity data", ex);
-// }
+ try {
+ new FetchActivityOperation(this).perform();
+ } catch (IOException ex) {
+ LOG.error("Unable to fetch MI activity data", ex);
+ }
}
@Override
public void onEnableRealtimeSteps(boolean enable) {
- try {
- BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
- if (enable) {
- TransactionBuilder builder = performInitialized("Read realtime steps");
- builder.read(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS)).queue(getQueue());
- }
- performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications")
- .write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), enable ? getLowLatency() : getHighLatency())
- .write(controlPoint, enable ? startRealTimeStepsNotifications : stopRealTimeStepsNotifications).queue(getQueue());
- } catch (IOException e) {
- LOG.error("Unable to change realtime steps notification to: " + enable, e);
- }
+// try {
+// BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+// if (enable) {
+// TransactionBuilder builder = performInitialized("Read realtime steps");
+// builder.read(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS)).queue(getQueue());
+// }
+// performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications")
+// .write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_LE_PARAMS), enable ? getLowLatency() : getHighLatency())
+// .write(controlPoint, enable ? startRealTimeStepsNotifications : stopRealTimeStepsNotifications).queue(getQueue());
+// enableRealtimeSamplesTimer(enable);
+// } catch (IOException e) {
+// LOG.error("Unable to change realtime steps notification to: " + enable, e);
+// }
}
private byte[] getHighLatency() {
@@ -800,12 +781,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onInstallApp(Uri uri) {
-// TODO: onInstallApp (firmware update)
-// try {
-// new UpdateFirmwareOperation(uri, this).perform();
-// } catch (IOException ex) {
-// GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
-// }
+ try {
+ new UpdateFirmwareOperation(uri, this).perform();
+ } catch (IOException ex) {
+ GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+ }
}
@Override
@@ -844,7 +824,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
super.onCharacteristicChanged(gatt, characteristic);
UUID characteristicUUID = characteristic.getUuid();
- if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
+ if (MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
handleBatteryInfo(characteristic.getValue(), BluetoothGatt.GATT_SUCCESS);
return true;
} else if (MiBandService.UUID_CHARACTERISTIC_NOTIFICATION.equals(characteristicUUID)) {
@@ -880,13 +860,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
super.onCharacteristicRead(gatt, characteristic, status);
UUID characteristicUUID = characteristic.getUuid();
- if (MiBandService.UUID_CHARACTERISTIC_DEVICE_INFO.equals(characteristicUUID)) {
- handleDeviceInfo(characteristic.getValue(), status);
- return true;
- } else if (GattCharacteristic.UUID_CHARACTERISTIC_GAP_DEVICE_NAME.equals(characteristicUUID)) {
+ if (GattCharacteristic.UUID_CHARACTERISTIC_GAP_DEVICE_NAME.equals(characteristicUUID)) {
handleDeviceName(characteristic.getValue(), status);
return true;
- } else if (MiBandService.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
+ } else if (MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
handleBatteryInfo(characteristic.getValue(), status);
return true;
} else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
@@ -935,7 +912,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
public void logHeartrate(byte[] value, int status) {
if (status == BluetoothGatt.GATT_SUCCESS && value != null) {
LOG.info("Got heartrate:");
- if (value.length == 2 && value[0] == 6) {
+ if (value.length == 2 && value[0] == 0) {
int hrValue = (value[1] & 0xff);
GB.toast(getContext(), "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
}
@@ -945,15 +922,17 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
}
private void handleHeartrate(byte[] value) {
- if (value.length == 2 && value[0] == 6) {
+ if (value.length == 2 && value[0] == 0) {
int hrValue = (value[1] & 0xff);
if (LOG.isDebugEnabled()) {
LOG.debug("heart rate: " + hrValue);
}
- Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
- .putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue)
- .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
- LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+ RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport();
+ realtimeSamplesSupport.setHeartrateBpm(hrValue);
+ if (!realtimeSamplesSupport.isRunning()) {
+ // single shot measurement, manually invoke storage and result publishing
+ realtimeSamplesSupport.triggerCurrentSample();
+ }
}
}
@@ -962,10 +941,75 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
if (LOG.isDebugEnabled()) {
LOG.debug("realtime steps: " + steps);
}
- Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS)
- .putExtra(DeviceService.EXTRA_REALTIME_STEPS, steps)
- .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
- LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+ getRealtimeSamplesSupport().setSteps(steps);
+ }
+
+ private void enableRealtimeSamplesTimer(boolean enable) {
+ if (enable) {
+ getRealtimeSamplesSupport().start();
+ } else {
+ if (realtimeSamplesSupport != null) {
+ realtimeSamplesSupport.stop();
+ }
+ }
+ }
+
+ public MiBandActivitySample createActivitySample(Device device, User user, int timestampInSeconds, SampleProvider provider) {
+ MiBandActivitySample sample = new MiBandActivitySample();
+ sample.setDevice(device);
+ sample.setUser(user);
+ sample.setTimestamp(timestampInSeconds);
+ sample.setProvider(provider);
+
+ return sample;
+ }
+
+ private RealtimeSamplesSupport getRealtimeSamplesSupport() {
+ if (realtimeSamplesSupport == null) {
+ realtimeSamplesSupport = new RealtimeSamplesSupport(1000, 1000) {
+ @Override
+ public void doCurrentSample() {
+
+ try (DBHandler handler = GBApplication.acquireDB()) {
+ DaoSession session = handler.getDaoSession();
+
+ Device device = DBHelper.getDevice(getDevice(), session);
+ User user = DBHelper.getUser(session);
+ int ts = (int) (System.currentTimeMillis() / 1000);
+ MiBand2SampleProvider provider = new MiBand2SampleProvider(gbDevice, session);
+ MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
+ sample.setHeartRate(getHeartrateBpm());
+ sample.setSteps(getSteps());
+ sample.setRawIntensity(ActivitySample.NOT_MEASURED);
+ sample.setRawKind(MiBand2SampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
+
+ // TODO: remove this once fully ported to REALTIME_SAMPLES
+ if (sample.getSteps() != ActivitySample.NOT_MEASURED) {
+ Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS)
+ .putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps())
+ .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
+ LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+ }
+ if (sample.getHeartRate() != ActivitySample.NOT_MEASURED) {
+ Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
+ .putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, sample.getHeartRate())
+ .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
+ LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+ }
+
+// Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+// .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
+
+ LOG.debug("Storing realtime sample: " + sample);
+ provider.addGBActivitySample(sample);
+ } catch (Exception e) {
+ LOG.warn("Unable to acquire db for saving realtime samples", e);
+ }
+ }
+ };
+ }
+ return realtimeSamplesSupport;
}
/**
@@ -1028,19 +1072,6 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
}
}
- private void handleDeviceInfo(byte[] value, int status) {
- if (status == BluetoothGatt.GATT_SUCCESS) {
- mDeviceInfo = new DeviceInfo(value);
- if (getDeviceInfo().supportsHeartrate()) {
- getDevice().setFirmwareVersion2(MiBandFWHelper.formatFirmwareVersion(mDeviceInfo.getHeartrateFirmwareVersion()));
- }
- LOG.warn("Device info: " + mDeviceInfo);
- versionCmd.hwVersion = mDeviceInfo.getHwVersion();
- versionCmd.fwVersion = MiBandFWHelper.formatFirmwareVersion(mDeviceInfo.getFirmwareVersion());
- handleGBDeviceEvent(versionCmd);
- }
- }
-
private void handleDeviceName(byte[] value, int status) {
// if (status == BluetoothGatt.GATT_SUCCESS) {
// versionCmd.hwVersion = new String(value);
@@ -1165,39 +1196,94 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
/**
* Fetch the events from the android device calendars and set the alarms on the miband.
+ * @param builder
*/
- private void sendCalendarEvents() {
- try {
- TransactionBuilder builder = performInitialized("Send upcoming events");
- BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC3);
+ private MiBand2Support sendCalendarEvents(TransactionBuilder builder) {
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION);
- Prefs prefs = GBApplication.getPrefs();
- int availableSlots = prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
+ Prefs prefs = GBApplication.getPrefs();
+ int availableSlots = prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
- if (availableSlots > 0) {
- CalendarEvents upcomingEvents = new CalendarEvents();
- List mEvents = upcomingEvents.getCalendarEventList(getContext());
+ if (availableSlots > 0) {
+ CalendarEvents upcomingEvents = new CalendarEvents();
+ List mEvents = upcomingEvents.getCalendarEventList(getContext());
- int iteration = 0;
- for (CalendarEvents.CalendarEvent mEvt : mEvents) {
- if (iteration >= availableSlots || iteration > 2) {
- break;
- }
- int slotToUse = 2 - iteration;
- Calendar calendar = Calendar.getInstance();
- calendar.setTimeInMillis(mEvt.getBegin());
- Alarm alarm = GBAlarm.createSingleShot(slotToUse, false, calendar);
- queueAlarm(alarm, builder, characteristic);
- iteration++;
+ int iteration = 0;
+ for (CalendarEvents.CalendarEvent mEvt : mEvents) {
+ if (iteration >= availableSlots || iteration > 2) {
+ break;
}
- builder.queue(getQueue());
+ int slotToUse = 2 - iteration;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(mEvt.getBegin());
+ Alarm alarm = GBAlarm.createSingleShot(slotToUse, false, calendar);
+ queueAlarm(alarm, builder, characteristic);
+ iteration++;
}
- } catch (IOException ex) {
- LOG.error("Unable to send Events to MI device", ex);
+ builder.queue(getQueue());
+ }
+ return this;
+ }
+
+ @Override
+ public void onSendConfiguration(String config) {
+ TransactionBuilder builder = null;
+ try {
+ builder = performInitialized("Sending configuration for option: " + config);
+ switch (config) {
+ case MiBandConst.PREF_MI2_DATEFORMAT:
+ setDateDisplay(builder);
+ break;
+ case MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT:
+ setActivateDisplayOnLiftWrist(builder);
+ break;
+ case MiBandConst.PREF_MIBAND_FITNESS_GOAL:
+ setFitnessGoal(builder);
+ break;
+ }
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
}
}
@Override
public void onTestNewFunction() {
}
+
+ private MiBand2Support setDateDisplay(TransactionBuilder builder) {
+ DateTimeDisplay dateTimeDisplay = MiBand2Coordinator.getDateDisplay(getContext());
+ LOG.info("Setting date display to " + dateTimeDisplay);
+ switch (dateTimeDisplay) {
+ case TIME:
+ builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.DATEFORMAT_TIME);
+ break;
+ case DATE_TIME:
+ builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.DATEFORMAT_DATE_TIME);
+ break;
+ }
+ return this;
+ }
+
+ private MiBand2Support setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
+ boolean enable = MiBand2Coordinator.getActivateDisplayOnLiftWrist();
+ LOG.info("Setting activate display on lift wrist to " + enable);
+ if (enable) {
+ builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST);
+ } else {
+ builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST);
+ }
+ return this;
+ }
+
+ public void phase2Initialize(TransactionBuilder builder) {
+ LOG.info("phase2Initialize...");
+ enableFurtherNotifications(builder, true);
+ requestBatteryInfo(builder);
+ setDateDisplay(builder);
+ setWearLocation(builder);
+ setFitnessGoal(builder);
+ setActivateDisplayOnLiftWrist(builder);
+ setHeartrateSleepSupport(builder);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java
new file mode 100644
index 00000000..7f32886e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java
@@ -0,0 +1,248 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.AbstractMiBand2Operation;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches activity data. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public class FetchActivityOperation extends AbstractMiBand2Operation {
+ private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
+
+ private List samples = new ArrayList<>(60*24); // 1day per default
+
+ private byte lastPacketCounter = -1;
+ private Calendar startTimestamp;
+
+ public FetchActivityOperation(MiBand2Support support) {
+ super(support);
+ }
+
+ @Override
+ protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
+ if (!enable) {
+ // dynamically enabled, but always disabled on finish
+ builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA), enable);
+ }
+ }
+
+ @Override
+ protected void doPerform() throws IOException {
+ TransactionBuilder builder = performInitialized("fetching activity data");
+ getSupport().setLowLatency(builder);
+ builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
+ BluetoothGattCharacteristic characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
+ builder.notify(characteristicFetch, true);
+ BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
+
+ GregorianCalendar sinceWhen = getLastSuccessfulSynchronizedTime();
+ builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, 0x01 }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
+ builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply
+ builder.notify(characteristicActivityData, true);
+ builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_ACTIVITY_DATA });
+ builder.queue(getQueue());
+ }
+
+ private GregorianCalendar getLastSuccessfulSynchronizedTime() {
+ try (DBHandler dbHandler = GBApplication.acquireDB()) {
+ DaoSession session = dbHandler.getDaoSession();
+ SampleProvider sampleProvider = new MiBand2SampleProvider(getDevice(), session);
+ MiBandActivitySample sample = sampleProvider.getLatestActivitySample();
+ if (sample != null) {
+ int timestamp = sample.getTimestamp();
+ GregorianCalendar calendar = BLETypeConversions.createCalendar();
+ calendar.setTimeInMillis((long) timestamp * 1000);
+ return calendar;
+ }
+ } catch (Exception ex) {
+ LOG.error("Error querying for latest activity sample, synchronizing the last 10 days", ex);
+ }
+
+ GregorianCalendar calendar = BLETypeConversions.createCalendar();
+ calendar.add(Calendar.DAY_OF_MONTH, -10);
+ return calendar;
+ }
+
+ @Override
+ public boolean onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ UUID characteristicUUID = characteristic.getUuid();
+ if (MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) {
+ handleActivityNotif(characteristic.getValue());
+ return true;
+ } else if (MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) {
+ handleActivityMetadata(characteristic.getValue());
+ return true;
+ } else {
+ return super.onCharacteristicChanged(gatt, characteristic);
+ }
+ }
+
+ private void handleActivityFetchFinish() {
+ LOG.info("Fetching activity data has finished.");
+ saveSamples();
+ operationFinished();
+ unsetBusy();
+ }
+
+ private void saveSamples() {
+ if (samples.size() > 0) {
+ // save all the samples that we got
+ try (DBHandler handler = GBApplication.acquireDB()) {
+ DaoSession session = handler.getDaoSession();
+ SampleProvider sampleProvider = new MiBandSampleProvider(getDevice(), session);
+ Device device = DBHelper.getDevice(getDevice(), session);
+ User user = DBHelper.getUser(session);
+
+ GregorianCalendar timestamp = (GregorianCalendar) startTimestamp.clone();
+ for (MiBandActivitySample sample : samples) {
+ sample.setDevice(device);
+ sample.setUser(user);
+ sample.setTimestamp((int) (timestamp.getTimeInMillis() / 1000));
+ sample.setProvider(sampleProvider);
+
+ if (LOG.isDebugEnabled()) {
+// LOG.debug("sample: " + sample);
+ }
+
+ timestamp.add(Calendar.MINUTE, 1);
+ }
+ sampleProvider.addGBActivitySamples(samples.toArray(new MiBandActivitySample[0]));
+
+ LOG.info("Mi2 activity data: last sample timestamp: " + DateTimeUtils.formatDateTime(timestamp.getTime()));
+
+ } catch (Exception ex) {
+ GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
+ } finally {
+ samples.clear();
+ }
+ }
+ }
+
+ /**
+ * Method to handle the incoming activity data.
+ * There are two kind of messages we currently know:
+ * - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
+ * - the second one is 20 bytes long and contains the actual activity data
+ *
+ * The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
+ *
+ * @param value
+ */
+ private void handleActivityNotif(byte[] value) {
+ if (!isOperationRunning()) {
+ LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
+ getSupport().logMessageContent(value);
+ return;
+ }
+
+ if ((value.length % 4) == 1) {
+ if ((byte) (lastPacketCounter + 1) == value[0] ) {
+ lastPacketCounter++;
+ bufferActivityData(value);
+ } else {
+ GB.toast("Error fetching activity data, invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
+ handleActivityFetchFinish();
+ return;
+ }
+ } else {
+ GB.toast("Error fetching activity data, unexpected package length: " + value.length, Toast.LENGTH_LONG, GB.ERROR);
+ }
+ }
+
+ /**
+ * Creates samples from the given 17-length array
+ * @param value
+ */
+ private void bufferActivityData(byte[] value) {
+ int len = value.length;
+
+ if (len % 4 != 1) {
+ throw new AssertionError("Unexpected activity array size: " + value);
+ }
+
+ for (int i = 1; i < len; i+=4) {
+ MiBandActivitySample sample = createSample(value[i], value[i + 1], value[i + 2], value[i + 3]);
+ samples.add(sample);
+ }
+ }
+
+ private MiBandActivitySample createSample(byte category, byte intensity, byte steps, byte heartrate) {
+ MiBandActivitySample sample = new MiBandActivitySample();
+ sample.setRawKind(category & 0xff);
+ sample.setRawIntensity(intensity & 0xff);
+ sample.setSteps(steps & 0xff);
+ sample.setHeartRate(heartrate & 0xff);
+
+ return sample;
+ }
+
+ private void handleActivityMetadata(byte[] value) {
+ if (value.length == 15) {
+ // first two bytes are whether our request was accepted
+ if (ArrayUtils.equals(value, MiBand2Service.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
+ // the third byte (0x01 on success) = ?
+ // the 4th - 7th bytes probably somehow represent the number of bytes/packets to expect
+
+ // last 8 bytes are the start date
+ Calendar startTimestamp = getSupport().fromTimeBytes(org.apache.commons.lang3.ArrayUtils.subarray(value, 7, value.length));
+ setStartTimestamp(startTimestamp);
+
+ GB.toast(getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since,
+ DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), Toast.LENGTH_LONG, GB.INFO);
+ } else {
+ LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
+ handleActivityFetchFinish();
+ }
+ } else if (value.length == 3) {
+ if (Arrays.equals(MiBand2Service.RESPONSE_FINISH_SUCCESS, value)) {
+ handleActivityFetchFinish();
+ } else {
+ LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
+ handleActivityFetchFinish();
+ }
+ }
+ }
+
+ private void setStartTimestamp(Calendar startTimestamp) {
+ this.startTimestamp = startTimestamp;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/InitOperation.java
index 7e8335db..9048dad0 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/InitOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/InitOperation.java
@@ -25,7 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class InitOperation extends AbstractBTLEOperation {
@@ -98,9 +98,10 @@ public class InitOperation extends AbstractBTLEOperation {
} else if (value[0] == MiBand2Service.AUTH_RESPONSE &&
value[1] == MiBand2Service.AUTH_SEND_ENCRYPTED_AUTH_NUMBER &&
value[2] == MiBand2Service.AUTH_SUCCESS) {
- TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the band");
+ TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
getSupport().requestDeviceInfo(builder);
+ getSupport().phase2Initialize(builder);
getSupport().setInitialized(builder);
getSupport().performImmediately(builder);
} else {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java
new file mode 100644
index 00000000..542f35c4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java
@@ -0,0 +1,257 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
+import android.net.Uri;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.AbstractMiBand2Operation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2FirmwareInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
+ private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperation.class);
+
+ private final Uri uri;
+ private final BluetoothGattCharacteristic fwCControlChar;
+ private final BluetoothGattCharacteristic fwCDataChar;
+ final Prefs prefs = GBApplication.getPrefs();
+ private Mi2FirmwareInfo firmwareInfo;
+
+ public UpdateFirmwareOperation(Uri uri, MiBand2Support support) {
+ super(support);
+ this.uri = uri;
+ fwCControlChar = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_FIRMWARE);
+ fwCDataChar = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_FIRMWARE_DATA);
+ }
+
+ @Override
+ protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
+ builder.notify(fwCControlChar, enable);
+ }
+
+ @Override
+ protected void doPerform() throws IOException {
+ MiBand2FWHelper mFwHelper = new MiBand2FWHelper(uri, getContext());
+
+ firmwareInfo = mFwHelper.getFirmwareInfo();
+ if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
+ throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
+ }
+
+ if (!sendFwInfo()) {
+ displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+ done();
+ }
+ //the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
+ }
+
+ private void done() {
+ LOG.info("Operation done.");
+ operationFinished();
+ unsetBusy();
+ }
+
+ @Override
+ public boolean onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ UUID characteristicUUID = characteristic.getUuid();
+ if (fwCControlChar.getUuid().equals(characteristicUUID)) {
+ handleNotificationNotif(characteristic.getValue());
+ } else {
+ super.onCharacteristicChanged(gatt, characteristic);
+ }
+ return false;
+ }
+
+ /**
+ * React to messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION
+ * characteristic,
+ * These messages appear to be always 1 byte long, with values that are listed in MiBandService.
+ * It is not excluded that there are further values which are still unknown.
+ *
+ * Upon receiving known values that request further action by GB, the appropriate method is called.
+ *
+ * @param value
+ */
+ private void handleNotificationNotif(byte[] value) {
+ if (value.length != 3) {
+ LOG.error("Notifications should be 3 bytes long.");
+ getSupport().logMessageContent(value);
+ return;
+ }
+ boolean success = value[2] == MiBand2Service.SUCCESS;
+
+ if (value[0] == MiBand2Service.RESPONSE && success) {
+ try {
+ switch (value[1]) {
+ case MiBand2Service.COMMAND_FIRMWARE_INIT: {
+ sendFirmwareData(getFirmwareInfo());
+ break;
+ }
+ case MiBand2Service.COMMAND_FIRMWARE_START_DATA: {
+ sendChecksum(getFirmwareInfo());
+ break;
+ }
+ case MiBand2Service.COMMAND_FIRMWARE_CHECKSUM: {
+ sendApplyReboot(getFirmwareInfo());
+ break;
+ }
+ case MiBand2Service.COMMAND_FIRMWARE_APPLY_REBOOT: {
+ GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
+// getSupport().onReboot();
+ done();
+ break;
+ }
+ default: {
+ LOG.error("Unexpected response during firmware update: ");
+ getSupport().logMessageContent(value);
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+ done();
+ return;
+ }
+ }
+ } catch (Exception ex) {
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+ done();
+ }
+ } else {
+ LOG.error("Unexpected notification during firmware update: ");
+ getSupport().logMessageContent(value);
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR);
+ done();
+ }
+ }
+ private void displayMessage(Context context, String message, int duration, int severity) {
+ getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity));
+ }
+
+ public boolean sendFwInfo() {
+ try {
+ TransactionBuilder builder = performInitialized("send firmware info");
+// getSupport().setLowLatency(builder);
+ builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+ int fwSize = getFirmwareInfo().getSize();
+ byte[] sizeBytes = BLETypeConversions.fromUint24(fwSize);
+ byte[] bytes = new byte[]{
+ MiBand2Service.COMMAND_FIRMWARE_INIT,
+ sizeBytes[0],
+ sizeBytes[1],
+ sizeBytes[2],
+ };
+
+ builder.write(fwCControlChar, bytes);
+ builder.queue(getQueue());
+ return true;
+ } catch (IOException e) {
+ LOG.error("Error sending firmware info: " + e.getLocalizedMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * Method that uploads a firmware (fwbytes) to the Mi Band.
+ * The firmware has to be split into chunks of 20 bytes each, and periodically a COMMAND_SYNC command has to be issued to the Mi Band.
+ *
+ * The Mi Band will send a notification after receiving this data to confirm if the firmware looks good to it.
+ *
+ * @param info
+ * @return whether the transfer succeeded or not. Only a BT layer exception will cause the transmission to fail.
+ * @see MiBand2Support#handleNotificationNotif
+ */
+ private boolean sendFirmwareData(Mi2FirmwareInfo info) {
+ byte[] fwbytes = info.getBytes();
+ int len = fwbytes.length;
+ final int packetLength = 20;
+ int packets = len / packetLength;
+
+ try {
+ // going from 0 to len
+ int firmwareProgress = 0;
+
+ TransactionBuilder builder = performInitialized("send firmware packet");
+ if (prefs.getBoolean("mi_low_latency_fw_update", true)) {
+ getSupport().setLowLatency(builder);
+ }
+ builder.write(fwCControlChar, new byte[] { MiBand2Service.COMMAND_FIRMWARE_START_DATA });
+
+ for (int i = 0; i < packets; i++) {
+ byte[] fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength);
+
+ builder.write(fwCDataChar, fwChunk);
+ firmwareProgress += packetLength;
+
+ int progressPercent = (int) ((((float) firmwareProgress) / len) * 100);
+ if ((i > 0) && (i % 100 == 0)) {
+ builder.write(fwCControlChar, new byte[]{MiBand2Service.COMMAND_FIRMWARE_UPDATE_SYNC});
+ builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
+ }
+ }
+
+ if (firmwareProgress < len) {
+ byte[] lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len);
+ builder.write(fwCDataChar, lastChunk);
+ firmwareProgress = len;
+ }
+
+ builder.write(fwCControlChar, new byte[]{MiBand2Service.COMMAND_FIRMWARE_UPDATE_SYNC});
+ builder.queue(getQueue());
+
+ } catch (IOException ex) {
+ LOG.error("Unable to send fw to MI 2", ex);
+ GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_firmware_not_sent), false, 0, getContext());
+ return false;
+ }
+ return true;
+ }
+
+
+ private void sendChecksum(Mi2FirmwareInfo firmwareInfo) throws IOException {
+ TransactionBuilder builder = performInitialized("send firmware checksum");
+ int crc16 = firmwareInfo.getCrc16();
+ byte[] bytes = BLETypeConversions.fromUint16(crc16);
+ builder.write(fwCControlChar, new byte[] {
+ MiBand2Service.COMMAND_FIRMWARE_CHECKSUM,
+ bytes[0],
+ bytes[1],
+ });
+ builder.queue(getQueue());
+ }
+
+ private void sendApplyReboot(Mi2FirmwareInfo firmwareInfo) throws IOException {
+ TransactionBuilder builder = performInitialized("send firmware apply/reboot");
+ builder.write(fwCControlChar, new byte[] { MiBand2Service.COMMAND_FIRMWARE_APPLY_REBOOT });
+ builder.queue(getQueue());
+ }
+
+ private Mi2FirmwareInfo getFirmwareInfo() {
+ return firmwareInfo;
+ }
+
+ enum State {
+ INITIAL,
+ SEND_FW2,
+ SEND_FW1,
+ FINISHED,
+ UNKNOWN
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java
index 781e56c1..6d7d8502 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java
@@ -56,7 +56,7 @@ public class AppMessageHandlerPebStyle extends AppMessageHandler {
pairs.add(new Pair<>(KEY_SECOND_HAND, (Object) 0)); //1 enabled
pairs.add(new Pair<>(KEY_BLUETOOTH_ALERT, (Object) 0)); //1 silent, 2 weak, up to 5
pairs.add(new Pair<>(KEY_TEMPERATURE_FORMAT, (Object) 1)); //0 fahrenheit
- pairs.add(new Pair<>(KEY_LOCATION_SERVICE, (Object) 2)); //0 uto, 1 manual
+ pairs.add(new Pair<>(KEY_LOCATION_SERVICE, (Object) 2)); //0 auto, 1 manual
pairs.add(new Pair<>(KEY_SIDEBAR_LOCATION, (Object) 1)); //0 right
pairs.add(new Pair<>(KEY_COLOR_SELECTION, (Object) 1)); //1 custom
pairs.add(new Pair<>(KEY_MAIN_COLOR, (Object) PebbleColor.Black));
@@ -115,4 +115,4 @@ public class AppMessageHandlerPebStyle extends AppMessageHandler {
return new GBDeviceEvent[]{sendBytes};
*/
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java
new file mode 100644
index 00000000..2f70e1f4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java
@@ -0,0 +1,27 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+class DatalogSessionHealthHR extends DatalogSessionPebbleHealth {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthHR.class);
+
+ DatalogSessionHealthHR(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) {
+ super(id, uuid, tag, item_type, item_size, device);
+ taginfo = "(Health - HR " + tag + " )";
+ }
+
+ @Override
+ public boolean handleMessage(ByteBuffer datalogMessage, int length) {
+ LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+
+ return isPebbleHealthEnabled();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java
index 98e13261..e41d8f32 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java
@@ -24,7 +24,7 @@ class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
public DatalogSessionHealthOverlayData(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) {
super(id, uuid, tag, item_type, item_size, device);
- taginfo = "(health - overlay data " + tag + " )";
+ taginfo = "(Health - overlay data " + tag + " )";
}
@Override
@@ -85,7 +85,7 @@ class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
int durationSeconds;
byte[] rawData;
- public OverlayRecord(byte[] rawData) {
+ OverlayRecord(byte[] rawData) {
this.rawData = rawData;
ByteBuffer record = ByteBuffer.wrap(rawData);
record.order(ByteOrder.LITTLE_ENDIAN);
@@ -99,7 +99,7 @@ class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
this.durationSeconds = record.getInt();
}
- public byte[] getRawData() {
+ byte[] getRawData() {
if (storePebbleHealthRawRecord()) {
return rawData;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
index fef497f2..d6534ef7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
@@ -24,7 +24,7 @@ class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
public DatalogSessionHealthSleep(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) {
super(id, uuid, tag, item_type, item_size, device);
- taginfo = "(health - sleep " + tag + " )";
+ taginfo = "(Health - sleep " + tag + " )";
}
@Override
@@ -87,7 +87,7 @@ class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
int deepSleepSeconds;
byte[] rawData;
- public SleepRecord(byte[] rawData) {
+ SleepRecord(byte[] rawData) {
this.rawData = rawData;
ByteBuffer record = ByteBuffer.wrap(rawData);
record.order(ByteOrder.LITTLE_ENDIAN);
@@ -101,7 +101,7 @@ class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
this.deepSleepSeconds = record.getInt();
}
- public byte[] getRawData() {
+ byte[] getRawData() {
if (storePebbleHealthRawRecord()) {
return rawData;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
index b5bd1678..4bcc6ae5 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
@@ -22,7 +22,7 @@ public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
public DatalogSessionHealthSteps(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) {
super(id, uuid, tag, item_type, item_size, device);
- taginfo = "(health - steps)";
+ taginfo = "(Health - steps)";
}
@Override
@@ -89,7 +89,8 @@ public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
deviceId, userId,
stepsRecord.getRawData(),
stepsRecord.intensity,
- stepsRecord.steps
+ stepsRecord.steps,
+ stepsRecord.heart_rate
);
samples[j].setProvider(sampleProvider);
}
@@ -108,9 +109,11 @@ public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
int orientation;
int intensity;
int light_intensity;
+ int heart_rate;
+
byte[] rawData;
- public StepsRecord(int timestamp, short version, byte[] rawData) {
+ StepsRecord(int timestamp, short version, byte[] rawData) {
this.timestamp = timestamp;
this.rawData = rawData;
ByteBuffer record = ByteBuffer.wrap(rawData);
@@ -123,9 +126,16 @@ public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
this.orientation = record.get() & 0xff;
this.intensity = record.getShort() & 0xffff;
this.light_intensity = record.get() & 0xff;
+ if (version >= 7) {
+ // skip 7 bytes
+ record.getInt();
+ record.getShort();
+ record.get();
+ this.heart_rate = record.get() & 0xff;
+ }
}
- public byte[] getRawData() {
+ byte[] getRawData() {
if (storePebbleHealthRawRecord()) {
return rawData;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java
index 3d26bf68..2aae2cb9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java
@@ -19,12 +19,12 @@ abstract class DatalogSessionPebbleHealth extends DatalogSession {
return mDevice;
}
- protected boolean isPebbleHealthEnabled() {
+ boolean isPebbleHealthEnabled() {
Prefs prefs = GBApplication.getPrefs();
return prefs.getBoolean("pebble_sync_health", true);
}
- protected boolean storePebbleHealthRawRecord() {
+ boolean storePebbleHealthRawRecord() {
Prefs prefs = GBApplication.getPrefs();
return prefs.getBoolean("pebble_health_store_raw", true);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
index dbfe3171..45035477 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
@@ -21,6 +21,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
@@ -40,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble.PebbleLESupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@@ -61,7 +64,7 @@ class PebbleIoThread extends GBDeviceIoThread {
public static final String PEBBLEKIT_ACTION_APP_START = "com.getpebble.action.app.START";
public static final String PEBBLEKIT_ACTION_APP_STOP = "com.getpebble.action.app.STOP";
- final Prefs prefs = GBApplication.getPrefs();
+ private final Prefs prefs = GBApplication.getPrefs();
private final PebbleProtocol mPebbleProtocol;
private final PebbleSupport mPebbleSupport;
@@ -73,6 +76,8 @@ class PebbleIoThread extends GBDeviceIoThread {
private Socket mTCPSocket = null; // for emulator
private InputStream mInStream = null;
private OutputStream mOutStream = null;
+ private PebbleLESupport mPebbleLESupport;
+
private boolean mQuit = false;
private boolean mIsConnected = false;
private boolean mIsInstalling = false;
@@ -152,6 +157,14 @@ class PebbleIoThread extends GBDeviceIoThread {
mEnablePebblekit = prefs.getBoolean("pebble_enable_pebblekit", false);
}
+ private int readWithException(InputStream inputStream, byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ int ret = inputStream.read(buffer, byteOffset, byteCount);
+ if (ret == -1) {
+ throw new IOException("broken pipe");
+ }
+ return ret;
+ }
+
private void sendAppMessageAck(int transactionId) {
Intent intent = new Intent();
intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE_ACK);
@@ -161,33 +174,44 @@ class PebbleIoThread extends GBDeviceIoThread {
}
@Override
- protected boolean connect(String btDeviceAddress) {
+ protected boolean connect() {
+ String deviceAddress = gbDevice.getAddress();
GBDevice.State originalState = gbDevice.getState();
gbDevice.setState(GBDevice.State.CONNECTING);
gbDevice.sendDeviceUpdateIntent(getContext());
try {
// contains only one ":"? then it is addr:port
- int firstColon = btDeviceAddress.indexOf(":");
- if (firstColon == btDeviceAddress.lastIndexOf(":")) {
+ int firstColon = deviceAddress.indexOf(":");
+ if (firstColon == deviceAddress.lastIndexOf(":")) {
mIsTCP = true;
- InetAddress serverAddr = InetAddress.getByName(btDeviceAddress.substring(0, firstColon));
- mTCPSocket = new Socket(serverAddr, Integer.parseInt(btDeviceAddress.substring(firstColon + 1)));
+ InetAddress serverAddr = InetAddress.getByName(deviceAddress.substring(0, firstColon));
+ mTCPSocket = new Socket(serverAddr, Integer.parseInt(deviceAddress.substring(firstColon + 1)));
mInStream = mTCPSocket.getInputStream();
mOutStream = mTCPSocket.getOutputStream();
} else {
mIsTCP = false;
- BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
- ParcelUuid uuids[] = btDevice.getUuids();
- if (uuids == null) {
- return false;
+ if (gbDevice.getVolatileAddress() != null && prefs.getBoolean("pebble_force_le", false)) {
+ deviceAddress = gbDevice.getVolatileAddress();
}
- for (ParcelUuid uuid : uuids) {
- LOG.info("found service UUID " + uuid);
+ BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(deviceAddress);
+ if (btDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE) {
+ LOG.info("This is a Pebble 2 or Pebble-LE/Pebble Time LE, will use BLE");
+ mInStream = new PipedInputStream();
+ mOutStream = new PipedOutputStream();
+ mPebbleLESupport = new PebbleLESupport(this.getContext(), btDevice, (PipedInputStream) mInStream, (PipedOutputStream) mOutStream);
+ } else {
+ ParcelUuid uuids[] = btDevice.getUuids();
+ if (uuids == null) {
+ return false;
+ }
+ for (ParcelUuid uuid : uuids) {
+ LOG.info("found service UUID " + uuid);
+ }
+ mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
+ mBtSocket.connect();
+ mInStream = mBtSocket.getInputStream();
+ mOutStream = mBtSocket.getOutputStream();
}
- mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
- mBtSocket.connect();
- mInStream = mBtSocket.getInputStream();
- mOutStream = mBtSocket.getOutputStream();
}
} catch (IOException e) {
e.printStackTrace();
@@ -212,9 +236,9 @@ class PebbleIoThread extends GBDeviceIoThread {
@Override
public void run() {
- mIsConnected = connect(gbDevice.getAddress());
+ mIsConnected = connect();
if (!mIsConnected) {
- if (GBApplication.getGBPrefs().getAutoReconnect()) {
+ if (GBApplication.getGBPrefs().getAutoReconnect() && !mQuit) {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
gbDevice.sendDeviceUpdateIntent(getContext());
}
@@ -299,7 +323,7 @@ class PebbleIoThread extends GBDeviceIoThread {
writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete());
finishInstall(false);
} else if (mPBWReader.isLanguage() || mPebbleProtocol.mFwMajor >= 3) {
- finishInstall(false); // FIXME: dont know yet how to detect success
+ finishInstall(false); // FIXME: don't know yet how to detect success
} else {
writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot));
}
@@ -311,11 +335,12 @@ class PebbleIoThread extends GBDeviceIoThread {
if (mIsTCP) {
mInStream.skip(6);
}
- int bytes = mInStream.read(buffer, 0, 4);
+ int bytes = readWithException(mInStream, buffer, 0, 4);
- if (bytes < 4) {
- continue;
+ while (bytes < 4) {
+ bytes += readWithException(mInStream, buffer, bytes, 4 - bytes);
}
+
ByteBuffer buf = ByteBuffer.wrap(buffer);
buf.order(ByteOrder.BIG_ENDIAN);
short length = buf.getShort();
@@ -323,19 +348,14 @@ class PebbleIoThread extends GBDeviceIoThread {
if (length < 0 || length > 8192) {
LOG.info("invalid length " + length);
while (mInStream.available() > 0) {
- mInStream.read(buffer); // read all
+ readWithException(mInStream, buffer, 0, buffer.length); // read all
}
continue;
}
- bytes = mInStream.read(buffer, 4, length);
+ bytes = readWithException(mInStream, buffer, 4, length);
while (bytes < length) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- bytes += mInStream.read(buffer, bytes + 4, length - bytes);
+ bytes += readWithException(mInStream, buffer, bytes + 4, length - bytes);
}
if (mIsTCP) {
@@ -361,7 +381,7 @@ class PebbleIoThread extends GBDeviceIoThread {
e.printStackTrace();
}
} catch (IOException e) {
- if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right
+ if (e.getMessage() != null && (e.getMessage().equals("broken pipe") || e.getMessage().contains("socket closed"))) { //FIXME: this does not feel right
LOG.info(e.getMessage());
mIsConnected = false;
int reconnectAttempts = prefs.getInt("pebble_reconnect_attempts", 10);
@@ -372,7 +392,7 @@ class PebbleIoThread extends GBDeviceIoThread {
int delaySeconds = 1;
while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) {
LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")");
- mIsConnected = connect(gbDevice.getAddress());
+ mIsConnected = connect();
if (!mIsConnected) {
try {
Thread.sleep(delaySeconds * 1000);
@@ -453,7 +473,7 @@ class PebbleIoThread extends GBDeviceIoThread {
mOutStream.flush();
}
} catch (IOException e) {
- LOG.error("Error writing.", e);
+ LOG.error("Error writing.", e.getMessage());
}
try {
Thread.sleep(100);
@@ -480,6 +500,7 @@ class PebbleIoThread extends GBDeviceIoThread {
LOG.info("syncing time");
write(mPebbleProtocol.encodeSetTime());
}
+ write(mPebbleProtocol.encodeEnableAppLogs(prefs.getBoolean("pebble_enable_applogs",false)));
write(mPebbleProtocol.encodeReportDataLogSessions());
gbDevice.setState(GBDevice.State.INITIALIZED);
return false;
@@ -541,6 +562,9 @@ class PebbleIoThread extends GBDeviceIoThread {
break;
}
break;
+ case START:
+ LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid);
+ break;
default:
break;
}
@@ -588,6 +612,10 @@ class PebbleIoThread extends GBDeviceIoThread {
write(mPebbleProtocol.encodeSetSaneDistanceUnit(true));
return;
}
+ if (uri.equals(Uri.parse("fake://hrm"))) {
+ write(mPebbleProtocol.encodeActivateHRM(true));
+ return;
+ }
String platformName = PebbleUtils.getPlatformName(gbDevice.getModel());
@@ -699,16 +727,20 @@ class PebbleIoThread extends GBDeviceIoThread {
if (mBtSocket != null) {
try {
mBtSocket.close();
- } catch (IOException e) {
- e.printStackTrace();
+ } catch (IOException ignored) {
}
+ mBtSocket = null;
}
if (mTCPSocket != null) {
try {
mTCPSocket.close();
- } catch (IOException e) {
- e.printStackTrace();
+ } catch (IOException ignored) {
}
+ mTCPSocket = null;
+ }
+ if (mPebbleLESupport != null) {
+ mPebbleLESupport.close();
+ mPebbleLESupport = null;
}
}
@@ -723,4 +755,4 @@ class PebbleIoThread extends GBDeviceIoThread {
UPLOAD_COMPLETE,
APP_REFRESH,
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
index 57c58b70..065b7ade 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
@@ -28,7 +28,6 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificati
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
-import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
@@ -70,6 +69,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
public static final short ENDPOINT_DATALOG = 6778;
static final short ENDPOINT_RUNKEEPER = 7000;
static final short ENDPOINT_SCREENSHOT = 8000;
+ static final short ENDPOINT_AUDIOSTREAM = 10000;
+ static final short ENDPOINT_VOICECONTROL = 11000;
static final short ENDPOINT_NOTIFICATIONACTION = 11440; // 3.x only, TODO: find a better name
static final short ENDPOINT_APPREORDER = (short) 0xabcd; // 3.x only
static final short ENDPOINT_BLOBDB = (short) 45531; // 3.x only
@@ -366,6 +367,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
private final ArrayList tmpUUIDS = new ArrayList<>();
public static final UUID UUID_PEBBLE_HEALTH = UUID.fromString("36d8c6ed-4c83-4fa1-a9e2-8f12dc941f8c"); // FIXME: store somewhere else, this is also accessed by other code
+ public static final UUID UUID_WORKOUT = UUID.fromString("fef82c82-7176-4e22-88de-35a3fc18d43f"); // FIXME: store somewhere else, this is also accessed by other code
private static final UUID UUID_GBPEBBLE = UUID.fromString("61476764-7465-7262-6469-656775527a6c");
private static final UUID UUID_MORPHEUZ = UUID.fromString("5be44f1d-d262-4ea6-aa30-ddbec1e3cab2");
private static final UUID UUID_WHETHERNEAT = UUID.fromString("3684003b-a685-45f9-a713-abc6364ba051");
@@ -387,7 +389,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
private final HashMap mDatalogSessions = new HashMap<>();
- private static byte[] encodeSimpleMessage(short endpoint, byte command) {
+ private byte[] encodeSimpleMessage(short endpoint, byte command) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SIMPLEMESSAGE);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_SIMPLEMESSAGE);
@@ -532,7 +534,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return encodeSetCallState("Where are you?", "Gadgetbridge", start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END);
}
- private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) {
+ private byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) {
final short ACTION_LENGTH_MIN = 10;
String[] parts = {title, subtitle, body};
@@ -720,10 +722,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeActivateHealth(boolean activate) {
+ byte[] encodeActivateHealth(boolean activate) {
byte[] blob;
- byte command;
- command = BLOBDB_INSERT;
if (activate) {
ByteBuffer buf = ByteBuffer.allocate(9);
@@ -743,10 +743,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
} else {
blob = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
}
- return encodeBlobdb("activityPreferences", command, BLOBDB_PREFERENCES, blob);
+ return encodeBlobdb("activityPreferences", BLOBDB_INSERT, BLOBDB_PREFERENCES, blob);
}
- public byte[] encodeSetSaneDistanceUnit(boolean sane) {
+ byte[] encodeSetSaneDistanceUnit(boolean sane) {
byte value;
if (sane) {
value = 0x00;
@@ -756,7 +756,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
return encodeBlobdb("unitsDistance", BLOBDB_INSERT, BLOBDB_PREFERENCES, new byte[]{value});
}
- public byte[] encodeReportDataLogSessions() {
+
+ byte[] encodeActivateHRM(boolean activate) {
+ return encodeBlobdb("hrmPreferences", BLOBDB_INSERT, BLOBDB_PREFERENCES,
+ activate ? new byte[]{0x01} : new byte[]{0x00});
+ }
+
+ byte[] encodeReportDataLogSessions() {
return encodeSimpleMessage(ENDPOINT_DATALOG, DATALOG_REPORTSESSIONS);
}
@@ -803,7 +809,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.putShort(duration);
buf.put((byte) 0x02); // type (0x02 = pin)
buf.putShort((short) 0x0001); // flags 0x0001 = ?
- buf.put((byte) 0x01); // layout was (0x02 = pin?), 0x01 needed for subtitle aber seems to do no harm if there isn't one
+ buf.put((byte) 0x01); // layout was (0x02 = pin?), 0x01 needed for subtitle but seems to do no harm if there isn't one
buf.putShort((short) attributes_length); // total length of all attributes and actions in bytes
buf.put(attributes_count);
@@ -821,7 +827,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.put(subtitle.getBytes());
}
-
return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array());
}
@@ -831,50 +836,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
String[] parts = {title, subtitle, body};
- int icon_id;
- byte color_id;
- switch (notificationType) {
- case CONVERSATIONS:
- icon_id = PebbleIconID.NOTIFICATION_HIPCHAT;
- color_id = PebbleColor.Inchworm;
- break;
- case GENERIC_EMAIL:
- icon_id = PebbleIconID.GENERIC_EMAIL;
- color_id = PebbleColor.JaegerGreen;
- break;
- case GENERIC_NAVIGATION:
- icon_id = mFwMajor >= 4 ? PebbleIconID.NOTIFICATION_GOOGLE_MAPS : PebbleIconID.LOCATION;
- color_id = PebbleColor.Orange;
- break;
- case GENERIC_SMS:
- icon_id = PebbleIconID.GENERIC_SMS;
- color_id = PebbleColor.VividViolet;
- break;
- case TWITTER:
- icon_id = PebbleIconID.NOTIFICATION_TWITTER;
- color_id = PebbleColor.BlueMoon;
- break;
- case FACEBOOK:
- icon_id = PebbleIconID.NOTIFICATION_FACEBOOK;
- color_id = PebbleColor.Liberty;
- break;
- case FACEBOOK_MESSENGER:
- icon_id = PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER;
- color_id = PebbleColor.VeryLightBlue;
- break;
- case TELEGRAM:
- icon_id = PebbleIconID.NOTIFICATION_TELEGRAM;
- color_id = PebbleColor.PictonBlue;
- break;
- case SIGNAL:
- icon_id = PebbleIconID.NOTIFICATION_HIPCHAT;
- color_id = PebbleColor.BlueMoon;
- break;
- default:
- icon_id = PebbleIconID.NOTIFICATION_GENERIC;
- color_id = PebbleColor.Red;
- break;
+ if(notificationType == null) {
+ notificationType = NotificationType.UNKNOWN;
}
+
+ int icon_id = notificationType.icon;
+ byte color_id = notificationType.color;
+
// Calculate length first
byte actions_count;
short actions_length;
@@ -1013,7 +981,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array());
}
- public byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) {
+ private byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) {
short length = (short) (18 + caption.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
@@ -1034,7 +1002,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeActionResponse(UUID uuid, int iconId, String caption) {
+ private byte[] encodeActionResponse(UUID uuid, int iconId, String caption) {
short length = (short) (29 + caption.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
@@ -1055,7 +1023,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeInstallMetadata(UUID uuid, String appName, short appVersion, short sdkVersion, int flags, int iconId) {
+ byte[] encodeInstallMetadata(UUID uuid, String appName, short appVersion, short sdkVersion, int flags, int iconId) {
final short METADATA_LENGTH = 126;
byte[] name_buf = new byte[96];
@@ -1088,7 +1056,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeGetTime() {
+ byte[] encodeGetTime() {
return encodeSimpleMessage(ENDPOINT_TIME, TIME_GETTIME);
}
@@ -1248,6 +1216,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (UUID_PEBBLE_HEALTH.equals(uuid)) {
return encodeActivateHealth(false);
}
+ if (UUID_WORKOUT.equals(uuid)) {
+ return encodeActivateHRM(false);
+ }
return encodeBlobdb(uuid, BLOBDB_DELETE, BLOBDB_APP, null);
} else {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REMOVEAPP_2X);
@@ -1297,16 +1268,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.putInt(os);
buf.put(PHONEVERSION_APPVERSION_MAGIC);
- buf.put((byte) 3); // major
- buf.put((byte) 12); // minor
- buf.put((byte) 0); // patch
+ buf.put((byte) 4); // major
+ buf.put((byte) 1); // minor
+ buf.put((byte) 1); // patch
buf.order(ByteOrder.LITTLE_ENDIAN);
- buf.putLong(0x00000000000001af); //flags
+ buf.putLong(0x00000000000029af); //flags
return buf.array();
}
- public byte[] encodePhoneVersion(byte os) {
+ private byte[] encodePhoneVersion(byte os) {
return encodePhoneVersion3x(os);
}
@@ -1365,7 +1336,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
ByteBuffer buf = ByteBuffer.allocate(12 + replies_length);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(0x00000000); // unknown
- buf.put((byte) 0x00); // atributes count?
+ buf.put((byte) 0x00); // attributes count?
buf.put((byte) 0x01); // actions count?
// action
@@ -1385,7 +1356,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
/* pebble specific install methods */
- public byte[] encodeUploadStart(byte type, int app_id, int size, String filename) {
+ byte[] encodeUploadStart(byte type, int app_id, int size, String filename) {
short length;
if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) {
length = LENGTH_UPLOADSTART_3X;
@@ -1421,7 +1392,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeUploadChunk(int token, byte[] buffer, int size) {
+ byte[] encodeUploadChunk(int token, byte[] buffer, int size) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCHUNK + size);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) (LENGTH_UPLOADCHUNK + size));
@@ -1433,7 +1404,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeUploadCommit(int token, int crc) {
+ byte[] encodeUploadCommit(int token, int crc) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMMIT);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_UPLOADCOMMIT);
@@ -1444,7 +1415,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeUploadComplete(int token) {
+ byte[] encodeUploadComplete(int token) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMPLETE);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_UPLOADCOMPLETE);
@@ -1454,7 +1425,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeUploadCancel(int token) {
+ byte[] encodeUploadCancel(int token) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCANCEL);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_UPLOADCANCEL);
@@ -1475,11 +1446,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
- public byte[] encodeInstallFirmwareStart() {
+ byte[] encodeInstallFirmwareStart() {
return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARESTART);
}
- public byte[] encodeInstallFirmwareComplete() {
+ byte[] encodeInstallFirmwareComplete() {
return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARECOMPLETE);
}
@@ -1488,7 +1459,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
- public byte[] encodeAppRefresh(int index) {
+ byte[] encodeAppRefresh(int index) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_REFRESHAPP);
@@ -1499,7 +1470,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeDatalog(byte handle, byte reply) {
+ private byte[] encodeDatalog(byte handle, byte reply) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + 2);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) 2);
@@ -1524,7 +1495,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- private static byte[] encodePing(byte command, int cookie) {
+ private byte[] encodePing(byte command, int cookie) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PING);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_PING);
@@ -1535,6 +1506,17 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
+ byte[] encodeEnableAppLogs(boolean enable) {
+ final short LENGTH_APPLOGS = 1;
+ ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPLOGS);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.putShort(LENGTH_APPLOGS);
+ buf.putShort(ENDPOINT_APPLOGS);
+ buf.put((byte) (enable ? 1 : 0));
+
+ return buf.array();
+ }
+
private ArrayList> decodeDict(ByteBuffer buf) {
ArrayList> dict = new ArrayList<>();
buf.order(ByteOrder.LITTLE_ENDIAN);
@@ -1644,8 +1626,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
length += ((String) pair.second).getBytes().length + 1;
} else if (pair.second instanceof byte[]) {
length += ((byte[]) pair.second).length;
- }
- else {
+ } else {
LOG.warn("unknown type: " + pair.second.getClass().toString());
}
}
@@ -1692,7 +1673,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
- public byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) {
+ byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) {
ArrayList> pairs = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
@@ -1731,7 +1712,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs);
}
- private static byte reverseBits(byte in) {
+ private byte reverseBits(byte in) {
byte out = 0;
for (int i = 0; i < 8; i++) {
byte bit = (byte) (in & 1);
@@ -1795,14 +1776,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
byte command = buf.get();
if (command == NOTIFICATIONACTION_INVOKE) {
int id;
- long uuid_high = 0;
- long uuid_low = 0;
+ UUID uuid = new UUID(0,0);
if (mFwMajor >= 3) {
- buf.order(ByteOrder.BIG_ENDIAN);
- uuid_high = buf.getLong();
- uuid_low = buf.getLong();
- buf.order(ByteOrder.LITTLE_ENDIAN);
- id = (int) (uuid_low & 0xffffffffL);
+ uuid = getUUID(buf);
+ id = (int) (uuid.getLeastSignificantBits() & 0xffffffffL);
} else {
id = buf.getInt();
}
@@ -1872,7 +1849,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (mFwMajor >= 3 || needsAck2x) {
sendBytesAck = new GBDeviceEventSendBytes();
if (mFwMajor >= 3) {
- sendBytesAck.encodedBytes = encodeActionResponse(new UUID(uuid_high, uuid_low), icon_id, caption);
+ sendBytesAck.encodedBytes = encodeActionResponse(uuid, icon_id, caption);
} else {
sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, caption);
}
@@ -1897,6 +1874,17 @@ public class PebbleProtocol extends GBDeviceProtocol {
return null;
}
+ private void decodeAppLogs(ByteBuffer buf) {
+ UUID uuid = getUUID(buf);
+ int timestamp = buf.getInt();
+ int logLevel = buf.get() & 0xff;
+ int messageLength = buf.get() & 0xff;
+ int lineNumber = buf.getShort() & 0xffff;
+ String fileName = getFixedString(buf, 16);
+ String message = getFixedString(buf, messageLength);
+ LOG.debug("APP_LOGS (" + logLevel +") from uuid " + uuid.toString() + " in " + fileName + ":" + lineNumber + " " + message);
+ }
+
private GBDeviceEvent decodeSystemMessage(ByteBuffer buf) {
buf.get(); // unknown;
byte command = buf.get();
@@ -1917,9 +1905,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
private GBDeviceEvent[] decodeAppRunState(ByteBuffer buf) {
byte command = buf.get();
- long uuid_high = buf.getLong();
- long uuid_low = buf.getLong();
- UUID uuid = new UUID(uuid_high, uuid_low);
+ UUID uuid = getUUID(buf);
final String ENDPOINT_NAME = "APPRUNSTATE";
switch (command) {
case APPRUNSTATE_START:
@@ -1929,7 +1915,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (handler != null) {
return handler.pushMessage();
}
- break;
+ else {
+ GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement();
+ gbDeviceEventAppManagement.uuid = uuid;
+ gbDeviceEventAppManagement.type = GBDeviceEventAppManagement.EventType.START;
+ gbDeviceEventAppManagement.event = GBDeviceEventAppManagement.Event.SUCCESS;
+ return new GBDeviceEvent[] {gbDeviceEventAppManagement};
+ }
case APPRUNSTATE_STOP:
LOG.info(ENDPOINT_NAME + ": stopped " + uuid);
break;
@@ -1968,9 +1960,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
private GBDeviceEventAppManagement decodeAppFetch(ByteBuffer buf) {
byte command = buf.get();
if (command == 0x01) {
- long uuid_high = buf.getLong();
- long uuid_low = buf.getLong();
- UUID uuid = new UUID(uuid_high, uuid_low);
+ UUID uuid = getUUID(buf);
buf.order(ByteOrder.LITTLE_ENDIAN);
int app_id = buf.getInt();
GBDeviceEventAppManagement fetchRequest = new GBDeviceEventAppManagement();
@@ -2003,10 +1993,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
break;
case DATALOG_OPENSESSION:
- buf.order(ByteOrder.BIG_ENDIAN);
- long uuid_high = buf.getLong();
- long uuid_low = buf.getLong();
- UUID uuid = new UUID(uuid_high, uuid_low);
+ UUID uuid = getUUID(buf);
buf.order(ByteOrder.LITTLE_ENDIAN);
int timestamp = buf.getInt();
int log_tag = buf.getInt();
@@ -2020,6 +2007,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size, getDevice()));
} else if (uuid.equals(UUID_ZERO) && log_tag == 84) {
mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, log_tag, item_type, item_size, getDevice()));
+ } else if (uuid.equals(UUID_ZERO) && log_tag == 85) {
+ mDatalogSessions.put(id, new DatalogSessionHealthHR(id, uuid, log_tag, item_type, item_size, getDevice()));
} else {
mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size));
}
@@ -2056,6 +2045,32 @@ public class PebbleProtocol extends GBDeviceProtocol {
return null;
}
+ private GBDeviceEvent decodeVoiceControl(ByteBuffer buf) {
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ byte command = buf.get();
+ int flags = buf.getInt();
+ byte session_type = buf.get(); //0x01 dictation 0x02 command
+ short session_id = buf.getShort();
+ //attributes
+ byte count = buf.get();
+ byte type = buf.get();
+ short length = buf.getShort();
+ byte[] version = new byte[20];
+ buf.get(version); //it's a string like "1.2rc1"
+ int sample_rate = buf.getInt();
+ short bit_rate = buf.getShort();
+ byte bitstream_version = buf.get();
+ short frame_size = buf.getShort();
+
+ GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
+ if (command == 0x01) { //session setup
+ sendBytes.encodedBytes = null;
+ } else if (command == 0x02) { //dictation result
+ sendBytes.encodedBytes = null;
+ }
+ return sendBytes;
+ }
+
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
ByteBuffer buf = ByteBuffer.wrap(responseData);
@@ -2063,7 +2078,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
short length = buf.getShort();
short endpoint = buf.getShort();
GBDeviceEvent devEvts[] = null;
- byte pebbleCmd = -1;
+ byte pebbleCmd;
switch (endpoint) {
case ENDPOINT_MUSICCONTROL:
pebbleCmd = buf.get();
@@ -2113,14 +2128,12 @@ public class PebbleProtocol extends GBDeviceProtocol {
GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
buf.getInt(); // skip
- byte[] tmp = new byte[32];
- buf.get(tmp, 0, 32);
-
- versionCmd.fwVersion = new String(tmp).trim();
+ versionCmd.fwVersion = getFixedString(buf, 32);
mFwMajor = versionCmd.fwVersion.charAt(1) - 48;
LOG.info("Pebble firmware major detected as " + mFwMajor);
+ byte[] tmp = new byte[9];
buf.get(tmp, 0, 9);
int hwRev = buf.get() + 8;
if (hwRev >= 0 && hwRev < hwRevisions.length) {
@@ -2135,8 +2148,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
int slotCount = buf.getInt();
int slotsUsed = buf.getInt();
- byte[] appName = new byte[32];
- byte[] appCreator = new byte[32];
appInfoCmd.apps = new GBDeviceApp[slotsUsed];
boolean[] slotInUse = new boolean[slotCount];
@@ -2144,8 +2155,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
int id = buf.getInt();
int index = buf.getInt();
slotInUse[index] = true;
- buf.get(appName, 0, 32);
- buf.get(appCreator, 0, 32);
+ String appName = getFixedString(buf, 32);
+ String appCreator = getFixedString(buf, 32);
+
int flags = buf.getInt();
GBDeviceApp.Type appType;
@@ -2157,7 +2169,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
appType = GBDeviceApp.Type.APP_GENERIC;
}
Short appVersion = buf.getShort();
- appInfoCmd.apps[i] = new GBDeviceApp(tmpUUIDS.get(i), new String(appName).trim(), new String(appCreator).trim(), appVersion.toString(), appType);
+ appInfoCmd.apps[i] = new GBDeviceApp(tmpUUIDS.get(i), appName, appCreator, appVersion.toString(), appType);
}
for (int i = 0; i < slotCount; i++) {
if (!slotInUse[i]) {
@@ -2175,9 +2187,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
tmpUUIDS.clear();
slotsUsed = buf.getInt();
for (int i = 0; i < slotsUsed; i++) {
- long uuid_high = buf.getLong();
- long uuid_low = buf.getLong();
- UUID uuid = new UUID(uuid_high, uuid_low);
+ UUID uuid = getUUID(buf);
LOG.info("found uuid: " + uuid);
tmpUUIDS.add(uuid);
}
@@ -2222,32 +2232,38 @@ public class PebbleProtocol extends GBDeviceProtocol {
case ENDPOINT_LAUNCHER:
pebbleCmd = buf.get();
last_id = buf.get();
- long uuid_high = buf.getLong();
- long uuid_low = buf.getLong();
+ UUID uuid = getUUID(buf);
switch (pebbleCmd) {
case APPLICATIONMESSAGE_PUSH:
- UUID uuid = new UUID(uuid_high, uuid_low);
-
- if (endpoint == ENDPOINT_LAUNCHER) {
- LOG.info("got LAUNCHER PUSH from UUID " + uuid);
- break;
- }
- LOG.info("got APPLICATIONMESSAGE PUSH from UUID " + uuid);
-
+ LOG.info((endpoint == ENDPOINT_LAUNCHER ? "got LAUNCHER PUSH from UUID : " : "got APPLICATIONMESSAGE PUSH from UUID : ") + uuid);
AppMessageHandler handler = mAppMessageHandlers.get(uuid);
if (handler != null) {
if (handler.isEnabled()) {
- ArrayList> dict = decodeDict(buf);
- devEvts = handler.handleMessage(dict);
+ if (endpoint == ENDPOINT_APPLICATIONMESSAGE) {
+ ArrayList> dict = decodeDict(buf);
+ devEvts = handler.handleMessage(dict);
+ }
+ else {
+ devEvts = handler.pushMessage();
+ }
} else {
devEvts = new GBDeviceEvent[]{null};
}
} else {
try {
- devEvts = decodeDictToJSONAppMessage(uuid, buf);
+ if (endpoint == ENDPOINT_APPLICATIONMESSAGE) {
+ devEvts = decodeDictToJSONAppMessage(uuid, buf);
+ }
+ else {
+ GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement();
+ gbDeviceEventAppManagement.uuid = uuid;
+ gbDeviceEventAppManagement.type = GBDeviceEventAppManagement.EventType.START;
+ gbDeviceEventAppManagement.event = GBDeviceEventAppManagement.Event.SUCCESS;
+ devEvts = new GBDeviceEvent[] {gbDeviceEventAppManagement};
+ }
} catch (JSONException e) {
- e.printStackTrace();
+ LOG.error(e.getMessage());
return null;
}
}
@@ -2308,6 +2324,15 @@ public class PebbleProtocol extends GBDeviceProtocol {
break;
case ENDPOINT_APPREORDER:
devEvts = new GBDeviceEvent[]{decodeAppReorder(buf)};
+ break;
+ case ENDPOINT_APPLOGS:
+ decodeAppLogs(buf);
+ break;
+// case ENDPOINT_VOICECONTROL:
+// devEvts = new GBDeviceEvent[]{decodeVoiceControl(buf)};
+// case ENDPOINT_AUDIOSTREAM:
+// LOG.debug(GB.hexdump(responseData, 0, responseData.length));
+// break;
default:
break;
}
@@ -2315,8 +2340,24 @@ public class PebbleProtocol extends GBDeviceProtocol {
return devEvts;
}
- public void setForceProtocol(boolean force) {
+ void setForceProtocol(boolean force) {
LOG.info("setting force protocol to " + force);
mForceProtocol = force;
}
+
+ private String getFixedString(ByteBuffer buf, int length) {
+ byte[] tmp = new byte[length];
+ buf.get(tmp, 0, length);
+
+ return new String(tmp).trim();
+ }
+
+ private UUID getUUID(ByteBuffer buf) {
+ ByteOrder byteOrder = buf.order();
+ buf.order(ByteOrder.BIG_ENDIAN);
+ long uuid_high = buf.getLong();
+ long uuid_low = buf.getLong();
+ buf.order(byteOrder);
+ return new UUID(uuid_high, uuid_low);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
index dcf14ada..2b37852a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
@@ -157,6 +157,13 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
}
}
+ @Override
+ public void onSendConfiguration(String config) {
+ if (reconnect()) {
+ super.onSendConfiguration(config);
+ }
+ }
+
@Override
public void onTestNewFunction() {
if (reconnect()) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java
new file mode 100644
index 00000000..e9935e18
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java
@@ -0,0 +1,226 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+import static android.bluetooth.BluetoothGattCharacteristic.FORMAT_UINT16;
+import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
+
+
+class PebbleGATTClient extends BluetoothGattCallback {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PebbleGATTClient.class);
+
+ private static final UUID SERVICE_UUID = UUID.fromString("0000fed9-0000-1000-8000-00805f9b34fb");
+ private static final UUID CONNECTIVITY_CHARACTERISTIC = UUID.fromString("00000001-328E-0FBB-C642-1AA6699BDADA");
+ private static final UUID PAIRING_TRIGGER_CHARACTERISTIC = UUID.fromString("00000002-328E-0FBB-C642-1AA6699BDADA");
+ private static final UUID MTU_CHARACTERISTIC = UUID.fromString("00000003-328e-0fbb-c642-1aa6699bdada");
+ private static final UUID CONNECTION_PARAMETERS_CHARACTERISTIC = UUID.fromString("00000005-328E-0FBB-C642-1AA6699BDADA");
+ private static final UUID CHARACTERISTIC_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+
+ private final BluetoothDevice mBtDevice;
+ private final Context mContext;
+ private final PebbleLESupport mPebbleLESupport;
+
+ private boolean oldPebble = false;
+ private boolean doPairing = true;
+ private boolean removeBond = false;
+ private BluetoothGatt mBluetoothGatt;
+
+ PebbleGATTClient(PebbleLESupport pebbleLESupport, Context context, BluetoothDevice btDevice) {
+ mContext = context;
+ mBtDevice = btDevice;
+ mPebbleLESupport = pebbleLESupport;
+ connectToPebble(mBtDevice);
+ }
+
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+ return;
+ }
+
+ if (characteristic.getUuid().equals(MTU_CHARACTERISTIC)) {
+ int newMTU = characteristic.getIntValue(FORMAT_UINT16, 0);
+ LOG.info("Pebble requested MTU: " + newMTU);
+ mPebbleLESupport.setMTU(newMTU);
+ } else {
+ LOG.info("onCharacteristicChanged()" + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1));
+ }
+ }
+
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+ return;
+ }
+
+ LOG.info("onCharacteristicRead() status = " + status);
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ LOG.info("onCharacteristicRead()" + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1));
+
+ if (oldPebble) {
+ subscribeToConnectivity(gatt);
+ } else {
+ subscribeToConnectionParams(gatt);
+ }
+ }
+ }
+
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+ return;
+ }
+
+ LOG.info("onConnectionStateChange() status = " + status + " newState = " + newState);
+ if (newState == BluetoothGatt.STATE_CONNECTED) {
+ LOG.info("calling discoverServices()");
+ gatt.discoverServices();
+ } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
+ mPebbleLESupport.close();
+ }
+ }
+
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+ return;
+ }
+
+ LOG.info("onCharacteristicWrite() " + characteristic.getUuid());
+ if (characteristic.getUuid().equals(PAIRING_TRIGGER_CHARACTERISTIC) || characteristic.getUuid().equals(CONNECTIVITY_CHARACTERISTIC)) {
+ //mBtDevice.createBond(); // did not work when last tried
+
+ if (oldPebble) {
+ subscribeToConnectivity(gatt);
+ } else {
+ subscribeToConnectionParams(gatt);
+ }
+ } else if (characteristic.getUuid().equals(MTU_CHARACTERISTIC)) {
+ if (GBApplication.isRunningLollipopOrLater()) {
+ gatt.requestMtu(339);
+ }
+ }
+ }
+
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor bluetoothGattDescriptor, int status) {
+ if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+ return;
+ }
+
+ LOG.info("onDescriptorWrite() status=" + status);
+
+ UUID CHARACTERISTICUUID = bluetoothGattDescriptor.getCharacteristic().getUuid();
+
+ if (CHARACTERISTICUUID.equals(CONNECTION_PARAMETERS_CHARACTERISTIC)) {
+ subscribeToConnectivity(gatt);
+ } else if (CHARACTERISTICUUID.equals(CONNECTIVITY_CHARACTERISTIC)) {
+ subscribeToMTU(gatt);
+ } else if (CHARACTERISTICUUID.equals(MTU_CHARACTERISTIC)) {
+ setMTU(gatt);
+ }
+ }
+
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (!mPebbleLESupport.isExpectedDevice(gatt.getDevice())) {
+ return;
+ }
+
+ LOG.info("onServicesDiscovered() status = " + status);
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ BluetoothGattCharacteristic connectionPararmharacteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC);
+ oldPebble = connectionPararmharacteristic == null;
+
+ if (oldPebble) {
+ LOG.info("This seems to be an older le enabled pebble");
+ }
+
+ if (doPairing) {
+ BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(PAIRING_TRIGGER_CHARACTERISTIC);
+ if ((characteristic.getProperties() & PROPERTY_WRITE) != 0) {
+ characteristic.setValue(new byte[]{1});
+ gatt.writeCharacteristic(characteristic);
+ } else {
+ LOG.info("This seems to be some <4.0 FW Pebble, reading pairing trigger");
+ gatt.readCharacteristic(characteristic);
+ }
+ } else {
+ if (oldPebble) {
+ subscribeToConnectivity(gatt);
+ } else {
+ subscribeToConnectionParams(gatt);
+ }
+ }
+ }
+ }
+
+ private void connectToPebble(BluetoothDevice btDevice) {
+ if (removeBond) {
+ try {
+ Method m = btDevice.getClass()
+ .getMethod("removeBond", (Class[]) null);
+ m.invoke(btDevice, (Object[]) null);
+ } catch (Exception e) {
+ LOG.warn(e.getMessage());
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignore) {
+ }
+ }
+ if (mBluetoothGatt != null) {
+ this.close();
+ }
+ mBluetoothGatt = btDevice.connectGatt(mContext, false, this);
+ }
+
+ private void subscribeToConnectivity(BluetoothGatt gatt) {
+ LOG.info("subscribing to connectivity characteristic");
+ BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC), true);
+ }
+
+ private void subscribeToMTU(BluetoothGatt gatt) {
+ LOG.info("subscribing to mtu characteristic");
+ BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC), true);
+ }
+
+ private void subscribeToConnectionParams(BluetoothGatt gatt) {
+ LOG.info("subscribing to connection parameters characteristic");
+ BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ gatt.writeDescriptor(descriptor);
+ gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC), true);
+ }
+
+ private void setMTU(BluetoothGatt gatt) {
+ LOG.info("setting MTU");
+ BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC);
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
+ descriptor.setValue(new byte[]{0x0b, 0x01}); // unknown
+ gatt.writeCharacteristic(characteristic);
+ }
+
+ public void close() {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java
new file mode 100644
index 00000000..d3f54ee7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java
@@ -0,0 +1,191 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
+
+class PebbleGATTServer extends BluetoothGattServerCallback {
+ private static final Logger LOG = LoggerFactory.getLogger(PebbleGATTServer.class);
+ private static final UUID WRITE_CHARACTERISTICS = UUID.fromString("10000001-328E-0FBB-C642-1AA6699BDADA");
+ private static final UUID READ_CHARACTERISTICS = UUID.fromString("10000002-328E-0FBB-C642-1AA6699BDADA");
+ private static final UUID CHARACTERISTICS_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+ private static final UUID SERVER_SERVICE = UUID.fromString("10000000-328E-0FBB-C642-1AA6699BDADA");
+ private static final UUID SERVER_SERVICE_BADBAD = UUID.fromString("BADBADBA-DBAD-BADB-ADBA-BADBADBADBAD");
+ private final BluetoothDevice mBtDevice;
+ private final PebbleLESupport mPebbleLESupport;
+ private Context mContext;
+ private BluetoothGattServer mBluetoothGattServer;
+ private BluetoothGattCharacteristic writeCharacteristics;
+
+ PebbleGATTServer(PebbleLESupport pebbleLESupport, Context context, BluetoothDevice btDevice) {
+ mContext = context;
+ mBtDevice = btDevice;
+ mPebbleLESupport = pebbleLESupport;
+ }
+
+ boolean initialize() {
+ BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+
+ mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this);
+ if (mBluetoothGattServer == null) {
+ return false;
+ }
+
+ BluetoothGattService pebbleGATTService = new BluetoothGattService(SERVER_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ pebbleGATTService.addCharacteristic(new BluetoothGattCharacteristic(READ_CHARACTERISTICS, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ));
+
+ writeCharacteristics = new BluetoothGattCharacteristic(WRITE_CHARACTERISTICS, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ writeCharacteristics.addDescriptor(new BluetoothGattDescriptor(CHARACTERISTICS_CONFIGURATION_DESCRIPTOR, BluetoothGattDescriptor.PERMISSION_WRITE));
+ pebbleGATTService.addCharacteristic(writeCharacteristics);
+ mBluetoothGattServer.addService(pebbleGATTService);
+
+ return true;
+ }
+
+ synchronized void sendDataToPebble(byte[] data) {
+ //LOG.info("send data to pebble " + GB.hexdump(data, 0, -1));
+ writeCharacteristics.setValue(data.clone());
+
+ mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false);
+ }
+
+ synchronized private void sendAckToPebble(int serial) {
+ writeCharacteristics.setValue(new byte[]{(byte) (((serial << 3) | 1) & 0xff)});
+
+ mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false);
+
+ try {
+ Thread.sleep(100); // FIXME: bad bad, I mean BAAAD
+ } catch (InterruptedException ignore) {
+ }
+ }
+
+ public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+ if (!mPebbleLESupport.isExpectedDevice(device)) {
+ return;
+ }
+
+ if (!characteristic.getUuid().equals(READ_CHARACTERISTICS)) {
+ LOG.warn("unexpected read request");
+ return;
+ }
+
+ LOG.info("will send response to read request from device: " + device.getAddress());
+ if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, new byte[]{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})) {
+ LOG.warn("error sending response");
+ }
+ }
+
+
+ public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+ if (!mPebbleLESupport.isExpectedDevice(device)) {
+ return;
+ }
+
+ if (!characteristic.getUuid().equals(WRITE_CHARACTERISTICS)) {
+ LOG.warn("unexpected write request");
+ return;
+ }
+ if (!mPebbleLESupport.mIsConnected) {
+ mPebbleLESupport.mIsConnected = true;
+ synchronized (mPebbleLESupport) {
+ mPebbleLESupport.notify();
+ }
+ }
+ //LOG.info("write request: offset = " + offset + " value = " + GB.hexdump(value, 0, -1));
+ int header = value[0] & 0xff;
+ int command = header & 7;
+ int serial = header >> 3;
+ if (command == 0x01) {
+ LOG.info("got ACK for serial = " + serial);
+ }
+ if (command == 0x02) { // some request?
+ LOG.info("got command 0x02");
+ if (value.length > 1) {
+ sendDataToPebble(new byte[]{0x03, 0x19, 0x19}); // no we don't know what that means
+ mPebbleLESupport.createPipedInputReader(); // FIXME: maybe not here
+ } else {
+ sendDataToPebble(new byte[]{0x03}); // no we don't know what that means
+ }
+ } else if (command == 0) { // normal package
+ LOG.info("got PPoGATT package serial = " + serial + " sending ACK");
+
+ sendAckToPebble(serial);
+
+ mPebbleLESupport.writeToPipedOutputStream(value, 1, value.length - 1);
+ }
+ }
+
+ public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+ if (!mPebbleLESupport.isExpectedDevice(device)) {
+ return;
+ }
+
+ LOG.info("Connection state change for device: " + device.getAddress() + " status = " + status + " newState = " + newState);
+ if (newState == BluetoothGattServer.STATE_DISCONNECTED) {
+ mPebbleLESupport.close();
+ }
+ }
+
+ public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+
+ if (!mPebbleLESupport.isExpectedDevice(device)) {
+ return;
+ }
+
+ if (!descriptor.getCharacteristic().getUuid().equals(WRITE_CHARACTERISTICS)) {
+ LOG.warn("unexpected write request");
+ return;
+ }
+
+ LOG.info("onDescriptorWriteRequest() notifications enabled = " + (value[0] == 1));
+ if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) {
+ LOG.warn("onDescriptorWriteRequest() error sending response!");
+ }
+ }
+
+ public void onServiceAdded(int status, BluetoothGattService service) {
+ LOG.info("onServiceAdded() status = " + status + " service = " + service.getUuid());
+ if (status == BluetoothGatt.GATT_SUCCESS && service.getUuid().equals(SERVER_SERVICE)) {
+ final BluetoothGattService badbadService = new BluetoothGattService(SERVER_SERVICE_BADBAD, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ badbadService.addCharacteristic(new BluetoothGattCharacteristic(SERVER_SERVICE_BADBAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ));
+ mBluetoothGattServer.addService(badbadService);
+ }
+ }
+
+ @Override
+ public void onMtuChanged(BluetoothDevice device, int mtu) {
+ if (!mPebbleLESupport.isExpectedDevice(device)) {
+ return;
+ }
+
+ LOG.info("Pebble requested mtu for server: " + mtu);
+ mPebbleLESupport.setMTU(mtu);
+ }
+
+ public void onNotificationSent(BluetoothDevice bluetoothDevice, int status) {
+ //LOG.info("onNotificationSent() status = " + status + " to device " + mmBtDevice.getAddress());
+ }
+
+ void close() {
+ if (mBluetoothGattServer != null) {
+ mBluetoothGattServer.cancelConnection(mBtDevice);
+ mBluetoothGattServer.clearServices();
+ mBluetoothGattServer.close();
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java
new file mode 100644
index 00000000..6fa3d2c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java
@@ -0,0 +1,171 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+public class PebbleLESupport {
+ private static final Logger LOG = LoggerFactory.getLogger(PebbleLESupport.class);
+ private final BluetoothDevice mBtDevice;
+ private PipeReader mPipeReader;
+ private PebbleGATTServer mPebbleGATTServer;
+ private PebbleGATTClient mPebbleGATTClient;
+ private PipedInputStream mPipedInputStream;
+ private PipedOutputStream mPipedOutputStream;
+ private int mMTU = 20;
+ boolean mIsConnected = false;
+
+ public PebbleLESupport(Context context, final BluetoothDevice btDevice, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) throws IOException {
+ mBtDevice = btDevice;
+ mPipedInputStream = new PipedInputStream();
+ mPipedOutputStream = new PipedOutputStream();
+ try {
+ pipedOutputStream.connect(mPipedInputStream);
+ pipedInputStream.connect(mPipedOutputStream);
+ } catch (IOException e) {
+ LOG.warn("could not connect input stream");
+ }
+
+ mPebbleGATTServer = new PebbleGATTServer(this, context, mBtDevice);
+ if (mPebbleGATTServer.initialize()) {
+ mPebbleGATTClient = new PebbleGATTClient(this, context, mBtDevice);
+ try {
+ synchronized (this) {
+ wait(30000);
+ if (mIsConnected) {
+ return;
+ }
+ }
+ } catch (InterruptedException ignored) {
+ }
+ }
+ this.close();
+ throw new IOException("connection failed");
+ }
+
+ void writeToPipedOutputStream(byte[] value, int offset, int count) {
+ try {
+ mPipedOutputStream.write(value, offset, count);
+ } catch (IOException e) {
+ LOG.warn("error writing to output stream");
+ }
+ }
+
+ synchronized public void close() {
+ destroyPipedInputReader();
+ if (mPebbleGATTServer != null) {
+ mPebbleGATTServer.close();
+ mPebbleGATTServer = null;
+ }
+ if (mPebbleGATTClient != null) {
+ mPebbleGATTClient.close();
+ mPebbleGATTClient = null;
+ }
+ try {
+ mPipedInputStream.close();
+ } catch (IOException ignore) {
+ }
+ try {
+ mPipedOutputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+
+ synchronized void createPipedInputReader() {
+ if (mPipeReader == null) {
+ mPipeReader = new PipeReader();
+ }
+ if (!mPipeReader.isAlive()) {
+ mPipeReader.start();
+ }
+ }
+
+ synchronized private void destroyPipedInputReader() {
+ if (mPipeReader != null) {
+ mPipeReader.interrupt();
+ try {
+ mPipeReader.join();
+ } catch (InterruptedException e) {
+ LOG.error(e.getMessage());
+ }
+ mPipeReader = null;
+ }
+ }
+
+ void setMTU(int mtu) {
+ mMTU = mtu;
+ }
+
+ private class PipeReader extends Thread {
+ int mmSequence = 0;
+
+ @Override
+ public void run() {
+ byte[] buf = new byte[8192];
+ int bytesRead;
+ while (true) {
+ try {
+ // this code is very similar to iothread, that is bad
+ // because we are the ones who prepared the buffer, there should be no
+ // need to do crazy stuff just to find out the PP boundaries again.
+ bytesRead = mPipedInputStream.read(buf, 0, 4);
+ while (bytesRead < 4) {
+ bytesRead += mPipedInputStream.read(buf, bytesRead, 4 - bytesRead);
+ }
+
+ int length = (buf[0] & 0xff) << 8 | (buf[1] & 0xff);
+ bytesRead = mPipedInputStream.read(buf, 4, length);
+
+ while (bytesRead < length) {
+ bytesRead += mPipedInputStream.read(buf, bytesRead + 4, length - bytesRead);
+ }
+
+
+ int payloadToSend = bytesRead + 4;
+ int srcPos = 0;
+ while (payloadToSend > 0) {
+ int chunkSize = (payloadToSend < (mMTU - 4)) ? payloadToSend : mMTU - 4;
+ byte[] outBuf = new byte[chunkSize + 1];
+ outBuf[0] = (byte) ((mmSequence++ << 3) & 0xff);
+ System.arraycopy(buf, srcPos, outBuf, 1, chunkSize);
+ mPebbleGATTServer.sendDataToPebble(outBuf);
+ srcPos += chunkSize;
+ payloadToSend -= chunkSize;
+ }
+
+ Thread.sleep(500); // FIXME ugly wait 0.5s after each pebble package send to the pebble (we do not wait for the GATT chunks)
+ } catch (IOException | InterruptedException e) {
+ LOG.info(e.getMessage());
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ LOG.info("Pipereader thread shut down");
+ }
+
+ @Override
+ public void interrupt() {
+ super.interrupt();
+ try {
+ LOG.info("closing piped inputstream");
+ mPipedInputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ boolean isExpectedDevice(BluetoothDevice device) {
+ if (!device.getAddress().equals(mBtDevice.getAddress())) {
+ LOG.info("unhandled device: " + device.getAddress() + " , ignoring, will only talk to " + mBtDevice.getAddress());
+ return false;
+ }
+ return true;
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java
index e53d63b8..c4539a87 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java
@@ -26,7 +26,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
-import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
@@ -272,6 +271,11 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
return false;
}
+ @Override
+ public void onSendConfiguration(String config) {
+
+ }
+
@Override
public void onTestNewFunction() {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
index 71acbb87..9c8add33 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
@@ -215,6 +215,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
sendToDevice(bytes);
}
+ @Override
+ public void onSendConfiguration(String config) {
+ byte[] bytes = gbDeviceProtocol.encodeSendConfiguration(config);
+ sendToDevice(bytes);
+ }
+
@Override
public void onTestNewFunction() {
byte[] bytes = gbDeviceProtocol.encodeTestNewFunction();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java
index 31be6f02..a61d6a58 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceIoThread.java
@@ -21,7 +21,7 @@ public abstract class GBDeviceIoThread extends Thread {
return gbDevice;
}
- protected boolean connect(String btDeviceAddress) {
+ protected boolean connect() {
return false;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
index 55d4c10c..c4fe1e73 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
@@ -94,6 +94,10 @@ public abstract class GBDeviceProtocol {
return null;
}
+ public byte[] encodeSendConfiguration(String config) {
+ return null;
+ }
+
public byte[] encodeTestNewFunction() { return null; }
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
@@ -103,4 +107,5 @@ public abstract class GBDeviceProtocol {
public GBDevice getDevice() {
return mDevice;
}
+
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore
new file mode 100644
index 00000000..8242cf9f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/.gitignore
@@ -0,0 +1,2 @@
+small/
+SmallHelper.java
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
new file mode 100644
index 00000000..f08691e2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
@@ -0,0 +1,15 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+public class AndroidUtils {
+ public static ParcelUuid[] toParcelUUids(Parcelable[] uuids) {
+ if (uuids == null) {
+ return null;
+ }
+ ParcelUuid[] uuids2 = new ParcelUuid[uuids.length];
+ System.arraycopy(uuids, 0, uuids2, 0, uuids.length);
+ return uuids2;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java
index 7c767cc1..d0bee577 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java
@@ -1,39 +1,55 @@
package nodomain.freeyourgadget.gadgetbridge.util;
+import java.util.Collection;
+
public class ArrayUtils {
/**
- * Checks the two given arrays for equality, but comparing only a subset of the second
- * array with the whole first array.
+ * Checks the two given arrays for equality, but comparing the second array with a specified
+ * subset of the first array.
*
- * @param first the whole array to compare against
- * @param second the array, of which a subset shall be compared against the whole first array
- * @param secondStartIndex the start index (inclusive) of the second array from which to start the comparison
- * @param secondEndIndex the end index (exclusive) of the second array until which to compare
- * @return whether the first byte array is equal to the specified subset of the second byte array
- * @throws IllegalArgumentException when one of the arrays is null or start and end index are wrong
+ * @param first the array in which to look for the second array
+ * @param second the data to look for inside the first array
+ * @param startIndex the start index (inclusive) inside the first array from which to start the comparison
+ * @return whether the second byte array is equal to the specified subset of the first byte array
+ * @throws IllegalArgumentException when one of the arrays is null or start index/length are wrong
*/
- public static boolean equals(byte[] first, byte[] second, int secondStartIndex, int secondEndIndex) {
+ public static boolean equals(byte[] first, byte[] second, int startIndex) {
if (first == null) {
throw new IllegalArgumentException("first must not be null");
}
if (second == null) {
throw new IllegalArgumentException("second must not be null");
}
- if (secondStartIndex >= secondEndIndex) {
- throw new IllegalArgumentException("secondStartIndex must be smaller than secondEndIndex");
+ if (startIndex < 0) {
+ throw new IllegalArgumentException("startIndex must be >= 0");
}
- if (second.length < secondEndIndex) {
- throw new IllegalArgumentException("secondStartIndex must be smaller than secondEndIndex");
- }
- if (first.length < secondEndIndex) {
+
+ if (second.length + startIndex > first.length) {
return false;
}
- int len = secondEndIndex - secondStartIndex;
- for (int i = 0; i < len; i++) {
- if (first[i] != second[secondStartIndex + i]) {
+ for (int i = 0; i < second.length; i++) {
+ if (first[startIndex + i] != second[i]) {
return false;
}
}
return true;
}
+
+ /**
+ * Converts a collection of Integer values to an int[] array.
+ * @param values
+ * @return null if the given collection is null, otherwise an array of the same size as the collection
+ * @throws NullPointerException when an element of the collection is null
+ */
+ public static int[] toIntArray(Collection values) {
+ if (values == null) {
+ return null;
+ }
+ int i = 0;
+ int[] result = new int[values.size()];
+ for (Integer value : values) {
+ result[i++] = value;
+ }
+ return result;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
index a14aff80..0b0c6a8d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
@@ -8,7 +8,6 @@ import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
@@ -23,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
@@ -47,16 +47,17 @@ public class DeviceHelper {
// lazily created
private List coordinators;
- public boolean isSupported(GBDeviceCandidate candidate) {
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
for (DeviceCoordinator coordinator : getAllCoordinators()) {
- if (coordinator.supports(candidate)) {
- return true;
+ DeviceType deviceType = coordinator.getSupportedType(candidate);
+ if (deviceType.isSupported()) {
+ return deviceType;
}
}
- return false;
+ return DeviceType.UNKNOWN;
}
- public boolean isSupported(GBDevice device) {
+ public boolean getSupportedType(GBDevice device) {
for (DeviceCoordinator coordinator : getAllCoordinators()) {
if (coordinator.supports(device)) {
return true;
@@ -120,7 +121,7 @@ public class DeviceHelper {
}
public GBDevice toSupportedDevice(BluetoothDevice device) {
- GBDeviceCandidate candidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN);
+ GBDeviceCandidate candidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN, device.getUuids());
for (DeviceCoordinator coordinator : getAllCoordinators()) {
if (coordinator.supports(candidate)) {
@@ -165,6 +166,7 @@ public class DeviceHelper {
result.add(new MiBandCoordinator());
result.add(new PebbleCoordinator());
result.add(new VibratissimoCoordinator());
+ result.add(new LiveviewCoordinator());
return result;
}
@@ -174,7 +176,7 @@ public class DeviceHelper {
List activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
for (Device dbDevice : activeDevices) {
GBDevice gbDevice = toGBDevice(dbDevice);
- if (gbDevice != null && DeviceHelper.getInstance().isSupported(gbDevice)) {
+ if (gbDevice != null && DeviceHelper.getInstance().getSupportedType(gbDevice)) {
result.add(gbDevice);
}
}
@@ -192,7 +194,7 @@ public class DeviceHelper {
* @param dbDevice
* @return
*/
- private GBDevice toGBDevice(Device dbDevice) {
+ public GBDevice toGBDevice(Device dbDevice) {
DeviceType deviceType = DeviceType.fromKey(dbDevice.getType());
GBDevice gbDevice = new GBDevice(dbDevice.getIdentifier(), dbDevice.getName(), deviceType);
List deviceAttributesList = dbDevice.getDeviceAttributesList();
@@ -201,6 +203,7 @@ public class DeviceHelper {
DeviceAttributes attrs = deviceAttributesList.get(0);
gbDevice.setFirmwareVersion(attrs.getFirmwareVersion1());
gbDevice.setFirmwareVersion2(attrs.getFirmwareVersion2());
+ gbDevice.setVolatileAddress(attrs.getVolatileIdentifier());
}
return gbDevice;
@@ -211,6 +214,9 @@ public class DeviceHelper {
List result = new ArrayList<>(pairedDevices.size());
DeviceHelper deviceHelper = DeviceHelper.getInstance();
for (BluetoothDevice pairedDevice : pairedDevices) {
+ if (pairedDevice.getName() != null && (pairedDevice.getName().startsWith("Pebble-LE ") || pairedDevice.getName().startsWith("Pebble Time LE "))) {
+ continue; // ignore LE Pebble (this is part of the main device now (volatileAddress)
+ }
GBDevice device = deviceHelper.toSupportedDevice(pairedDevice);
if (device != null) {
result.add(device);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
index 9bfa4f37..7d7d7f45 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
@@ -128,7 +128,7 @@ public class GB {
// file header
headerbuf.put((byte) 'B');
headerbuf.put((byte) 'M');
- headerbuf.putInt(0); // size in bytes (unconpressed = 0)
+ headerbuf.putInt(0); // size in bytes (uncompressed = 0)
headerbuf.putInt(0); // reserved
headerbuf.putInt(FILE_HEADER_SIZE + INFO_HEADER_SIZE + screenshot.clut.length);
@@ -139,7 +139,7 @@ public class GB {
headerbuf.putShort((short) 1); // planes
headerbuf.putShort((short) screenshot.bpp);
headerbuf.putInt(0); // compression
- headerbuf.putInt(0); // length of pixeldata in byte (uncompressed=0)
+ headerbuf.putInt(0); // length of pixeldata in bytes (uncompressed=0)
headerbuf.putInt(0); // pixels per meter (x)
headerbuf.putInt(0); // pixels per meter (y)
headerbuf.putInt(screenshot.clut.length / 4); // number of colors in CLUT
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java
index bd18cde2..f6597807 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java
@@ -37,4 +37,14 @@ public class PebbleUtils {
public static int getFwMajor(String fwString) {
return fwString.charAt(1) - 48;
}
+
+ public static boolean hasHRM(String hwRev) {
+ String platformName = getPlatformName(hwRev);
+ return "diorite".equals(platformName) || "emery".equals(platformName);
+ }
+
+ public static boolean hasHealth(String hwRev) {
+ String platformName = getPlatformName(hwRev);
+ return !"aplite".equals(platformName);
+ }
}
diff --git a/app/src/main/res/layout/activity_mi_band_pairing.xml b/app/src/main/res/layout/activity_mi_band_pairing.xml
index b875dd34..9df8b8c7 100644
--- a/app/src/main/res/layout/activity_mi_band_pairing.xml
+++ b/app/src/main/res/layout/activity_mi_band_pairing.xml
@@ -6,7 +6,7 @@
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPairingActivity">
-
diff --git a/app/src/main/res/layout/activity_pebble_pairing.xml b/app/src/main/res/layout/activity_pebble_pairing.xml
new file mode 100644
index 00000000..d0793d61
--- /dev/null
+++ b/app/src/main/res/layout/activity_pebble_pairing.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml
index 8a2cadca..43645438 100644
--- a/app/src/main/res/menu/appmanager_context.xml
+++ b/app/src/main/res/menu/appmanager_context.xml
@@ -15,6 +15,12 @@
+
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4275efca..0f46a0a7 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -126,7 +126,7 @@
Gerät paaren
Verwende den Android Bluetooth Paaren-Dialog um Dein Gerät zu paaren.
Paare Dein Mi Band
- Paarung mit %s…
+ Paarung mit %s…
Kein MAC Adresse bekommen, kann nicht paaren.
Gerätespezifische Einstellungen
Mi Band Einstellungen
@@ -284,10 +284,12 @@ Wenn Du schon deine Daten importiert hast und mit dem Ergebnis zufrieden bist, k
Import erfolgreich.
Fehler beim Importieren der DB: %1$s
Führe Aktivitätsdaten zusammen
+ Beim Importieren der alten Aktivitätsdaten ist ein Fehler aufgetreten!
Alte Daten löschen?
Daten erfolgreich gelöscht.
Löschen der Datenbank fehlgeschlagen.
Alte Aktivitätsdatenbank löschen?
+ Wirklich die alte Aktivitätsdatenbank löschen? Nicht importierte Aktivitätsdaten gehen verloren.
Alte Aktivitätsdatenbank erfolgreich gelöscht.
Löschen der alten Aktivitätsdatenbank fehlgeschlagen.
Überschreiben
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 9d013624..3214d171 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -22,8 +22,11 @@
Borrar
Borrar y quitar de la caché
Reinstalar
+ Buscar en la Appstore de Pebble
Activar
Desactivar
+ Activar Monitor de Ritmo Cardíaco
+ Desactivar Monitor de Ritmo Cardíaco
Configurar
Mover a la parte de arriba
@@ -33,7 +36,7 @@
Estás a punto de instalar el firmware %s en lugar del que está en tu MiBand.
Estás a punto de instalar los firmwares %1$s y %2$s en lugar de los que están en tu MiBand.
Este firmware ha sido probado y se sabe que es compatible con Gadgetbridge.
- Este firmware no ha sido probado y puede no sea compatible con Gadgetbridge.\n\nNO se recomienda la instalación en tu MiBand!.
+ Este firmware no ha sido probado y puede que no sea compatible con Gadgetbridge.\n\nNO se recomienda la instalación en tu MiBand!.
Si aún así quieres seguir y las cosas continúan funcionando correctamente después de esto, por favor indícales a los desarrolladores de Gadgetbridge que la versión del firmware: %s funciona bien.
Ajustes
@@ -46,15 +49,16 @@
Sincronizar hora
Sincroniza la hora en el dispositivo cuando se conecte o se cambie la hora o zona horaria en Android
Tema
- Ligero
+ Claro
Oscuro
- Lenguaje
+ Idioma
Notificaciones
Repeticiones
Llamadas telefónicas
SMS
K9-Mail
Mensajes de Pebble
+ Soporte para aplicaciones que envían notificaciones a Pebble a través de PebbleKit.
Soporte para notificaciones genéricas
… también con pantalla encendida
No Molestar
@@ -63,7 +67,7 @@
cuando la pantalla está apagada
nunca
Excluir aplicaciones
- Mensajes preservados
+ Mensajes predeterminados
Respuestas
Sufijo habitual
Rechazar llamada
@@ -78,18 +82,24 @@
Sincronizar con Morpheuz
Permitir el acceso a aplicaciones Android de terceros
Permitir el soporte experimental para aplicaciones Android que usan PebbleKit
- Salida y Puesta de Sol
- Enviar los tiempos de Salida y Puesta del sol basándose en la localización a la línea cronológica del Pebble
+ Salida y puesta de Sol
+ Enviar las horas de salida y puesta de Sol basándose en la localización a la línea cronológica del Pebble
Localización
Buscar localización
Latitud
Longitud
+ Mantener la localización actualizada
+ Intentar conseguir la localización en marcha, usar la guardada como respaldo
Por favor, activa la localización por red
localización encontrada
Forzar protocolo de notificación
Esta opción fuerza el uso del último protocolo de notificación dependiendo de la versión de firmware. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO!
Habilitar características no probadas
Habilita características que no han sido probadas. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO!
+ Preferir siempre BLE
+ Usar el soporte experimental de Pebble LE para todos los Pebble en lugar del bluetooth clásico. Requiere vincular \"Pebble LE\" si un Pebble no-LE ha sido vinculado antes.
+ Activar crear registros de la App del Reloj
+ Producirá registros de las apps del reloj que Gadgetbridge guardará (necesita reconexión)
Intentos de reconexión
no conectado
conectando
@@ -101,8 +111,8 @@
Probar
Probar notificación
Notificación de prueba desde Gadgetbridge
- Bluetooth no está soportado.
- Bluetooth está desactivado.
+ El Bluetooth no está soportado.
+ El Bluetooth está desactivado.
Pulsa el dispositivo conectado para Gestión de App
Pulsa el dispositivo conectado para Actividad
Pulsa el dispositivo conectado para Vibración
@@ -111,33 +121,37 @@
Gadgetbridge funcionando
instalando binario %1$d/%2$d
¡La instalación ha fallado!
- instalación satisfactoria
+ instalación correcta
ESTÁS INTENTANDO INSTALAR UN FIRMWARE, CONTINÚA BAJO TU RESPONSABILIDAD.\n\n\n Este firmware es para la revisión de HW: %s
Estás a punto de instalar la siguiente app:\n\n\n%1$s Versión %2$s de %3$s\n
N/A
iniciado
%1$s de %2$s
Detección de dispositivos
- Finalizar el escaneo
+ Detener el escaneo
Comenzar la detección
Conectar un nuevo dispositivo
%1$s (%2$s)
Emparejar dispositivo
Usar el menú de emparejar Bluetooth de Android para emparejar el dispositivo.
Emparejar tu MiBand
- Emparejando con %s...
- Ninguna dirección MAC aprobada, no se puede emparejar.
+ Emparejando con %s...
+ Creando emparejamiento con %1$s (%2$s)
+ No se ha podido emparejar con %1$s (%2$s)
+ Emparejamiento en curso: %1$s (%2$s)
+ Emparejado con %1$s (%2$s), conectando…
+ Ninguna dirección MAC proporcionada, no se puede emparejar.
Ajustes específicos del dispositivo
Ajustes de MiBand
- masculino
- femenino
- otro
+ Hombre
+ Mujer
+ Otro
izquierda
derecha
No se han proporcionado datos de usuario válidos, se usarán datos de usuario por defecto.
Cuando tu MiBand vibre y parpadee, púlsala repetidas veces.
Instalar
- Haz tu dispositivo detectable. Los dipositivos ya conectados es probable que no se encuentren. Si tu dispositivo no aparece en dos minutos, prueba de nuevo reiniciando tu móvil.
+ Haga su dispositivo detectable. Los dispositivos conectados no serán detectables. En Android 6 o superior, necesita activar la localización (ej. GPS). Si su dispositivo no aparece en dos minutos, vuelva a intentarlo tras reiniciar su teléfono.
Nota:
Imagen del dispositivo
Nombre/Apodo
@@ -157,7 +171,7 @@
Ring
Alarma
Vibración
- Intentar
+ Probar
Notificación de SMS
Ajustes de vibración
Notificación genérica
@@ -179,7 +193,7 @@
Jue
Vie
Sáb
- Despertar avanzado
+ Alarma inteligente
Hubo un error al configurar las alarmas, ¡Por favor, inténtalo de nuevo!
¡Alarmas enviadas al dispositivo!
Sin datos. ¿Sincronizar dispositivo?
@@ -207,9 +221,9 @@
Conexión al dispositivo: %1$s
Pebble firmware %1$s
Revisión de hardware correcta
- La revisión de hardware incorrecta!
+ La revisión de hardware es incorrecta!
%1$s (%2$s)
- Hubo un problema con la transferencia de firmware. NO REINICIES tu MiBand!
+ Hubo un problema con la transferencia de firmware. NO REINICIES tu Mi Band!
Hubo un problema con la transferencia de metadatos del firmware
Instalación del firmware completa
Instalación del firmware completa, reiniciando dispositivo...
@@ -218,8 +232,8 @@
Actividad
Pasos hoy, objetivo: %1$s
No confirmar transferencia
- Si los datos no son marcados como descargados, no serán borrados de tu MiBand. Útil si Gadgetbridge se usa conjuntamente con otras apps.
- Mantendrá los datos de actividad en la MiBand incluso después de la sincronización. Útil si GB se usa junto con otras apps.
+ Si los datos no son marcados como descargados, no serán borrados de tu Mi Band. Útil si Gadgetbridge se usa conjuntamente con otras apps.
+ Mantendrá los datos de actividad en la Mi Band incluso después de la sincronización. Útil si GB se usa junto con otras apps.
Usa el modo de baja latencia para las actualizaciones de FW
Esto podría ayudar en dispositivos donde las actualizaciones de firmware fallan
Historial de pasos
@@ -239,6 +253,11 @@
Reserva de alarmas para próximos eventos
Usar sensor de pulsaciones para mejorar la detección del sueño
Compensación de la hora del dispositivo en horas (para detectar el sueño de trabajadores a turnos)
+ Mi2: Formato de fecha
+ Tiempo
+
+ Activar pantalla al levantar
+ A punto de transferir datos desde %1$s
esperando reconexión
Sobre ti
Año de nacimiento
@@ -262,10 +281,17 @@
Importar Base de Datos
Importar datos de actividad antiguos
+ Desde Gadgetbridge 0.12.0 usamos un nuevo formato de base de datos.
+Se pueden importar los antiguos datos de actividad y asociarlos con el dispoditivo al que se está conectando (%1$s).\n
+\n
+Si no importas los antiguos datos de actividad ahora, siempre lo podrás hacer después seleccionando \"MERGE OLD ACTIVITY DATA\" en el apartado de gestión de base de datos de actividad.\n
+\n
+Por favor, ten en cuenta que puedes importar datos desde Mi Band, Pebble Health y Morpheuz pero NO desde Pebble Misfit.
Almacenar datos en bruto en la base de datos
Seleccionado, los datos archivados se guardan en bruto y están disponibles para ser interpretados más tarde. La base de datos será más grande.
Administración de Bases de Datos
Administración de Bases de Datos
+ La base de datos usa la siguiente ubicación en su dispositivo. \nEsta ubicación está accesible para otras aplicaciones Android y para su ordenador. \nEncontrará sus bases de datos exportadas (o la que quiere importar) aquí:
Los datos de actividad grabados con versiones anteriores de Gadgetbridge a 0.12 deben ser convertidos a un nuevo formato. \nPuede hacerlo utilizando el botón de abajo. ¡Tenga en cuenta que debe estar conectado con el dispositivo al que desea asociar los antiguos datos de la actividad! \nSi ya importó sus datos y está satisfecho con el resultado, es posible eliminar la base de datos antigua.
Importar/Borrar Base de Datos Antigua
No se puede acceder a la ruta para exportar . Por favor, contacta con los desarrolladores.
@@ -294,4 +320,7 @@
Borrar
Vibración
+
+ Emparejando con Pebble
+ En su dispositivo Android va a aparecer un mensaje para emparejarse. Si no apareciera, mire en el cajón de notificaciones y acepte la propuesta de emparejamiento. Después acepte también en su Pebble.
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 133e23f5..f634a340 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -10,6 +10,8 @@
Trouver l\'appareil
Prendre une capture d\'écran
Déconnexion
+ Supprimer l\'appareil
+ Supprimer %1$s
Déboguer
Gestionnaire d\'application
@@ -45,11 +47,11 @@
Thème
Clair
Sombre
- Langage
+ Langue
Notifications
Répétitions
Appels téléphoniques
- Textos
+ SMS
K9-Mail
Messages Pebble
Support des applications qui envoient des notification à Pebble par PebbleKit.
@@ -60,7 +62,7 @@
toujours
quand l\'écran est éteint
jamais
- Apps bloquées
+ Applications bloquées
Modèles de messages
Réponses
Suffixe commun
@@ -82,6 +84,8 @@
Obtenir l\'emplacement
Latitude
Longitude
+ Garder l\'emplacement à jour
+ Tenter d\'obtenir la localisation pendant la course à pied, utiliser la localisation enregistré en cas de problème.
Activez la localisation réseau s\'il vous plaît
Emplacement obtenu
Protocole des notifications en vigueur
@@ -123,7 +127,7 @@
Coupler l\'appareil
Utiliser le couplement Bluetooth d\'Android pour coupler l\'appareil
Coupler votre Mi Band
- Coupler avec %s...
+ Coupler avec %s...
Aucune adresse mac fournie, ne peut être couplé
Paramètres spécifiques à l\'appareil
Paramètres Mi Band
@@ -145,7 +149,7 @@
Initialisation
Récupération des données d\'activité
De %1$s à %2$s
- Port main gauche ou droite?
+ Côté de port du bracelet
Profil de vibration
Saccadé
Court
@@ -155,7 +159,10 @@
Sonnette
Réveil
Vibration
- Notification Texto
+ Notification SMS
+ Clavardage (Chat)
+ Navigation
+ Réseaux sociaux
Paramètres des vibrations
Notification générique
Notification Pebble
@@ -211,7 +218,7 @@
Échec lors de l\'écriture du micrologiciel
Pas
Activité en direct
- Nombre de pas aujourd\'hui, objectif: %1$s
+ Nombre de pas aujourd\'hui, objectif : %1$s
Ne pas confirmer le transfert de données d\'activités
Les données d\'activités ne seront pas effacées du bracelet si elles ne sont pas confirmées. Utile si GB est utilisé avec d\'autres applications.
Les données d\'activités seront conservées sur le Mi Band après la synchronisation. Utile si GB est utilisé avec d\'autres applications.
@@ -234,6 +241,11 @@
Alarmes à réserver pour événements futurs
Utiliser le capteur cardiaque pour améliorer la précision du sommeil
La compensation de temps en heure (pour détecter le sommeil de travailleurs en rotation, par exemple)
+ Mi band 2 : format de l\'heure
+ Heure seule
+
+ Allumer quand le poignet se lève
+ About to transfer data since %1$s
en attente de reconnexion
A propos de vous
Année de naissance
@@ -244,7 +256,7 @@
authentification requise
ZzZz
Ajouter un widget
- Préférer le mode heure pendant le sommeil
+ Durée préférée de sommeil en heures
Une alarme a été enregistré pour %1$02d:%2$02d
Modèle: %1$s
Micrologiciel: %1$s
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index c0d6cfaa..6c9eec16 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -12,29 +12,32 @@
Szétcsatlakoztatás
Eszköz törlése
%1$s törlése
- Az eszköz törlésre és az összes hozzátartozó adat törlésre kerül!
+ Az eszköz és az összes hozzátartozó adat törlésre kerül!
Hibakeresés
- Alkalmazás kezelő
- Applikációk gyorsítótárban
- Telepített applikációk
+ Alkalmazáskezelő
+ Gyorsítótárazott alkalmazások
+ Telepített alkalmazások
Telepített számlapok
Törlés
Törlés és eltávolítás a gyorsítótárból
Újratelepítés
+ Keresés a Pebble alkalmazás-áruházban
Aktiválás
Deaktiválás
+ Pulzusmérő aktiválása
+ Pulzusmérő deaktiválása
Beállítás
A tetejére mozgatás
Értesítés tiltólista
FW/Alkalmazás telepítő
- Firmware-t fogsz telepíteni: %s a mostani helyett a Mi Band-edre.
- Firmware-t fogsz telepíteni: %1$s és %2$s a mostani helyett a Mi Band-edre.
+ A %s firmware-változatot tervezed telepíteni a Mi Band-edre a mostani helyett.
+ A %1$s és %2$s firmware-változatokat tervezed telepíteni a Mi Band-edre a mostani helyett.
Ez a firmware tesztelt és ismerten kompatibilis a Gadgetbridge-dzsel.
- Ez a firmware nincs tesztelve, lehetséges, hogy nem kompatibilis a Gadgetbridge-dzsel.\n\nNem javasoljuk, hogy feltelepítsd a Mi Band-edre!
- Ha ennek ellenére is telepíteni szeretnéd, és a dolgok jól működnek, utána kérlek jelezd a Gadgetbridge fejlesztőinek, hogy fehérlistára tehessék a
+ Ez a firmware nincs tesztelve, lehetséges, hogy nem kompatibilis a Gadgetbridge-dzsel.\n\nNEM javasoljuk, hogy feltelepítsd a Mi Band-edre!
+ Ha ennek ellenére is telepíteni szeretnéd, és a dolgok jól működnek, utána kérlek jelezd a Gadgetbridge fejlesztőinek, hogy fehérlistára tehessék a %s firmware-változatot.
Beállítások
Általános beállítások
@@ -54,8 +57,8 @@
Telefonhívások
SMS
K9-Mail
- Pebble Üzenetek
- Támogatás az alkalmazásoknak, amik értesítést küldenek a Pebble-nek Intent-en keresztül. Ez beszélgetésre használható.
+ Pebble üzenetek
+ Támogatás az alkalmazásoknak, amik értesítéseket küldenek a Pebble-nek a PebbleKit-en keresztül.
Általános értesítési támogatás
… és amikor a kijelző be van kapcsolva
Ne zavarj mód
@@ -64,13 +67,16 @@
amikor a kijelző ki van kapcsolva
soha
Tiltott alkalmazások
+ Előre beállított üzenetek
Előre megírt válaszok
Közös előtag
+ Hívás elutasítás
+ Frissítés a Pebble-n
Fejlesztői beállítások
Mi Band cím
Pebble beállítások
Aktivitásmérők
- Preferált Aktivitásmérők
+ Preferált Aktivitásmérő
Pebble Health szinkronizálása
Misfit szinkronizálás
Morpheuz szinkronizálás
@@ -78,16 +84,22 @@
Kísérleti támogatás engedélyezése az Android alkalmazások számára a PebbleKit használatára
Napkelte és napnyugta
A napkelte és napnyugta idejének küldése a Pebble idővonalra a hely és idő alapján
- Helység
- Földrajzi hely
+ Pozició
+ Pozició meghatározása
Szélesség
Hosszúság
+ Pozició frissentartása
+ Aktuális pozició meghatározásának megpróbálása futásidőben, hiba esetén a letárolt pozició használata
Kérlek, engedélyezd a hálózati helymeghatározást.
- Lokáció megszerezve
- Erőltetett értesítés protokoll
- Ez az opció erőlteti az utolsó értesítési protokollt a firmware verziótól függően. CSAK AKKOR ENGEDÉLYEZD, HA TUDOD MIT CSINÁLSZ!
+ Hely meghatározva
+ Értesítési protokoll erőltetése
+ Ez az opció a legfrissebb értesítési protokollt erőlteti a firmware verziótól függően. CSAK AKKOR ENGEDÉLYEZD, HA TUDOD MIT CSINÁLSZ!
Nem tesztelt funkciók engedélyezése
Nem tesztelt funkciók engedélyezése. CSAK AKKOR ENGEDÉLYEZD, HA TUDOD MIT CSINÁLSZ!
+ Mindig preferálja a BLE-t
+ A kísérleti LE támogatás használata minden Pebble-hez a klasszikus BT helyett, párosítani kell hozzá egy \"Pebble LE\"-t, miután a nem-LE-t párosítva lett egyszer.
+ Óraalkalmazás-naplózás engedélyezése
+ Az óra-alkalmazások naplóit a Gadgetbridge is naplózni fogja (újracsatlakozást igényel)
Újracsatlakozási kísérletek száma
nincs csatlakozva
csatlakozás
@@ -101,21 +113,21 @@
Ez egy tesztértesítés a Gadgetbridge-től
A Bluetooth nem támogatott.
A Bluetooth ki van kapcsolva.
- Érintsd meg az eszközt az alkalmazáskezelőhöz
- Érintsd meg az eszközt az aktivitáshoz
- Érintsd meg az eszközt a rezgéshez.
- Érintsd meg az eszközt a csatlakozáshoz
+ Koppints az eszközre az alkalmazáskezelőhöz
+ Koppints az eszközre az Aktivitáshoz
+ Koppints az eszközre a rezgetéshez.
+ Koppints az eszközre a csatlakozáshoz
Nem lehet csatlakozni. Rossz Bluetooth cím?
A Gadgetbridge fut.
- bináris telepítés %1$d/%2$d
+ %1$d/%2$d bináris telepítése
sikertelen telepítés!
sikeres telepítés
FIRMWARE-T PRÓBÁLSZ TELEPÍTENI, CSAK SAJÁT FELELŐSSÉGRE TEDD!.\n\n\n Ez a firmware ehhez a hardware-verzióhoz tartozik: %s
- A következő alkalmazást fogod telepíteni:\n\n\n%1$s Verzió %2$s : %3$s\n
+ A következő alkalmazást fogod telepíteni:\n\n\n%1$s Verzió %2$s , készítette: %3$s\n
N/A
inicializált
- %1$s by %2$s
- Eszköz fellelhetőség
+ %1$s , készítette: %2$s
+ Eszköz keresés
Keresés leállítása
Keresés kezdése
Új eszköz csatlakoztatása
@@ -123,7 +135,11 @@
Eszköz párosítása
Használja az Android Bluetooth párosítás ablakot eszköz párosításához.
Párosítsd a Mi Band-ed
- Párosítás: %s…
+ %s párosítása...
+ Összeköttetés létesítése %1$s (%2$s) eszközzel
+ Sikertelen párosítás %1$s (%2$s) eszközzel
+ Összeköttetés folyamatban %1$s (%2$s) eszközzel
+ Meglévő párosítás %1$s (%2$s) eszközzel, kapcsolódás...
Nincs találat a MAC címre, nem lehet párosítani.
Eszközspecifikus beállítások
MI Band beállítások
@@ -135,7 +151,7 @@
Nem helyes felhasználói adatok vannak megadva, alapértelmezett adatokat fogok használni.
Amikor vibrálni kezd a Mi Band-ed, érintsd meg párszor egymás után.
Telepítés
- Engedélyezd a készüléked láthatóságát. A csatlakoztatott készülékek valószínűleg nem fognak megjelenni. Ha a készüléked nem jelenik meg 2 perc elteltével, próbáld újra, majd indítsd újra a telefonod.
+ Tedd felfedezhetővé az eszközöd. A jelenleg csatlakoztatott eszközök valóüszínűleg nem fognak megjelenni. 6-os és frissebb Androidon engedélyezni kell a helymeghatározást (GPS). Ha az eszköz két perc után sem jelenik meg, próbáld újra a mobileszközöd újraindítása után.
Megjegyzés:
Eszköz kép
Név/Alias
@@ -144,7 +160,7 @@
Naplófájlok írása
inicializálás
Aktivitási adatok lekérdezése.
- Ettől %1$s eddig %2$s
+ %1$s és %2$s között
Melyik kezeden hordod?
Rezgés profil
Szaggatott
@@ -161,6 +177,9 @@
Általános értesítések
E-Mail értesítés
Bejővő hívás értesítés
+ Csevegés
+ Navigáció
+ Közösségi háló
Elveszett eszköz keresése
A vibrálás leállításához: Mégse.
Aktivitásod
@@ -177,7 +196,7 @@
Okos ébresztés
Hiba történt az ébresztések beállításakor, kérlek próbáld újra!
Ébresztések beállítva!
- Nincs adat. Szinkronizáltál?
+ Nincs adat. Szinkronizáljunk?
Adatok fogadása: %1$s %2$s -tól
Napi céllépésszám
Hiba a futtatáskor: \'%1$s\'
@@ -212,10 +231,10 @@
Lépések
Aktuális aktivitás
Lépések ma, cél: %1$s
- Hagyja az aktivitási adatokat a készüléken
- Az aktivitás adatok a band-en maradnak. Hasznos, ha a GB-t más alkalamzásokkal együtt használod.
- Az adatokat a Mi Band-en fogja tárolni szinkronizálás után is. Hasznos, ha a GadgetBridge-et együtt használod más alkalmazásokkal.
- Használja a low-latency módot a Firmware frissítésnél
+ Ne erősítse meg az aktivitásadatok átvitelét
+ Ha az aktivitásadatok átvitele nincs megerősítve a band felé, az nem kerülnek törlésre. Hasznos, ha a GB-t más alkalmzásokkal együtt használod.
+ Az adatokat meghagyja a Mi Band-en szinkronizálás után is. Hasznos, ha a GadgetBridge-et együtt használod más alkalmazásokkal.
+ Használja a low-latency módot a firmware frissítésnél
Ez segíthet, ha a firmware frissítés sikertelen.
Lépésnapló
Jelenlegi lépés/perc
@@ -234,6 +253,11 @@
Naptári események számára fentartott riasztások száma
Pulzus szenzor használata az alvás érzékelés javításához
Eszköz időeltolása órákban (Hogy az éjszakai műszakban dolgozók alvását is érzékelje.)
+ Mi2: dátumformátum
+ Idő
+
+ Kijelző aktiválása felemeléskor
+ Adatok átvitele %1$s óta
várakozás az újracsatlakozásra
Rólad
Születési év
@@ -259,6 +283,7 @@
Régi aktivitásadatok importálása
A Gadgetbridge 0.12.0 verziója óta új adatbázis-formátumot használ. Lehetőség van a régi adatok importálására és azok párosítására a jelenleg csatlakozott eszközödhöz (%1$s) Kérlek, vedd figyelembe, hogy tudsz importálni a Mi Band, Pebble Health és Morpheuz eszközökből, de NEM tudsz a Pebble Misfit-ből.
Nyers adatok tárolása az adatbázisban
+ Ha bejelölöd, az adatok eredeti formában lesznek tárolva későbbi értelmezéshez. Megj.: az adatbázis ebben az esetben nagyobb lesz!
Adatbáziskezelés
Adatbáziskezelés
Az adatbázisművelet ezt a helyet fogja használni. Ez a hely elérhető másik Android-alkalmazások és a számítógép számára. Itt keresd az exportált adatbázist is, illetve ide rakd az importálni kívánt adatbázist is.
@@ -290,4 +315,7 @@
Törlés
Rezgés
+
+ Pebble párosítása
+ Egy párosítási párbeszédablaknak kéne megjelennie az Android eszközödön. Ha nem látod, nézd meg az értesítési sávban és fogadd el a párosítási kérelmet. Utána fogadd ela kérelmet a Pebble-n.
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 5c0b85b4..fe603a9c 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -126,7 +126,7 @@
Accoppia dispositivo
Utilizza la funzione del sistema operativo per accoppiare il dispositivo.
Accoppia la Mi Band
- Accoppiamento con %s…
+ Accoppiamento con %s…
Indirizzo MAC mancante, impossibile completare l\'accoppiamento.
Impostazioni specifiche dispositivo
Impostazioni Mi Band
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 34eee75a..21200fee 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -22,8 +22,11 @@
削除
キャッシュから削除
再インストール
+ Pebble Appstore で検索
アクティベート
非アクティベート
+ HRM をアクティベート
+ HRM を非アクティベート
設定
先頭に移動
@@ -85,12 +88,18 @@
場所の取得
緯度
経度
+ 場所の更新を継続する
+ 実行時に現在位置の取得を試みます。フォールバックとして保存されている場所を使用します
ネットワークの場所を有効にしてください
場所を取得しました
通知プロトコルを強制する
このオプションを指定すると、ファームウェアのバージョンに応じて強制的に最新の通知プロトコルを使用します。何をしているかわかっている場合のみ有効にしてください!
未テストの機能を有効にする
テストされていない機能を有効にします。何をしているかわかっている場合のみ有効にしてください!
+ 常に BLE を好みにする
+ すべてのPebbleに対して、BT クラシックではなく実験的なPebble LEサポートを使用します。非LEで一度接続された後に \"Pebble LE\" をペアリングする必要があります
+ ウォッチアプリのログ記録を有効にする
+ Gadgetbridgeがウォッチアプリからログを記録するようにする (再接続が必要です)
再接続の試行
非接続
接続中
@@ -126,7 +135,11 @@
デバイスのペアリング
デバイスをペアリングするには、AndroidのBluetoothペアリングのダイアログを使用します。
お使いのMi Bandをペアリング
- %sとペアリング…
+ %sとペアリング…
+ %1$s (%2$s) と結合を作成中
+ %1$s (%2$s) とペアにすることができません
+ 結合の進行中: %1$s (%2$s)
+ 既に %1$s (%2$s) と結合しています。接続中…
MACアドレスが渡されませんでした。ペアリングできません。
デバイス固有の設定
Mi Bandの設定
@@ -184,7 +197,7 @@
アラームの設定時にエラーが発生しました。もう一度試してください!
アラームをデバイスに送信しました!
データはありません。デバイスを同期しますか?
- %1$s のデータを %2$s から転送することについて
+ %1$s のデータを %2$s から転送します
毎日の目標ステップ
\'%1$s\' の実行エラー
あなたの活動 (アルファ版)
@@ -240,6 +253,11 @@
今後のイベントのために予約するアラーム
睡眠の検出を改善するために心拍センサーを使用する
デバイスの時刻オフセット時間 (交代勤務の睡眠検知用)
+ Mi2: 日付形式
+ 時間
+
+ 持ち上げ時に表示を有効にする
+ %1$s からのデータを転送します
再接続の待機中
あなたについて
誕生年
@@ -302,7 +320,7 @@ Mi Band、Pebble Health、Morpheuz からデータをインポートすること
削除
バイブレーション
- %1$s (%2$s) と結合を作成中
- 結合の進行中: %1$s (%2$s)
- 既に %1$s (%2$s) と結合しています。接続中…
+
+ Pebbleペアリング
+ お使いのAndroidデバイスでペアリングのダイアログがポップアップすると思います。 起こらない場合は、通知ドロワーを調べて、ペアリング要求を受け入れます。 その後、Pebbleでペアリング要求を受け入れます
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index ae5569dc..4c993ae8 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -96,7 +96,7 @@
기기 페어링
기기를 페어링하려면 안드로이드 블루투스 페어링 설정을 사용하세요
Mi Band 페어링
- %s에 페어링…
+ %s에 페어링…
MAC 주소가 통과되지 않았습니다. 페어링 할 수 없습니다.
기기 특정 설정
Mi Band 설정
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 82c019b6..885356aa 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -83,7 +83,7 @@
sparuj urządzenie
Użyj parowania bluetooth na androidzie by sparować urządzenie.
Sparuj swoje Mi Band
- Parowanie z %s…
+ Parowanie z %s…
Żaden mac nie przeszedł, nie można sparować.
Ustawienia danego urządzenia
Ustawienia Mi Band
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index f08d9e03..2041edd1 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -99,7 +99,7 @@
Сопряжение устройств
Для сопряжения устройств используйте диалог Android.
Сопряжение вашего Mi Band
- Сопряжение с %s…
+ Сопряжение с %s…
MAC-адрес не был передан, сопряжение не удалось.
Настройки устройства
Настройки Mi Band
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 58421a74..d67627cb 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -80,7 +80,7 @@
Cihaz ile eşleştir
Android Bluetooth Eşleme penceresini kullanarak cihaz ile eşleştiriniz.
Mi Band ile eşleştir
- %s ile eşleşiyor…
+ %s ile eşleşiyor…
MAC adresi gelmedi, eşleşemiyor.
Cihaz için özel ayarlar
Mi Band Ayarları
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index db7942b3..3779ce4a 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -105,7 +105,7 @@
Створення пари з пристроєм
Для створення пари із пристроєм використовуйте діалог Android.
Створення пари із вашим Mi—Band
- Створення пари із %s…
+ Створення пари із %s…
MAC-адресу не було передано, не вдалося створити пару.
Параметри пристрою
Параметри Mi—Band
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index cb9e6a06..5c7f9cb1 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -67,7 +67,7 @@
%1$s (%2$s)
Ghép đôi thiết bị
Ghép đôi Mi Band
- Đang ghép đôi với %s…
+ Đang ghép đôi với %s…
không thể kiểm tra địa chỉ mac, không thể ghép đôi.
Cài đặt cụ thể cho thiết bị
Cài đặt Mi Band
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 830f7002..e2aff8f5 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -113,5 +113,13 @@
- 3
- 1
+
+ - @string/dateformat_time
+ - @string/dateformat_date_time
+
+
+ - @string/p_dateformat_time
+ - @string/p_dateformat_datetime
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2f3a8ecc..58d39157 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -35,6 +35,8 @@
Search in Pebble Appstore
Activate
Deactivate
+ Activate HRM
+ Deactivate HRM
Configure
Move to top
@@ -113,6 +115,8 @@
Acquire Location
Latitude
Longitude
+ Keep location updated
+ Try to get the current location at runtime, use the stored location as fallback
Please enable network location
location acquired
@@ -121,6 +125,11 @@
This option forces using the latest notification protocol depending on the firmware version. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!
Enable untested features
Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!
+ Always prefer BLE
+ Use experimental Pebble LE support for all Pebbles instead of BT classic, requires paring a "Pebble LE" after non LE had been connected once
+ Enable Watch App Logging
+ Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect)
+
Reconnection Attempts
not connected
@@ -158,11 +167,11 @@
Pair Device
Use the Android Bluetooth Pairing dialog to pair the device.
Pair your Mi Band
- Pairing with %s…
- "Creating bond with %1$s (%2$s)"
- "Unable to pair with %1$s (%2$s)"
- Bonding in progress: %1$s (%2$s)
- "Already bonded with %1$s (%2$s), connecting…"
+ Pairing with %s…
+ "Creating bond with %1$s (%2$s)"
+ "Unable to pair with %1$s (%2$s)"
+ Bonding in progress: %1$s (%2$s)
+ "Already bonded with %1$s (%2$s), connecting…"
No mac address passed, cannot pair.
Device Specific Settings
Mi Band Settings
@@ -281,6 +290,11 @@
Alarms to reserve for upcoming events
Use Heartrate Sensor to improve sleep detection
Device time offset in hours (for detecting sleep of shift workers)
+ Mi2: Date Format
+ Time
+
+ Activate display upon lift
+ About to transfer data since %1$s
waiting for reconnect
@@ -350,4 +364,8 @@
Vibration
+
+
+ Pebble Pairing
+ A pairing dialog is expected to pop up on your Android device. If that does not happen, look in the notification drawer and accept the pairing request. After that accept the pairing request on your Pebble
diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml
index a89f911c..df90cb5b 100644
--- a/app/src/main/res/values/values.xml
+++ b/app/src/main/res/values/values.xml
@@ -9,4 +9,7 @@
- ring
- alarm_clock
+ - dateformat_time
+ - dateformat_datetime
+
diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml
index 7ca5092e..4f1ec60e 100644
--- a/app/src/main/res/xml/changelog_master.xml
+++ b/app/src/main/res/xml/changelog_master.xml
@@ -1,5 +1,52 @@
+
+ New device: Liveview
+ Liveview: initial support (set the time and receive notifications)
+ Pebble: log pebble app logs if option is enabled in pebble development settings
+ Pebble: notification icons for more apps
+ Pebble: Further improve compatibility for watchface configuration
+ Mi Band 2: Initial support for firmware update (tested so far: 1.0.0.39)
+
+
+ Pebble 2/LE: Fix multiple bugs in reconnection code, honor reconnect tries from settings
+ Mi Band 2: Experimental support for activity recognition
+ Mi Band 2: Fix time setting code
+
+
+ Pebble: Experimental support for pairing and using all Pebble models via BLE
+ Mi Band 1: Fix regression causing display of wrong activity data
+ Mi Band 2: Support for continuous heart rate measurements in live activity view
+
+
+ Pebble 2: Fix a bug where the Pebble got disconnected by other unrelated LE devices
+
+
+ Mi Band 2: Initial experimental support for activity data
+ Mi Band 2: Send the fitness goal (steps) to the band
+ Pebble 2: Work around firmware installation issues (tested with upgrading 4.2 to 4.3)
+ Pebble: Further improve compatibility for watchface configuration
+ Pebble: add Kickstart watch face to app manager on FW 4.x
+ Charts: display the total time range, not just the range with available data
+
+
+ Pebble 2: Initial experimental support for P2/PT2 using BLE
+ Pebble: Special support in device discovery activity (MUST be used to get Pebble 2 working)
+ Pebble: Improve compatibility for watchface configuration
+ Mi Band 2: support for heart rate measurement during sleep
+ Mi Band 2: configuration option to activate the display on lift
+ Mi Band 2: configuration option to display the time + date or just the time
+ Mi Band 2: honor the wear location configuration option
+
+
+ Pebble: use the last known location for setting sunrise and sunset
+ Pebble: fix Health disappearing forever when deactivating through app manager (and get it back for affected users)
+ Mi Band 2: More fixes for connection issues (#408)
+
+
+ Mi Band 2: fix connection issues for users of Mi Fit
+ Mi Band 1A: fix firmware update on certain 1A models
+
Pebble: Fix configuration of certain pebble apps (eg. QR Generator, Squared 4.0)
Pebble: Add context menu option in app manager to search a watchapp in the pebble appstore
@@ -25,7 +72,7 @@
Possibly fix logging to file on certain devices)
Mi Band 2: Possibly fix weird connection interdependency between Mi 1 and 2
Mi Band 1S: Whitelist firmware 4.16.4.22
- Mi Band: try application level pairing again, in ordert to support data sharing with Mi Fit
+ Mi Band: try application level pairing again, in order to support data sharing with Mi Fit
Pebble: new icons and colours for certain apps
Debug-screen: added button to test "new functionality", currently live sensor data for Mi Band 1
@@ -56,7 +103,7 @@
Pebble: fix activity data being associated with the wrong device and/or user in some cases causing them to invisible in charts
- Remove special handling for Conversations notfications since upstream dropped special pebble support
+ Remove special handling for Conversations notifications since upstream dropped special pebble support
NB: User action needed to migrate existing data!
@@ -73,13 +120,13 @@
Various fixes (including crashes) for location settings
Pebble: Support Pebble Time 2 emulator (needs recompilation of Gadgetbridge)
- Fix a rare crash when, due to bluetooth problems, when a device has no name
+ Fix a rare crash when, due to Bluetooth problems, when a device has no name
Fix activity fetching getting stuck when double tapping (#333)
Mi Band: in the Device Discovery activity, do not display devices that are already paired
Mi Band: only allow automatic reconnection on disconnect when the device was previously fully connected
- Mi Band: fix a rare crash when reading data fails due to bluetooth problems
+ Mi Band: fix a rare crash when reading data fails due to Bluetooth problems
Mi Band: log full activity sample to help deciphering activity kinds (#341)
- Mi Band 2: improved discovery mechanism to not rely on mac addresses (#323)
+ Mi Band 2: improved discovery mechanism to not rely on MAC addresses (#323)
Charts: only display heart rate samples on devices that support that
Add more logging to detect problems with external directories (#343)
@@ -92,13 +139,13 @@
Pebble: allow to manually paste configuration data for legacy configuration pages
Pebble: various improvements to the configuration page
- Pebble: Suppport FW 4.0-dp1 and Pebble2 emulator (needs recompilation of Gadgetbridge)
+ Pebble: Support FW 4.0-dp1 and Pebble2 emulator (needs recompilation of Gadgetbridge)
Pebble: Fix a problem with key events when using the Pebble music player
Pebble: set extended music info by dissecting notifications on Android 5.0+
- Pebble: various other improvemnts to music playback
- Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble)
+ Pebble: various other improvements to music playback
+ Pebble: allow ignoring activity trackers individually (to keep the data on the pebble)
Mi Band: support for shifting the device time by N hours (for people who sleep at daytime)
Mi Band: initial and untested support for Mi Band 2
Allow setting the application language
@@ -107,12 +154,12 @@
Pebble: option to send sunrise and sunset events to timeline
Pebble: fix problems with unknown app keys while configuring watchfaces
Mi Band: BLE connection fixes
- Fixes for enabling logging at whithout restarting Gadgetbridge
+ Fixes for enabling logging at without restarting Gadgetbridge
Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)
Display device address in device info
- Pebble: fix more reconnnect issues
+ Pebble: fix more reconnect issues
Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health
Pebble: option in AppManager to delete files from cache
Pebble: enable pbw cache and watchface configuration for Firmware 2.x
@@ -124,7 +171,7 @@
Pebble: hopefully fix some reconnect issues
Mi Band: fix live activity monitoring running forever if back button pressed
Mi Band: allow low latency firmware updates, fixes update with some phones
- Mi Band: inital experimental and probably broken support for Amazfit
+ Mi Band: initial experimental and probably broken support for Amazfit
Show aliases for BT Devices if they had been renamed in BT Settings
Do not show a hint about App Manager when a Mi Band is connected
diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml
index faf1d732..6b0e1032 100644
--- a/app/src/main/res/xml/miband_preferences.xml
+++ b/app/src/main/res/xml/miband_preferences.xml
@@ -43,6 +43,20 @@
android:maxLength="2"
android:title="@string/miband_prefs_device_time_offset_hours" />
+
+
+
+
+
+
@@ -330,6 +336,16 @@
android:key="pebble_force_untested"
android:summary="@string/pref_summary_pebble_forceuntested"
android:title="@string/pref_title_pebble_forceuntested" />
+
+