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 6fc1ce24..3c4561c4 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
@@ -207,6 +207,8 @@ public class MiBand2Service {
public static final byte SUCCESS = 0x01;
public static final byte COMMAND_ACTIVITY_DATA_START_DATE = 0x01;
+ public static final byte COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY = 0x01;
+ public static final byte COMMAND_ACTIVITY_DATA_TYPE_UNKNOWN_2 = 0x02;
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
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java
new file mode 100644
index 00000000..548f3670
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java
@@ -0,0 +1,193 @@
+/* Copyright (C) 2016-2017 Carsten Pfeiffer
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.R;
+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.devices.miband2.AbstractMiBand2Operation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
+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 abstract class AbstractFetchOperation extends AbstractMiBand2Operation {
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractFetchOperation.class);
+
+ protected byte lastPacketCounter;
+ protected int fetchCount;
+ protected BluetoothGattCharacteristic characteristicActivityData;
+ protected BluetoothGattCharacteristic characteristicFetch;
+ protected Calendar startTimestamp;
+
+ public AbstractFetchOperation(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 {
+ startFetching();
+ }
+
+ protected void startFetching() throws IOException {
+ lastPacketCounter = -1;
+
+ TransactionBuilder builder = performInitialized("fetching activity data");
+ getSupport().setLowLatency(builder);
+ if (fetchCount == 0) {
+ builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
+ }
+ fetchCount++;
+
+ characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
+ builder.notify(characteristicActivityData, false);
+
+ characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
+ builder.notify(characteristicFetch, true);
+
+ startFetching(builder);
+ builder.queue(getQueue());
+ }
+
+ protected abstract void startFetching(TransactionBuilder builder);
+
+ protected abstract String getLastSyncTimeKey();
+
+ @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);
+ }
+ }
+
+ @CallSuper
+ protected void handleActivityFetchFinish() {
+ operationFinished();
+ unsetBusy();
+ }
+
+ /**
+ * 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
+ */
+ protected abstract void handleActivityNotif(byte[] value);
+
+ /**
+ * Creates samples from the given 17-length array
+ * @param value
+ */
+ protected abstract void bufferActivityData(byte[] value);
+
+ protected 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();
+ }
+ } else {
+ LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
+ handleActivityFetchFinish();
+ }
+ }
+
+ protected void setStartTimestamp(Calendar startTimestamp) {
+ this.startTimestamp = startTimestamp;
+ }
+
+ protected void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
+ SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
+ editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
+ editor.apply();
+ }
+
+
+ protected GregorianCalendar getLastSuccessfulSyncTime() {
+ long timeStampMillis = GBApplication.getPrefs().getLong(getLastSyncTimeKey(), 0);
+ if (timeStampMillis != 0) {
+ GregorianCalendar calendar = BLETypeConversions.createCalendar();
+ calendar.setTimeInMillis(timeStampMillis);
+ return calendar;
+ }
+ GregorianCalendar calendar = BLETypeConversions.createCalendar();
+ calendar.add(Calendar.DAY_OF_MONTH, -10);
+ return calendar;
+ }
+}
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
index 927af487..36999ace 100644
--- 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
@@ -16,11 +16,6 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.content.SharedPreferences;
-import android.support.annotation.NonNull;
-import android.support.v4.util.TimeUtils;
import android.text.format.DateUtils;
import android.widget.Toast;
@@ -28,18 +23,13 @@ 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;
@@ -51,11 +41,8 @@ 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;
@@ -63,95 +50,31 @@ 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 {
+public class FetchActivityOperation extends AbstractFetchOperation {
private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class);
private List samples = new ArrayList<>(60*24); // 1day per default
- private byte lastPacketCounter;
- private Calendar startTimestamp;
- private int fetchCount;
-
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);
- }
+ protected void startFetching() throws IOException {
+ samples.clear();
+ super.startFetching();
}
@Override
- protected void doPerform() throws IOException {
- startFetching();
- }
-
- private void startFetching() throws IOException {
- samples.clear();
- lastPacketCounter = -1;
-
- TransactionBuilder builder = performInitialized("fetching activity data");
- getSupport().setLowLatency(builder);
- if (fetchCount == 0) {
- builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
- }
- fetchCount++;
-
- BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
- builder.notify(characteristicActivityData, false);
-
- BluetoothGattCharacteristic characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
- builder.notify(characteristicFetch, true);
-
+ protected void startFetching(TransactionBuilder builder) {
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
- builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, 0x01 }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
+ builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, MiBand2Service.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY }, 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 getLastSuccessfulSyncTime() {
- long timeStampMillis = GBApplication.getPrefs().getLong(getLastSyncTimeKey(), 0);
- if (timeStampMillis != 0) {
- GregorianCalendar calendar = BLETypeConversions.createCalendar();
- calendar.setTimeInMillis(timeStampMillis);
- return calendar;
- }
- GregorianCalendar calendar = BLETypeConversions.createCalendar();
- calendar.add(Calendar.DAY_OF_MONTH, -10);
- return calendar;
- }
-
- private void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) {
- SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
- editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis());
- editor.apply();
- }
-
- private String getLastSyncTimeKey() {
- return getDevice().getAddress() + "_" + "lastSyncTimeMillis";
- }
-
- @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() {
+ protected void handleActivityFetchFinish() {
LOG.info("Fetching activity data has finished round " + fetchCount);
GregorianCalendar lastSyncTimestamp = saveSamples();
if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
@@ -163,8 +86,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
}
}
- operationFinished();
- unsetBusy();
+ super.handleActivityFetchFinish();
}
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
@@ -232,7 +154,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
*
* @param value
*/
- private void handleActivityNotif(byte[] value) {
+ protected void handleActivityNotif(byte[] value) {
if (!isOperationRunning()) {
LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
getSupport().logMessageContent(value);
@@ -257,7 +179,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
* Creates samples from the given 17-length array
* @param value
*/
- private void bufferActivityData(byte[] value) {
+ protected void bufferActivityData(byte[] value) {
int len = value.length;
if (len % 4 != 1) {
@@ -280,34 +202,8 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
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;
+ @Override
+ protected String getLastSyncTimeKey() {
+ return getDevice().getAddress() + "_" + "lastSyncTimeMillis";
}
}