diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java index 74fc8c4de..de314737c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java @@ -229,8 +229,6 @@ public class HuamiService { public static final byte COMMAND_FIRMWARE_CHECKSUM = 0x04; // to UUID_CHARACTERISTIC_FIRMWARE public static final byte COMMAND_FIRMWARE_REBOOT = 0x05; // to UUID_CHARACTERISTIC_FIRMWARE - 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}; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index b337e2703..9fb7eec42 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -344,7 +344,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements protected Huami2021ChunkedEncoder huami2021ChunkedEncoder; protected Huami2021ChunkedDecoder huami2021ChunkedDecoder; - private final Queue fetchOperationQueue = new LinkedList<>(); + private final LinkedList fetchOperationQueue = new LinkedList<>(); public HuamiSupport() { this(LOG); @@ -1725,6 +1725,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements return fetchOperationQueue.poll(); } + public LinkedList getFetchOperationQueue() { + return fetchOperationQueue; + } + @Override public void onEnableRealtimeSteps(boolean enable) { try { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java index 07f399669..5b180b1a6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractFetchOperation.java @@ -28,6 +28,7 @@ import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.text.DateFormat; import java.util.Arrays; @@ -42,39 +43,41 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; -import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbstractGattListenerWriteAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiOperation; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; -import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport; +import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; /** - * 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. + * An operation that fetches activity data. */ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { private static final Logger LOG = LoggerFactory.getLogger(AbstractFetchOperation.class); - protected byte lastPacketCounter; - int fetchCount; protected BluetoothGattCharacteristic characteristicActivityData; protected BluetoothGattCharacteristic characteristicFetch; - Calendar startTimestamp; - int expectedDataLength = 0; - public AbstractFetchOperation(HuamiSupport support) { + protected Calendar startTimestamp; + + protected int fetchCount; + protected byte lastPacketCounter; + protected int expectedDataLength = 0; + protected final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024); + + protected boolean operationValid = true; // to mark operation failed midway (eg. out of sync) + + public AbstractFetchOperation(final HuamiSupport support) { super(support); } @Override - protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) { + protected void enableNeededNotifications(final TransactionBuilder builder, final boolean enable) { if (!enable) { // dynamically enabled, but always disabled on finish - builder.notify(characteristicFetch, enable); - builder.notify(characteristicActivityData, enable); + builder.notify(characteristicFetch, false); + builder.notify(characteristicActivityData, false); } } @@ -87,7 +90,7 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { expectedDataLength = 0; lastPacketCounter = -1; - TransactionBuilder builder = performInitialized(getName()); + final TransactionBuilder builder = performInitialized(getName()); if (fetchCount == 0) { builder.add(new SetDeviceBusyAction(getDevice(), taskDescription(), getContext())); } @@ -114,11 +117,11 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { protected abstract String getLastSyncTimeKey(); @Override - public boolean onCharacteristicChanged(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic) { - UUID characteristicUUID = characteristic.getUuid(); + public boolean onCharacteristicChanged(final BluetoothGatt gatt, + final BluetoothGattCharacteristic characteristic) { + final UUID characteristicUUID = characteristic.getUuid(); if (HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) { - handleActivityNotif(characteristic.getValue()); + handleActivityData(characteristic.getValue()); return true; } else if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) { handleActivityMetadata(characteristic.getValue()); @@ -129,18 +132,15 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { } /** - * Handles the finishing of fetching the activity. - * @param success whether fetching was successful - * @return whether handling the activity fetch finish was successful + * Handles the finishing of fetching the activity. This signals the actual end of this operation. */ - @CallSuper - protected boolean handleActivityFetchFinish(boolean success) { + protected final void onOperationFinished() { final AbstractFetchOperation nextFetchOperation = getSupport().getNextFetchOperation(); if (nextFetchOperation != null) { LOG.debug("Performing next operation {}", nextFetchOperation.getName()); try { nextFetchOperation.perform(); - return true; + return; } catch (final IOException e) { GB.toast( getContext(), @@ -148,17 +148,16 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { Toast.LENGTH_SHORT, GB.ERROR, e ); - - return false; + return; } } LOG.debug("All operations finished"); GB.updateTransferNotification(null, "", false, 100, getContext()); + GB.signalActivityDataFinish(); operationFinished(); unsetBusy(); - return true; } /** @@ -168,72 +167,54 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { * @param crc32 the expected checksum * @return whether the checksum was valid */ - protected abstract boolean validChecksum(int crc32); + protected boolean validChecksum(int crc32) { + return crc32 == CheckSums.getCRC32(buffer.toByteArray()); + } - /** - * 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); + protected abstract boolean processBufferedData(); - protected abstract void bufferActivityData(byte[] value); + protected void handleActivityData(final byte[] value) { + LOG.debug("{} data: {}", getName(), Logging.formatBytes(value)); - protected void startFetching(TransactionBuilder builder, byte fetchType, GregorianCalendar sinceWhen) { - final String taskName = StringUtils.ensureNotNull(builder.getTaskName()); + if (!isOperationRunning()) { + LOG.error("ignoring {} notification because operation is not running. Data length: {}", getName(), value.length); + getSupport().logMessageContent(value); + return; + } + + if ((byte) (lastPacketCounter + 1) == value[0]) { + // TODO we should handle skipped or repeated bytes more gracefully + lastPacketCounter++; + bufferActivityData(value); + } else { + GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR); + operationValid = false; + } + } + + protected void bufferActivityData(byte[] value) { + buffer.write(value, 1, value.length - 1); // skip the counter + } + + protected void startFetching(final TransactionBuilder builder, final byte fetchType, final GregorianCalendar sinceWhen) { final HuamiSupport support = getSupport(); - final boolean isZeppOs = support instanceof ZeppOsSupport; byte[] fetchBytes = BLETypeConversions.join(new byte[]{ HuamiService.COMMAND_ACTIVITY_DATA_START_DATE, fetchType}, support.getTimeBytes(sinceWhen, support.getFetchOperationsTimeUnit())); - builder.add(new AbstractGattListenerWriteAction(getQueue(), characteristicFetch, fetchBytes) { - @Override - protected boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - UUID characteristicUUID = characteristic.getUuid(); - if (HuamiService.UUID_UNKNOWN_CHARACTERISTIC4.equals(characteristicUUID)) { - byte[] value = characteristic.getValue(); - - if (ArrayUtils.equals(value, HuamiService.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) { - handleActivityMetadata(value); - if (expectedDataLength == 0 && isZeppOs) { - // Nothing to receive, if we try to fetch data it will fail - sendAckZeppOs(true); - } else if (expectedDataLength != 0) { - TransactionBuilder newBuilder = createTransactionBuilder(taskName + " Step 2"); - newBuilder.notify(characteristicActivityData, true); - newBuilder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA}); - try { - performImmediately(newBuilder); - } catch (IOException ex) { - GB.toast(getContext(), "Error fetching debug logs: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); - } - } - return true; - } else { - handleActivityMetadata(value); - } - } - return false; - } - }); + builder.write(characteristicFetch, fetchBytes); } private void handleActivityMetadata(byte[] value) { if (value.length < 3) { LOG.warn("Activity metadata too short: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); return; } if (value[0] != HuamiService.RESPONSE) { LOG.warn("Activity metadata not a response: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); return; } @@ -245,26 +226,26 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { handleFetchDataResponse(value); return; case HuamiService.COMMAND_ACK_ACTIVITY_DATA: - // ignore, this is just the reply to the COMMAND_ACK_ACTIVITY_DATA LOG.info("Got reply to COMMAND_ACK_ACTIVITY_DATA"); + onOperationFinished(); return; default: LOG.warn("Unexpected activity metadata: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); } } private void handleStartDateResponse(final byte[] value) { if (value[2] != HuamiService.SUCCESS) { LOG.warn("Start date unsuccessful response: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); return; } // it's 16 on the MB7, with a 0 at the end if (value.length != 15 && (value.length != 16 && value[15] != 0x00)) { LOG.warn("Start date response length: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); return; } @@ -277,7 +258,8 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { if (expectedDataLength == 0) { LOG.info("No data to fetch since {}", startTimestamp.getTime()); - handleActivityFetchFinish(true); + sendAck(true); + // do not finish the operation - do it in the ack response return; } @@ -287,85 +269,108 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation { GB.updateTransferNotification(taskDescription(), getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since, DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), true, 0, getContext()); + + // Trigger the actual data fetch + final TransactionBuilder step2builder = createTransactionBuilder(getName() + " Step 2"); + step2builder.notify(characteristicActivityData, true); + step2builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA}); + try { + performImmediately(step2builder); + } catch (final IOException e) { + GB.toast(getContext(), "Error starting fetch step 2: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); + onOperationFinished(); + } } private void handleFetchDataResponse(final byte[] value) { if (value[2] != HuamiService.SUCCESS) { LOG.warn("Fetch data unsuccessful response: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); return; } if (value.length != 3 && value.length != 7) { LOG.warn("Fetch data unexpected metadata length: {}", Logging.formatBytes(value)); - handleActivityFetchFinish(false); + onOperationFinished(); return; } if (value.length == 7 && !validChecksum(BLETypeConversions.toUint32(value, 3))) { LOG.warn("Data checksum invalid"); - handleActivityFetchFinish(false); - sendAckZeppOs(true); + // If we're on Zepp OS, ack but keep data on device + if (isZeppOs()) { + sendAck(true); + // do not finish the operation - do it in the ack response + return; + } + onOperationFinished(); return; } - boolean handleFinishSuccess; - try { - handleFinishSuccess = handleActivityFetchFinish(true); - } catch (final Exception e) { - LOG.warn("Failed to handle activity fetch finish", e); - handleFinishSuccess = false; + final boolean success = operationValid && processBufferedData(); + + final boolean keepActivityDataOnDevice = !success || HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress()); + if (isZeppOs() || !keepActivityDataOnDevice) { + sendAck(keepActivityDataOnDevice); + // do not finish the operation - do it in the ack response + return; } - final boolean keepActivityDataOnDevice = HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress()); - - sendAckZeppOs(keepActivityDataOnDevice || !handleFinishSuccess); + onOperationFinished(); } - protected void sendAckZeppOs(final boolean keepDataOnDevice) { - if (!(getSupport() instanceof ZeppOsSupport)) { - return; + protected void sendAck(final boolean keepDataOnDevice) { + final byte[] ackBytes; + + if (isZeppOs()) { + LOG.debug("Sending ack, keepDataOnDevice = {}", keepDataOnDevice); + + // 0x01 to ACK, mark as saved on phone (drop from band) + // 0x09 to ACK, but keep it marked as not saved + // If 0x01 is sent, detailed information seems to be discarded, and is not sent again anymore + final byte ackByte = (byte) (keepDataOnDevice ? 0x09 : 0x01); + ackBytes = new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA, ackByte}; + } else { + LOG.debug("Sending ack, simple"); + ackBytes = new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA}; } - LOG.debug("Sending Zepp OS ack, keepDataOnDevice = {}", keepDataOnDevice); - - // 0x01 to ACK, mark as saved on phone (drop from band) - // 0x09 to ACK, but keep it marked as not saved - // If 0x01 is sent, detailed information seems to be discarded, and is not sent again anymore - final byte ackByte = (byte) (keepDataOnDevice ? 0x09 : 0x01); - try { - final TransactionBuilder builder = performInitialized(getName() + " end"); - builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_ACK_ACTIVITY_DATA, ackByte}); + final TransactionBuilder builder = createTransactionBuilder(getName() + " end"); + builder.write(characteristicFetch, ackBytes); performImmediately(builder); } catch (final IOException e) { - LOG.error("Ending failed", e); + LOG.error("Failed to send ack", e); } } - private void setStartTimestamp(Calendar startTimestamp) { + private void setStartTimestamp(final Calendar startTimestamp) { this.startTimestamp = startTimestamp; } - Calendar getLastStartTimestamp() { + protected Calendar getLastStartTimestamp() { return startTimestamp; } - void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) { - SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit(); + protected void saveLastSyncTimestamp(@NonNull final GregorianCalendar timestamp) { + final SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).edit(); editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis()); editor.apply(); } protected GregorianCalendar getLastSuccessfulSyncTime() { - long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0); + final long timeStampMillis = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getLong(getLastSyncTimeKey(), 0); if (timeStampMillis != 0) { GregorianCalendar calendar = BLETypeConversions.createCalendar(); calendar.setTimeInMillis(timeStampMillis); return calendar; } - GregorianCalendar calendar = BLETypeConversions.createCalendar(); + final GregorianCalendar calendar = BLETypeConversions.createCalendar(); calendar.add(Calendar.DAY_OF_MONTH, -100); return calendar; } + + protected boolean isZeppOs() { + return getSupport() instanceof ZeppOsSupport; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java index 509804341..9b3c4bc03 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/AbstractRepeatingFetchOperation.java @@ -16,30 +16,20 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCharacteristic; -import android.widget.Toast; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; -import nodomain.freeyourgadget.gadgetbridge.Logging; -import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; -import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; -import nodomain.freeyourgadget.gadgetbridge.util.GB; /** * A repeating fetch operation. This operation repeats the fetch up to a certain number of times, or @@ -49,8 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOperation { private static final Logger LOG = LoggerFactory.getLogger(AbstractRepeatingFetchOperation.class); - private final ByteArrayOutputStream byteStreamBuffer = new ByteArrayOutputStream(140); - protected final HuamiFetchDataType dataType; public AbstractRepeatingFetchOperation(final HuamiSupport support, final HuamiFetchDataType dataType) { @@ -77,21 +65,14 @@ public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOpera protected abstract boolean handleActivityData(GregorianCalendar timestamp, byte[] bytes); @Override - protected boolean handleActivityFetchFinish(final boolean success) { - LOG.info("{} has finished round {}: {}, got {} bytes in buffer", getName(), fetchCount, success, byteStreamBuffer.size()); + protected boolean processBufferedData() { + LOG.info("{} has finished round {}, got {} bytes in buffer", getName(), fetchCount, buffer.size()); - if (!success) { - // We need to explicitly ack this, or the next operation will fail fetch will become stuck - sendAckZeppOs(true); - super.handleActivityFetchFinish(false); - return false; + if (buffer.size() == 0) { + return true; } - if (byteStreamBuffer.size() == 0) { - return super.handleActivityFetchFinish(true); - } - - final byte[] bytes = byteStreamBuffer.toByteArray(); + final byte[] bytes = buffer.toByteArray(); final GregorianCalendar timestamp = (GregorianCalendar) this.startTimestamp.clone(); // Uncomment to dump the bytes to external storage for debugging @@ -100,7 +81,6 @@ public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOpera final boolean handleSuccess = handleActivityData(timestamp, bytes); if (!handleSuccess) { - super.handleActivityFetchFinish(false); return false; } @@ -108,63 +88,12 @@ public abstract class AbstractRepeatingFetchOperation extends AbstractFetchOpera saveLastSyncTimestamp(timestamp); if (needsAnotherFetch(timestamp)) { - byteStreamBuffer.reset(); + buffer.reset(); - try { - final boolean keepActivityDataOnDevice = HuamiCoordinator.getKeepActivityDataOnDevice(getDevice().getAddress()); - sendAckZeppOs(keepActivityDataOnDevice); - startFetching(); - return true; - } catch (final IOException ex) { - LOG.error("Error starting another round of " + getName(), ex); - super.handleActivityFetchFinish(false); - return false; - } + getSupport().getFetchOperationQueue().add(0, this); } - final boolean superSuccess = super.handleActivityFetchFinish(true); - postActivityFetchFinish(superSuccess); - return superSuccess; - } - - protected void postActivityFetchFinish(final boolean success) { - - } - - @Override - protected boolean validChecksum(final int crc32) { - return crc32 == CheckSums.getCRC32(byteStreamBuffer.toByteArray()); - } - - @Override - public boolean onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { - LOG.debug("characteristic read: {}: {}", characteristic.getUuid(), Logging.formatBytes(characteristic.getValue())); - return super.onCharacteristicRead(gatt, characteristic, status); - } - - @Override - protected void handleActivityNotif(final byte[] value) { - LOG.debug("{} data: {}", getName(), Logging.formatBytes(value)); - - if (!isOperationRunning()) { - LOG.error("ignoring {} notification because operation is not running. Data length: {}", getName(), value.length); - getSupport().logMessageContent(value); - return; - } - - if ((byte) (lastPacketCounter + 1) == value[0]) { - // TODO we should handle skipped or repeated bytes more gracefully - lastPacketCounter++; - bufferActivityData(value); - } else { - GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR); - handleActivityFetchFinish(false); - } - } - - @Override - protected void bufferActivityData(final byte[] value) { - byteStreamBuffer.write(value, 1, value.length - 1); // skip the counter + return true; } private boolean needsAnotherFetch(final GregorianCalendar lastSyncTimestamp) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java index 596fb4877..4c2bc4bcc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchActivityOperation.java @@ -129,11 +129,6 @@ public class FetchActivityOperation extends AbstractRepeatingFetchOperation { } } - @Override - protected void postActivityFetchFinish(final boolean success) { - GB.signalActivityDataFinish(); - } - @Override protected boolean validChecksum(final int crc32) { // TODO actually check it diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java index ad35b1da6..c03e49fa6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchDebugLogsOperation.java @@ -37,7 +37,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; -import nodomain.freeyourgadget.gadgetbridge.util.GB; public class FetchDebugLogsOperation extends AbstractFetchOperation { private static final Logger LOG = LoggerFactory.getLogger(FetchDebugLogsOperation.class); @@ -81,11 +80,11 @@ public class FetchDebugLogsOperation extends AbstractFetchOperation { @Override protected String getLastSyncTimeKey() { - return null; + return "lastDebugTimeMillis"; } @Override - protected boolean handleActivityFetchFinish(boolean success) { + protected boolean processBufferedData() { LOG.info("{} data has finished", getName()); try { logOutputStream.close(); @@ -95,7 +94,7 @@ public class FetchDebugLogsOperation extends AbstractFetchOperation { return false; } - return super.handleActivityFetchFinish(success); + return true; } @Override @@ -105,30 +104,13 @@ public class FetchDebugLogsOperation extends AbstractFetchOperation { return true; } - @Override - protected void handleActivityNotif(byte[] value) { - if (!isOperationRunning()) { - LOG.error("ignoring notification because operation is not running. Data length: " + value.length); - getSupport().logMessageContent(value); - return; - } - - if ((byte) (lastPacketCounter + 1) == value[0]) { - lastPacketCounter++; - bufferActivityData(value); - } else { - GB.toast("Error " + getName() + " invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR); - handleActivityFetchFinish(false); - } - } - @Override protected void bufferActivityData(@NonNull byte[] value) { try { logOutputStream.write(value, 1, value.length - 1); } catch (final IOException e) { LOG.warn("could not write to output stream", e); - handleActivityFetchFinish(false); + operationValid = false; } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java index c813c4b97..0f23a3508 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsDetailsOperation.java @@ -23,7 +23,6 @@ import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -32,7 +31,6 @@ import java.util.GregorianCalendar; import androidx.annotation.NonNull; 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.entities.BaseActivitySummary; @@ -45,7 +43,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser; -import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -60,8 +57,6 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation { private final BaseActivitySummary summary; private final String lastSyncTimeKey; - private ByteArrayOutputStream buffer; - FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary, @NonNull AbstractHuamiActivityDetailsParser detailsParser, @NonNull HuamiSupport support, @@ -83,93 +78,88 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation { @Override protected void startFetching(TransactionBuilder builder) { LOG.info("start " + getName()); - buffer = new ByteArrayOutputStream(1024); - GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); + final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); startFetching(builder, HuamiFetchDataType.SPORTS_DETAILS.getCode(), sinceWhen); } @Override - protected boolean handleActivityFetchFinish(boolean success) { - LOG.info(getName() + " has finished round " + fetchCount); + protected boolean processBufferedData() { + LOG.info("{} has finished round {}", getName(), fetchCount); - boolean parseSuccess = true; + if (buffer.size() == 0) { + LOG.warn("Buffer is empty"); + return false; + } - if (success && buffer.size() > 0) { - if (detailsParser instanceof HuamiActivityDetailsParser) { - ((HuamiActivityDetailsParser) detailsParser).setSkipCounterByte(false); // is already stripped + if (detailsParser instanceof HuamiActivityDetailsParser) { + ((HuamiActivityDetailsParser) detailsParser).setSkipCounterByte(false); // is already stripped + } + + try { + final ActivityTrack track = detailsParser.parse(buffer.toByteArray()); + final ActivityTrackExporter exporter = createExporter(); + final String trackType; + switch (summary.getActivityKind()) { + case ActivityKind.TYPE_CYCLING: + trackType = getContext().getString(R.string.activity_type_biking); + break; + case ActivityKind.TYPE_RUNNING: + trackType = getContext().getString(R.string.activity_type_running); + break; + case ActivityKind.TYPE_WALKING: + trackType = getContext().getString(R.string.activity_type_walking); + break; + case ActivityKind.TYPE_HIKING: + trackType = getContext().getString(R.string.activity_type_hiking); + break; + case ActivityKind.TYPE_CLIMBING: + trackType = getContext().getString(R.string.activity_type_climbing); + break; + case ActivityKind.TYPE_SWIMMING: + trackType = getContext().getString(R.string.activity_type_swimming); + break; + default: + trackType = "track"; + break; } + + final String rawBytesPath = saveRawBytes(); + + final String fileName = FileUtils.makeValidFileName("gadgetbridge-" + trackType.toLowerCase() + "-" + DateTimeUtils.formatIso8601(summary.getStartTime()) + ".gpx"); + final File targetFile = new File(FileUtils.getExternalFilesDir(), fileName); + + boolean exportGpxSuccess = true; try { - ActivityTrack track = detailsParser.parse(buffer.toByteArray()); - ActivityTrackExporter exporter = createExporter(); - String trackType = "track"; - switch (summary.getActivityKind()) { - case ActivityKind.TYPE_CYCLING: - trackType = getContext().getString(R.string.activity_type_biking); - break; - case ActivityKind.TYPE_RUNNING: - trackType = getContext().getString(R.string.activity_type_running); - break; - case ActivityKind.TYPE_WALKING: - trackType = getContext().getString(R.string.activity_type_walking); - break; - case ActivityKind.TYPE_HIKING: - trackType = getContext().getString(R.string.activity_type_hiking); - break; - case ActivityKind.TYPE_CLIMBING: - trackType = getContext().getString(R.string.activity_type_climbing); - break; - case ActivityKind.TYPE_SWIMMING: - trackType = getContext().getString(R.string.activity_type_swimming); - break; - } - final String rawBytesPath = saveRawBytes(); - - String fileName = FileUtils.makeValidFileName("gadgetbridge-" + trackType.toLowerCase() + "-" + DateTimeUtils.formatIso8601(summary.getStartTime()) + ".gpx"); - File targetFile = new File(FileUtils.getExternalFilesDir(), fileName); - - boolean exportGpxSuccess = true; - try { - exporter.performExport(track, targetFile); - } catch (ActivityTrackExporter.GPXTrackEmptyException ex) { - exportGpxSuccess = false; - GB.toast(getContext(), "This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.ERROR, ex); - } - - try (DBHandler dbHandler = GBApplication.acquireDB()) { - if (exportGpxSuccess) { - summary.setGpxTrack(targetFile.getAbsolutePath()); - } - if (rawBytesPath != null) { - summary.setRawDetailsPath(rawBytesPath); - } - dbHandler.getDaoSession().getBaseActivitySummaryDao().update(summary); - } - } catch (Exception ex) { - GB.toast(getContext(), "Error getting activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); - parseSuccess = false; + exporter.performExport(track, targetFile); + } catch (final ActivityTrackExporter.GPXTrackEmptyException ex) { + exportGpxSuccess = false; } + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + if (exportGpxSuccess) { + summary.setGpxTrack(targetFile.getAbsolutePath()); + } + if (rawBytesPath != null) { + summary.setRawDetailsPath(rawBytesPath); + } + dbHandler.getDaoSession().getBaseActivitySummaryDao().update(summary); + } + } catch (final Exception e) { + GB.toast(getContext(), "Error saving activity details: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); + return false; } - final boolean superSuccess = super.handleActivityFetchFinish(success); + // Always increment the sync timestamp on success, even if we did not get data + final GregorianCalendar endTime = BLETypeConversions.createCalendar(); + endTime.setTime(summary.getEndTime()); + saveLastSyncTimestamp(endTime); - if (success && parseSuccess) { - // Always increment the sync timestamp on success, even if we did not get data - GregorianCalendar endTime = BLETypeConversions.createCalendar(); - endTime.setTime(summary.getEndTime()); - saveLastSyncTimestamp(endTime); - - if (needsAnotherFetch(endTime)) { - FetchSportsSummaryOperation nextOperation = new FetchSportsSummaryOperation(getSupport(), fetchCount); - try { - nextOperation.perform(); - } catch (IOException ex) { - LOG.error("Error starting another round of fetching activity data", ex); - } - } + if (needsAnotherFetch(endTime)) { + final FetchSportsSummaryOperation nextOperation = new FetchSportsSummaryOperation(getSupport(), fetchCount); + getSupport().getFetchOperationQueue().add(0, nextOperation); } - - return superSuccess && parseSuccess; + return true; } private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) { @@ -183,78 +173,30 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation { LOG.info("Hopefully no further fetch needed, last synced timestamp is from today."); return false; } + if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) { LOG.warn("Not doing another fetch since last synced timestamp is in the future: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime())); return false; } + LOG.info("Doing another fetch since last sync timestamp is still too old: {}", DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime())); return true; } - @Override - protected boolean validChecksum(int crc32) { - return crc32 == CheckSums.getCRC32(buffer.toByteArray()); - } - private ActivityTrackExporter createExporter() { - GPXExporter exporter = new GPXExporter(); + final GPXExporter exporter = new GPXExporter(); exporter.setCreator(GBApplication.app().getNameAndVersion()); return exporter; } - /** - * 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 - */ - @Override - protected void handleActivityNotif(byte[] value) { - LOG.warn("sports details: " + Logging.formatBytes(value)); - - if (!isOperationRunning()) { - LOG.error("ignoring sports details notification because operation is not running. Data length: " + value.length); - getSupport().logMessageContent(value); - return; - } - - if (value.length < 2) { - LOG.error("unexpected sports details data length: " + value.length); - getSupport().logMessageContent(value); - return; - } - - if ((byte) (lastPacketCounter + 1) == value[0]) { - lastPacketCounter++; - bufferActivityData(value); - } else { - GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR); - handleActivityFetchFinish(false); - } - } - - /** - * Buffers the given activity summary data. If the total size is reached, - * it is converted to an object and saved in the database. - * - * @param value - */ - @Override - protected void bufferActivityData(byte[] value) { - buffer.write(value, 1, value.length - 1); // skip the counter - } - @Override protected String getLastSyncTimeKey() { return lastSyncTimeKey; } + @Override protected GregorianCalendar getLastSuccessfulSyncTime() { - GregorianCalendar calendar = BLETypeConversions.createCalendar(); + final GregorianCalendar calendar = BLETypeConversions.createCalendar(); calendar.setTime(summary.getStartTime()); return calendar; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java index 22f69d422..445b266de 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSportsSummaryOperation.java @@ -17,19 +17,14 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCharacteristic; import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.GregorianCalendar; 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; @@ -43,7 +38,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuamiActivityDetailsParser; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; -import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.GB; /** @@ -53,7 +47,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class FetchSportsSummaryOperation extends AbstractFetchOperation { private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class); - private ByteArrayOutputStream buffer = new ByteArrayOutputStream(140); public FetchSportsSummaryOperation(HuamiSupport support, int fetchCount) { super(support); setName("fetching sport summaries"); @@ -68,126 +61,57 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation { @Override protected void startFetching(TransactionBuilder builder) { LOG.info("start" + getName()); - GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); + final GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); startFetching(builder, HuamiFetchDataType.SPORTS_SUMMARIES.getCode(), sinceWhen); } @Override - protected boolean handleActivityFetchFinish(boolean success) { - LOG.info(getName() + " has finished round " + fetchCount); + protected boolean processBufferedData() { + LOG.info("{} has finished round {}", getName(), fetchCount); + if (buffer.size() < 2) { + LOG.warn("Buffer size {} too small for activity summary", buffer.size()); + return false; + } - BaseActivitySummary summary = null; final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator(); final ActivitySummaryParser summaryParser = coordinator.getActivitySummaryParser(getDevice()); - boolean parseSummarySuccess = true; + BaseActivitySummary summary = new BaseActivitySummary(); + summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set + summary.setRawSummaryData(buffer.toByteArray()); + try { + summary = summaryParser.parseBinaryData(summary); + } catch (final Exception e) { + GB.toast(getContext(), "Failed to parse activity summary", Toast.LENGTH_LONG, GB.ERROR, e); + return false; + } - if (success && buffer.size() > 0) { - summary = new BaseActivitySummary(); - summary.setStartTime(getLastStartTimestamp().getTime()); // due to a bug this has to be set + if (summary == null) { + return false; + } + + summary.setSummaryData(null); // remove json before saving to database, + try (DBHandler dbHandler = GBApplication.acquireDB()) { + final DaoSession session = dbHandler.getDaoSession(); + final Device device = DBHelper.getDevice(getDevice(), session); + final User user = DBHelper.getUser(session); + summary.setDevice(device); + summary.setUser(user); summary.setRawSummaryData(buffer.toByteArray()); - try { - summary = summaryParser.parseBinaryData(summary); - } catch (final Exception e) { - GB.toast(getContext(), "Failed to parse activity summary", Toast.LENGTH_LONG, GB.ERROR, e); - summary = null; - parseSummarySuccess = false; - } - - if (summary != null) { - summary.setSummaryData(null); // remove json before saving to database, - try (DBHandler dbHandler = GBApplication.acquireDB()) { - DaoSession session = dbHandler.getDaoSession(); - Device device = DBHelper.getDevice(getDevice(), session); - User user = DBHelper.getUser(session); - summary.setDevice(device); - summary.setUser(user); - summary.setRawSummaryData(buffer.toByteArray()); - session.getBaseActivitySummaryDao().insertOrReplace(summary); - } catch (Exception ex) { - GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex); - parseSummarySuccess = false; - } - } + session.getBaseActivitySummaryDao().insertOrReplace(summary); + } catch (final Exception ex) { + GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex); + return false; } - final boolean superSuccess = super.handleActivityFetchFinish(success); - boolean getDetailsSuccess = true; + final AbstractHuamiActivityDetailsParser detailsParser = ((HuamiActivitySummaryParser) summaryParser).getDetailsParser(summary); + final FetchSportsDetailsOperation nextOperation = new FetchSportsDetailsOperation(summary, detailsParser, getSupport(), getLastSyncTimeKey(), fetchCount); + getSupport().getFetchOperationQueue().add(0, nextOperation); - if (summary != null) { - final AbstractHuamiActivityDetailsParser detailsParser = ((HuamiActivitySummaryParser) summaryParser).getDetailsParser(summary); - - FetchSportsDetailsOperation nextOperation = new FetchSportsDetailsOperation(summary, detailsParser, getSupport(), getLastSyncTimeKey(), fetchCount); - try { - nextOperation.perform(); - } catch (IOException ex) { - GB.toast(getContext(), "Unable to fetch activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); - getDetailsSuccess = false; - } - } - - return parseSummarySuccess && superSuccess && getDetailsSuccess; + return true; } - @Override - protected boolean validChecksum(int crc32) { - return crc32 == CheckSums.getCRC32(buffer.toByteArray()); - } - - @Override - public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - LOG.warn("characteristic read: " + characteristic.getUuid() + ": " + Logging.formatBytes(characteristic.getValue())); - return super.onCharacteristicRead(gatt, characteristic, status); - } - - /** - * 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 - */ - @Override - protected void handleActivityNotif(byte[] value) { - LOG.warn("sports summary data: " + Logging.formatBytes(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 < 2) { - LOG.error("unexpected sports summary data length: " + value.length); - getSupport().logMessageContent(value); - return; - } - - if ((byte) (lastPacketCounter + 1) == value[0]) { - lastPacketCounter++; - bufferActivityData(value); - } else { - GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR); - handleActivityFetchFinish(false); - } - } - - /** - * Buffers the given activity summary data. If the total size is reached, - * it is converted to an object and saved in the database. - * - * @param value - */ - @Override - protected void bufferActivityData(byte[] value) { - buffer.write(value, 1, value.length - 1); // skip the counter - } - - @Override protected String getLastSyncTimeKey() { return "lastSportsActivityTimeMillis"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java index cb9c5fa8b..9c8cfc46e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchStatisticsOperation.java @@ -19,12 +19,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fe import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; /** @@ -52,14 +50,6 @@ public class FetchStatisticsOperation extends AbstractRepeatingFetchOperation { return true; } - @Override - protected GregorianCalendar getLastSuccessfulSyncTime() { - // One minute ago - we don't really care about this data, and we don't want to fetch it all - final GregorianCalendar sinceWhen = BLETypeConversions.createCalendar(); - sinceWhen.add(Calendar.MINUTE, -1); - return sinceWhen; - } - @Override protected String getLastSyncTimeKey() { return "lastStatisticsTimeMillis";