1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 04:16:49 +01:00

Huawei: Workout GPS synchronization

This commit is contained in:
Martin.JM 2024-08-18 17:32:35 +02:00
parent 52798393a4
commit 159ebfd891
21 changed files with 696 additions and 211 deletions

View File

@ -46,7 +46,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(78, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(79, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -137,8 +137,8 @@ public class GBDaoGenerator {
addHuaweiActivitySample(schema, user, device);
Entity huaweiWorkoutSummary = addHuaweiWorkoutSummarySample(schema, user, device);
addHuaweiWorkoutDataSample(schema, user, device, huaweiWorkoutSummary);
addHuaweiWorkoutPaceSample(schema, user, device, huaweiWorkoutSummary);
addHuaweiWorkoutDataSample(schema, huaweiWorkoutSummary);
addHuaweiWorkoutPaceSample(schema, huaweiWorkoutSummary);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
@ -1307,10 +1307,12 @@ public class GBDaoGenerator {
workoutSummary.addByteArrayProperty("rawData");
workoutSummary.addStringProperty("gpxFileLocation");
return workoutSummary;
}
private static Entity addHuaweiWorkoutDataSample(Schema schema, Entity user, Entity device, Entity summaryEntity) {
private static Entity addHuaweiWorkoutDataSample(Schema schema, Entity summaryEntity) {
Entity workoutDataSample = addEntity(schema, "HuaweiWorkoutDataSample");
workoutDataSample.setJavaDoc("Contains Huawei Workout data samples (multiple per workout)");
@ -1345,7 +1347,7 @@ public class GBDaoGenerator {
return workoutDataSample;
}
private static Entity addHuaweiWorkoutPaceSample(Schema schema, Entity user, Entity device, Entity summaryEntity) {
private static Entity addHuaweiWorkoutPaceSample(Schema schema, Entity summaryEntity) {
Entity workoutPaceSample = addEntity(schema, "HuaweiWorkoutPaceSample");
workoutPaceSample.setJavaDoc("Contains Huawei Workout pace data samples (one per workout)");

View File

@ -0,0 +1,38 @@
/* Copyright (C) 2024 Martin.JM
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 <https://www.gnu.org/licenses/>. */
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.HuaweiWorkoutSummarySampleDao;
public class GadgetbridgeUpdate_79 implements DBUpdateScript {
@Override
public void upgradeSchema(final SQLiteDatabase db) {
if (!DBHelper.existsColumn(HuaweiWorkoutSummarySampleDao.TABLENAME, HuaweiWorkoutSummarySampleDao.Properties.GpxFileLocation.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiWorkoutSummarySampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiWorkoutSummarySampleDao.Properties.GpxFileLocation.columnName + "\" TEXT";
db.execSQL(statement);
}
}
@Override
public void downgradeSchema(final SQLiteDatabase db) {
}
}

View File

@ -26,9 +26,7 @@ import androidx.annotation.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
@ -38,13 +36,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
@ -87,21 +80,7 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
long deviceId = device.getId();
QueryBuilder<?> qb = session.getHuaweiActivitySampleDao().queryBuilder();
qb.where(HuaweiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<HuaweiWorkoutSummarySample> qb2 = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
List<HuaweiWorkoutSummarySample> workouts = qb2.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).build().list();
for (HuaweiWorkoutSummarySample sample : workouts) {
session.getHuaweiWorkoutDataSampleDao().queryBuilder().where(
HuaweiWorkoutDataSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
).buildDelete().executeDeleteWithoutDetachingEntities();
}
session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
session.getBaseActivitySummaryDao().queryBuilder().where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
huaweiCoordinator.deleteDevice(gbDevice, device, session);
}
@Override

View File

@ -29,7 +29,9 @@ import java.util.TreeMap;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
@ -40,11 +42,21 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
import androidx.annotation.NonNull;
public class HuaweiCoordinator {
Logger LOG = LoggerFactory.getLogger(HuaweiCoordinator.class);
@ -56,6 +68,7 @@ public class HuaweiCoordinator {
ByteBuffer notificationConstraints = null;
private boolean supportsTruSleepNewSync = false;
private boolean supportsGpsNewSync = false;
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
@ -92,6 +105,28 @@ public class HuaweiCoordinator {
}
}
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
long deviceId = device.getId();
QueryBuilder<?> qb = session.getHuaweiActivitySampleDao().queryBuilder();
qb.where(HuaweiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<HuaweiWorkoutSummarySample> qb2 = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
List<HuaweiWorkoutSummarySample> workouts = qb2.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).build().list();
for (HuaweiWorkoutSummarySample sample : workouts) {
session.getHuaweiWorkoutDataSampleDao().queryBuilder().where(
HuaweiWorkoutDataSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
).buildDelete().executeDeleteWithoutDetachingEntities();
session.getHuaweiWorkoutPaceSampleDao().queryBuilder().where(
HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
).buildDelete().executeDeleteWithoutDetachingEntities();
}
session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
session.getBaseActivitySummaryDao().queryBuilder().where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
}
private SharedPreferences getCapabilitiesSharedPreferences() {
return GBApplication.getContext().getSharedPreferences("huawei_coordinator_capatilities" + parent.getDeviceType().name(), Context.MODE_PRIVATE);
}
@ -694,4 +729,12 @@ public class HuaweiCoordinator {
public void setSupportsTruSleepNewSync(boolean supportsTruSleepNewSync) {
this.supportsTruSleepNewSync = supportsTruSleepNewSync;
}
public boolean isSupportsGpsNewSync() {
return supportsGpsNewSync;
}
public void setSupportsGpsNewSync(boolean supportsGpsNewSync) {
this.supportsGpsNewSync = supportsGpsNewSync;
}
}

View File

@ -0,0 +1,102 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
public class HuaweiGpsParser {
public static class GpsPoint {
public int timestamp;
public double latitude;
public double longitude;
public boolean altitudeSupported;
public double altitude;
@NonNull
@Override
public String toString() {
return "GpsPoint{" +
"timestamp=" + timestamp +
", longitude=" + longitude +
", latitude=" + latitude +
", altitudeSupported=" + altitudeSupported +
", altitude=" + altitude +
'}';
}
}
public static GpsPoint[] parseHuaweiGps(byte[] data) {
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.LITTLE_ENDIAN);
// Skip trim
buffer.position(0x20);
int timestamp;
double lon_start;
double lat_start;
boolean alt_support;
double alt_start;
byte fileType = buffer.get();
if ((fileType & 0x03) != 0x03) {
alt_support = false;
timestamp = buffer.getInt();
lon_start = buffer.getDouble();
lat_start = buffer.getDouble();
alt_start = 0;
buffer.position(62); // Skip past unknown fields/padding
} else {
alt_support = true;
timestamp = buffer.getInt();
lon_start = buffer.getDouble();
lat_start = buffer.getDouble();
alt_start = buffer.getDouble();
buffer.position(70); // Skip past unknown fields/padding
}
lat_start = lat_start * 0.017453292519943;
lon_start = lon_start * 0.017453292519943;
// Working values
int time = timestamp;
double lat = lat_start;
double lon = lon_start;
double alt = alt_start;
int data_size = 15;
if (alt_support)
data_size += 4;
ArrayList<GpsPoint> retv = new ArrayList<>(buffer.remaining() / data_size);
while (buffer.remaining() > data_size) {
short time_delta = buffer.getShort();
buffer.getShort(); // Unknown value
float lon_delta = buffer.getFloat();
float lat_delta = buffer.getFloat();
buffer.get(); buffer.get(); buffer.get(); // Unknown values
time = time + time_delta;
lat = lat + lat_delta;
lon = lon + lon_delta;
GpsPoint point = new GpsPoint();
point.timestamp = time;
point.latitude = (lat / 6383807.0d + lat_start) / 0.017453292519943d;
point.longitude = (lon / 6383807.0d / Math.cos(lat_start) + lon_start) / 0.017453292519943d;
point.altitudeSupported = alt_support;
if (alt_support) {
// TODO: not sure about this
float alt_delta = buffer.getFloat();
alt = alt + alt_delta;
point.altitude = alt;
}
retv.add(point);
}
return retv.toArray(new GpsPoint[0]);
}
}

View File

@ -27,9 +27,7 @@ import androidx.annotation.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
@ -39,13 +37,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
@ -96,21 +89,7 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
long deviceId = device.getId();
QueryBuilder<?> qb = session.getHuaweiActivitySampleDao().queryBuilder();
qb.where(HuaweiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<HuaweiWorkoutSummarySample> qb2 = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
List<HuaweiWorkoutSummarySample> workouts = qb2.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).build().list();
for (HuaweiWorkoutSummarySample sample : workouts) {
session.getHuaweiWorkoutDataSampleDao().queryBuilder().where(
HuaweiWorkoutDataSampleDao.Properties.WorkoutId.eq(sample.getWorkoutId())
).buildDelete().executeDeleteWithoutDetachingEntities();
}
session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
session.getBaseActivitySummaryDao().queryBuilder().where(BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
huaweiCoordinator.deleteDevice(gbDevice, device, session);
}
@Override

View File

@ -1515,6 +1515,7 @@ public class DeviceConfig {
public static class Response extends HuaweiPacket {
public boolean truSleepNewSync = false;
public boolean gpsNewSync = false;
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
@ -1532,6 +1533,7 @@ public class DeviceConfig {
// Tag 2 -> File support
byte value = this.tlv.getByte(0x02);
truSleepNewSync = (value & 2) != 0;
gpsNewSync = (value & 8) != 0;
}
// Tag 3 -> SmartWatchVersion

View File

@ -31,6 +31,7 @@ public class FileDownloadService0A {
Type of files that can be downloaded through here:
- debug files
- sleep files
- gps files
- rrisqi file
*/
@ -68,6 +69,26 @@ public class FileDownloadService0A {
}
}
public static class GpsFileRequest extends HuaweiPacket {
public GpsFileRequest(ParamsProvider paramsProvider, short workoutId) {
super(paramsProvider);
this.serviceId = FileDownloadService0A.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x02, (byte) 0x02)
.put(0x86, new HuaweiTLV()
.put(0x07, (byte) 0x01) // Might be type?
.put(0x88, new HuaweiTLV()
.put(0x09, workoutId)
)
);
this.complete = true;
}
}
public static class Response extends HuaweiPacket {
public String[] fileNames;
@ -77,7 +98,11 @@ public class FileDownloadService0A {
@Override
public void parseTlv() throws ParseException {
String possibleNames = this.tlv.getString(0x01);
String possibleNames;
if (this.tlv.contains(0x01))
possibleNames = this.tlv.getString(0x01);
else // For GPS, also has workoutId (0x09) and another tag (0x0e), not sure of the meaning
possibleNames = this.tlv.getObject(0x8b).getObject(0x8c).getString(0x0d);
fileNames = possibleNames.split(";");
}
}
@ -87,13 +112,16 @@ public class FileDownloadService0A {
public static final int id = 0x02;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider) {
public Request(ParamsProvider paramsProvider, boolean truSleep) {
super(paramsProvider);
this.serviceId = FileDownloadService0A.id;
this.commandId = id;
this.tlv = new HuaweiTLV().put(0x06, (byte) 1);
if (truSleep)
this.tlv = new HuaweiTLV().put(0x06, (byte) 0x01);
else
this.tlv = new HuaweiTLV().put(0x06, (byte) 0x02).put(0x07, (byte) 0x03);
this.complete = true;
}
@ -151,7 +179,8 @@ public class FileDownloadService0A {
@Override
public void parseTlv() throws ParseException {
this.fileLength = this.tlv.getInteger(0x02);
if (this.tlv.contains(0x02))
this.fileLength = this.tlv.getInteger(0x02);
if (this.tlv.contains(0x04))
this.transferType = this.tlv.getByte(0x04);
if (this.tlv.contains(0x05))

View File

@ -33,6 +33,7 @@ public class FileDownloadService2C {
public enum FileType {
SLEEP_STATE,
SLEEP_DATA,
GPS,
UNKNOWN; // Never use this as input
static byte fileTypeToByte(FileType fileType) {
@ -41,6 +42,8 @@ public class FileDownloadService2C {
return (byte) 0x0e;
case SLEEP_DATA:
return (byte) 0x0f;
case GPS:
return (byte) 0x11;
default:
throw new RuntimeException();
}
@ -52,6 +55,8 @@ public class FileDownloadService2C {
return FileType.SLEEP_STATE;
case 0x0f:
return FileType.SLEEP_DATA;
case 0x11:
return FileType.GPS;
default:
return FileType.UNKNOWN;
}

View File

@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
@ -102,48 +103,153 @@ public class HuaweiFileDownloadManager {
}
}
/**
* Only for internal use
*/
public enum FileType {
DEBUG,
SLEEP_STATE,
SLEEP_DATA,
GPS,
UNKNOWN // Never for input!
}
/**
* Only for internal use, though also used in exception
*/
public static class FileDownloadCallback {
public void downloadComplete(FileRequest fileRequest) { }
public void downloadException(HuaweiFileDownloadException e) {
if (e.fileRequest != null)
LOG.error("Error downloading file: {}{}", e.fileRequest.getFilename(), e.fileRequest.isNewSync() ? " (newsync)" : "", e);
else
LOG.error("Error in file download", e);
}
}
public static class FileRequest {
// Inputs
public String filename;
public FileType fileType;
public boolean newSync;
private final String filename;
private final FileType fileType;
private final boolean newSync;
// Sleep type only
public int startTime;
public int endTime;
FileDownloadCallback fileDownloadCallback = null;
// Sleep type only - for 2C GPS they are set to zero
private int startTime = 0;
private int endTime = 0;
// GPS type only
private short workoutId;
private Long databaseId;
private FileRequest(String filename, FileType fileType, boolean newSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
this.filename = filename;
this.fileType = fileType;
this.newSync = newSync;
this.fileDownloadCallback = fileDownloadCallback;
this.startTime = startTime;
this.endTime = endTime;
}
public static FileRequest sleepStateFileRequest(boolean supportsTruSleepNewSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
return new FileRequest("sleep_state.bin", FileType.SLEEP_STATE, supportsTruSleepNewSync, startTime, endTime, fileDownloadCallback);
}
public static FileRequest sleepDataFileRequest(boolean supportsTruSleepNewSync, int startTime, int endTime, FileDownloadCallback fileDownloadCallback) {
return new FileRequest("sleep_data.bin", FileType.SLEEP_DATA, supportsTruSleepNewSync, startTime, endTime, fileDownloadCallback);
}
private FileRequest(String filename, FileType fileType, boolean newSync, FileDownloadCallback fileDownloadCallback) {
this.filename = filename;
this.fileType = fileType;
this.newSync = newSync;
this.fileDownloadCallback = fileDownloadCallback;
}
public static FileRequest debugFileRequest(String filename, FileDownloadCallback fileDownloadCallback) {
return new FileRequest(filename, FileType.DEBUG, false, fileDownloadCallback);
}
private FileRequest(@Nullable String filename, FileType fileType, boolean newSync, short workoutId, Long databaseId, FileDownloadCallback fileDownloadCallback) {
this.filename = filename;
this.fileType = fileType;
this.newSync = newSync;
this.fileDownloadCallback = fileDownloadCallback;
this.workoutId = workoutId;
this.databaseId = databaseId;
}
public static FileRequest workoutGpsFileRequest(boolean newSync, short workoutId, Long databaseId, FileDownloadCallback fileDownloadCallback) {
if (newSync)
return new FileRequest(String.format(Locale.getDefault(), "%d_gps.bin", workoutId), FileType.GPS, true, workoutId, databaseId, fileDownloadCallback);
else
return new FileRequest(null, FileType.GPS, false, workoutId, databaseId, fileDownloadCallback);
}
// Retrieved
public int fileSize;
public int maxBlockSize;
public int timeout; // TODO: unit?
public ByteBuffer buffer;
private int fileSize;
private int maxBlockSize;
private int timeout; // TODO: unit?
private ByteBuffer buffer;
public int startOfBlockOffset;
public int currentBlockSize;
private int startOfBlockOffset;
private int currentBlockSize;
// Old sync only
public String[] filenames;
public byte lastPacketNumber;
private String[] filenames;
private byte lastPacketNumber;
// New sync only
public byte fileId;
public boolean noEncrypt;
private byte fileId;
private boolean noEncrypt;
public byte getFileId() {
return fileId;
}
public String getFilename() {
return filename;
}
public FileType getFileType() {
return fileType;
}
public byte[] getData() {
if (buffer == null)
return new byte[] {};
return buffer.array();
}
public int getCurrentOffset() {
return buffer.position();
}
public boolean isNewSync() {
return newSync;
}
public int getStartTime() {
return startTime;
}
public int getEndTime() {
return endTime;
}
public short getWorkoutId() {
return workoutId;
}
public Long getDatabaseId() {
return databaseId;
}
public int getCurrentBlockSize() {
return currentBlockSize;
}
public boolean isNoEncrypt() {
return noEncrypt;
}
}
/**
@ -219,37 +325,25 @@ public class HuaweiFileDownloadManager {
private final ArrayList<FileRequest> fileRequests;
private FileRequest currentFileRequest;
// If the GB interface needs to be updated at the end of the queue of downloads
private boolean needSync = false;
public HuaweiFileDownloadManager(HuaweiSupportProvider supportProvider) {
this.supportProvider = supportProvider;
handler = new Handler(Looper.getMainLooper());
isBusy = false;
fileRequests = new ArrayList<>();
timeout = () -> {
this.supportProvider.downloadException(new HuaweiFileDownloadTimeoutException(currentFileRequest));
this.currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadTimeoutException(currentFileRequest));
reset();
};
}
public void downloadDebug(String filename) {
FileRequest request = new FileRequest();
request.filename = filename;
request.fileType = FileType.DEBUG;
request.newSync = false;
public void addToQueue(FileRequest fileRequest, boolean needSync) {
synchronized (supportProvider) {
fileRequests.add(request);
}
startDownload();
}
public void downloadSleep(boolean supportsTruSleepNewSync, String filename, int startTime, int endTime) {
FileRequest request = new FileRequest();
request.filename = filename;
request.fileType = (filename.equals("sleep_state.bin"))?FileType.SLEEP_STATE: FileType.SLEEP_DATA;
request.newSync = supportsTruSleepNewSync;
request.startTime = startTime;
request.endTime = endTime;
synchronized (supportProvider) {
fileRequests.add(request);
fileRequests.add(fileRequest);
if (needSync)
this.needSync = true;
}
startDownload();
}
@ -275,7 +369,7 @@ public class HuaweiFileDownloadManager {
@Override
public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
}
});
}
@ -285,11 +379,15 @@ public class HuaweiFileDownloadManager {
initFileDataReceiver(); // Make sure the fileDataReceiver is ready
synchronized (this.supportProvider) {
if (this.isBusy)
if (this.isBusy) {
LOG.debug("A new download is started while a previous is in progress.");
return; // Already downloading, this file will come eventually
}
if (this.fileRequests.isEmpty()) {
// No more files to download
supportProvider.downloadQueueEmpty();
supportProvider.downloadQueueEmpty(this.needSync);
// Don't need sync after this anymore
this.needSync = false;
return;
}
this.isBusy = true;
@ -305,7 +403,7 @@ public class HuaweiFileDownloadManager {
GetFileDownloadInitRequest r = (GetFileDownloadInitRequest) request;
if (r.newSync) {
if (!currentFileRequest.filename.equals(r.filename)) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest,
r.filename
));
@ -313,7 +411,7 @@ public class HuaweiFileDownloadManager {
return;
}
if (currentFileRequest.fileType != r.fileType) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest,
r.fileType
));
@ -329,13 +427,39 @@ public class HuaweiFileDownloadManager {
}
getFileInfo();
} else {
if (!arrayContains(r.filenames, currentFileRequest.filename)) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest,
r.filenames
));
reset();
return;
if (currentFileRequest.fileType == FileType.GPS) {
if (r.filenames.length == 0) {
reset();
return;
}
for (String filename : r.filenames) {
// We only download the gps file itself right now
if (!filename.contains("_gps.bin"))
continue;
// Add download request with the filename at the start of the queue
FileRequest fileRequest = new FileRequest(
filename,
FileType.GPS,
currentFileRequest.newSync,
currentFileRequest.workoutId,
currentFileRequest.databaseId,
currentFileRequest.fileDownloadCallback
);
synchronized (supportProvider) {
fileRequests.add(0, fileRequest);
}
}
currentFileRequest = fileRequests.remove(0); // Replace with the file to download
} else {
if (!arrayContains(r.filenames, currentFileRequest.filename)) {
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest,
r.filenames
));
reset();
return;
}
}
currentFileRequest.filenames = r.filenames;
getDownloadParameters();
@ -344,13 +468,13 @@ public class HuaweiFileDownloadManager {
@Override
public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(currentFileRequest, this.getClass(), e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(currentFileRequest, this.getClass(), e));
}
});
try {
getFileDownloadInitRequest.doPerform();
} catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadInitRequest, e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadInitRequest, e));
reset();
}
}
@ -359,7 +483,10 @@ public class HuaweiFileDownloadManager {
// Old sync only, can never be multiple at the same time
// Assuming currentRequest is the correct one the entire time
// Which may no longer be the case when we implement multi-download for new sync
GetFileParametersRequest getFileParametersRequest = new GetFileParametersRequest(supportProvider);
GetFileParametersRequest getFileParametersRequest = new GetFileParametersRequest(supportProvider,
currentFileRequest.fileType == FileType.SLEEP_STATE ||
currentFileRequest.fileType == FileType.SLEEP_DATA
);
getFileParametersRequest.setFinalizeReq(new Request.RequestCallback() {
@Override
public void call() {
@ -370,14 +497,14 @@ public class HuaweiFileDownloadManager {
@Override
public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
reset();
}
});
try {
getFileParametersRequest.doPerform();
} catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileParametersRequest, e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileParametersRequest, e));
reset();
}
}
@ -390,7 +517,7 @@ public class HuaweiFileDownloadManager {
GetFileInfoRequest r = (GetFileInfoRequest) request;
if (r.newSync) {
if (currentFileRequest.fileId != r.fileId) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, r.fileId, true));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, r.fileId, true));
reset();
return;
}
@ -412,21 +539,19 @@ public class HuaweiFileDownloadManager {
@Override
public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
reset();
}
});
try {
getFileInfoRequest.doPerform();
} catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileInfoRequest, e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileInfoRequest, e));
reset();
}
}
private void downloadNextFileBlock() {
handler.removeCallbacks(this.timeout);
if (currentFileRequest.buffer == null) // New file
currentFileRequest.buffer = ByteBuffer.allocate(currentFileRequest.fileSize);
currentFileRequest.lastPacketNumber = -1; // Counts per block
@ -439,6 +564,9 @@ public class HuaweiFileDownloadManager {
// Start listening for file data
this.supportProvider.addInProgressRequest(fileDataReceiver);
handler.removeCallbacks(this.timeout);
handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
GetFileBlockRequest getFileBlockRequest = new GetFileBlockRequest(supportProvider, currentFileRequest);
getFileBlockRequest.setFinalizeReq(new Request.RequestCallback() {
@Override
@ -451,20 +579,20 @@ public class HuaweiFileDownloadManager {
try {
getFileBlockRequest.doPerform();
} catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileBlockRequest, e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileBlockRequest, e));
reset();
}
}
private void handleFileData(boolean newSync, byte number, byte[] data) {
if (newSync && currentFileRequest.fileId != number) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, true));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, true));
reset();
return;
}
if (!newSync) {
if (currentFileRequest.lastPacketNumber != number - 1) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, false));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, false));
reset();
return;
}
@ -496,13 +624,12 @@ public class HuaweiFileDownloadManager {
try {
getFileDownloadCompleteRequest.doPerform();
} catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadCompleteRequest, e));
currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadCompleteRequest, e));
reset();
}
// Handle file data
if (currentFileRequest.buffer != null) // File size was zero
supportProvider.downloadComplete(currentFileRequest.filename, currentFileRequest.buffer.array());
currentFileRequest.fileDownloadCallback.downloadComplete(currentFileRequest);
if (!this.currentFileRequest.newSync && !this.fileRequests.isEmpty() && !this.fileRequests.get(0).newSync) {
// Old sync can potentially take a shortcut

View File

@ -30,6 +30,7 @@ import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.ArrayList;
@ -54,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiGpsParser;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTruSleepParser;
@ -71,6 +73,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -79,11 +83,14 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -166,6 +173,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetN
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWorkModeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@ -173,6 +182,48 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class HuaweiSupportProvider {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiSupportProvider.class);
private class SyncState {
private boolean activitySync = false;
private boolean workoutSync = false;
private int workoutGpsDownload = 0;
public void setActivitySync(boolean state) {
this.activitySync = state;
updateState();
}
public void setWorkoutSync(boolean state) {
this.workoutSync = state;
updateState();
}
public void startWorkoutGpsDownload() {
this.workoutGpsDownload += 1;
}
public void stopWorkoutGpsDownload() {
this.workoutGpsDownload -= 1;
updateState();
}
public void updateState() {
updateState(true);
}
public void updateState(boolean needSync) {
if (!activitySync && !workoutSync && workoutGpsDownload == 0) {
if (getDevice().isBusy()) {
getDevice().unsetBusyTask();
getDevice().sendDeviceUpdateIntent(context);
}
if (needSync)
GB.signalActivityDataFinish(getDevice());
}
}
}
private final SyncState syncState = new SyncState();
private final int initTimeout = 2000;
private HuaweiBRSupport brSupport;
@ -1097,6 +1148,8 @@ public class HuaweiSupportProvider {
}
private void fetchActivityData() {
syncState.setActivitySync(true);
int sleepStart = 0;
int stepStart = 0;
final int end = (int) (System.currentTimeMillis() / 1000);
@ -1144,13 +1197,13 @@ public class HuaweiSupportProvider {
@Override
public void call() {
if (!downloadTruSleepData(start, end))
handleSyncFinished();
syncState.setActivitySync(false);
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Fitness totals exception", e);
handleSyncFinished();
syncState.setActivitySync(false);
}
});
@ -1161,14 +1214,14 @@ public class HuaweiSupportProvider {
getFitnessTotalsRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting fitness totals request", e);
handleSyncFinished();
syncState.setActivitySync(false);
}
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Step data count exception", e);
handleSyncFinished();
syncState.setActivitySync(false);
}
});
@ -1179,14 +1232,14 @@ public class HuaweiSupportProvider {
getStepDataCountRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting step data count request", e);
handleSyncFinished();
syncState.setActivitySync(false);
}
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Sleep data count exception", e);
handleSyncFinished();
syncState.setActivitySync(false);
}
});
@ -1194,11 +1247,13 @@ public class HuaweiSupportProvider {
getSleepDataCountRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting sleep data count request", e);
handleSyncFinished();
syncState.setActivitySync(false);
}
}
private void fetchWorkoutData() {
syncState.setWorkoutSync(true);
int start = 0;
int end = (int) (System.currentTimeMillis() / 1000);
@ -1261,13 +1316,13 @@ public class HuaweiSupportProvider {
getWorkoutCountRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
handleSyncFinished();
syncState.setWorkoutSync(false);
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Workout parsing exception", e);
handleSyncFinished();
syncState.setWorkoutSync(false);
}
});
@ -1275,18 +1330,10 @@ public class HuaweiSupportProvider {
getWorkoutCountRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting workout count request", e);
handleSyncFinished();
syncState.setWorkoutSync(false);
}
}
private void handleSyncFinished() {
if (gbDevice.isBusy()) {
gbDevice.unsetBusyTask();
gbDevice.sendDeviceUpdateIntent(context);
}
GB.signalActivityDataFinish(getDevice());
}
public void onReset(int flags) {
try {
if(flags== GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) {
@ -1559,7 +1606,8 @@ public class HuaweiSupportProvider {
packet.poolLength,
packet.laps,
packet.avgSwolf,
raw
raw,
null
);
db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample);
@ -2073,52 +2121,151 @@ public class HuaweiSupportProvider {
if (!getHuaweiCoordinator().supportsTruSleep())
return false;
huaweiFileDownloadManager.downloadSleep(
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
"sleep_state.bin", // new String[] {"sleep_state.bin"}, // Later also "sleep_data.bin", but we don't use it right now
start,
end
);
end,
new HuaweiFileDownloadManager.FileDownloadCallback() {
@Override
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
if (fileRequest.getData().length != 0) {
LOG.debug("Parsing sleep state file");
HuaweiTruSleepParser.TruSleepStatus[] results = HuaweiTruSleepParser.parseState(fileRequest.getData());
for (HuaweiTruSleepParser.TruSleepStatus status : results)
addSleepActivity(status.startTime, status.endTime, (byte) 0x06, (byte) 0x0a);
} else
LOG.debug("Sleep state file empty");
syncState.setActivitySync(false);
}
@Override
public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
super.downloadException(e);
syncState.setActivitySync(false);
}
}
), true);
return true;
}
/**
* Called when a file download is complete
* @param fileName Filename of the file
* @param fileContents Contents of the file
*/
public void downloadComplete(String fileName, byte[] fileContents) {
LOG.debug("File download complete: {}: {}", fileName, GB.hexdump(fileContents));
public void downloadWorkoutGpsFiles(short workoutId, Long databaseId) {
syncState.startWorkoutGpsDownload();
if (fileName.equals("sleep_state.bin")) {
HuaweiTruSleepParser.TruSleepStatus[] results = HuaweiTruSleepParser.parseState(fileContents);
for (HuaweiTruSleepParser.TruSleepStatus status : results)
addSleepActivity(status.startTime, status.endTime, (byte) 0x06, (byte) 0x0a);
// This should only be called once per sync - also if we start downloading more sleep data
GB.signalActivityDataFinish(getDevice());
// Unsetting busy is done at the end of all downloads
} // "sleep_data.bin" later as well
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.workoutGpsFileRequest(
getHuaweiCoordinator().isSupportsGpsNewSync(),
workoutId,
databaseId,
new HuaweiFileDownloadManager.FileDownloadCallback() {
@Override
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
syncState.stopWorkoutGpsDownload();
if (fileRequest.getData().length == 0) {
LOG.debug("GPS file empty");
return;
}
LOG.debug("Parsing GPS file");
HuaweiGpsParser.GpsPoint[] points = HuaweiGpsParser.parseHuaweiGps(fileRequest.getData());
LOG.debug("Received {} GPS points", points.length);
if (points.length == 0) {
LOG.debug("No GPS points returned");
return;
}
ActivityTrack track = new ActivityTrack();
track.setName("Workout " + fileRequest.getWorkoutId());
track.setBaseTime(DateTimeUtils.parseTimeStamp(points[0].timestamp));
try (DBHandler db = GBApplication.acquireDB()) {
track.setUser(DBHelper.getUser(db.getDaoSession()));
track.setDevice(DBHelper.getDevice(gbDevice, db.getDaoSession()));
} catch (Exception e) {
LOG.error("Cannot acquire DB, set user, or set device for Activity track, continuing anyway");
}
for (HuaweiGpsParser.GpsPoint point : points) {
GPSCoordinate coordinate;
if (point.altitudeSupported)
coordinate = new GPSCoordinate(point.longitude, point.latitude, point.altitude);
else
coordinate = new GPSCoordinate(point.longitude, point.latitude);
ActivityPoint activityPoint = new ActivityPoint();
activityPoint.setTime(DateTimeUtils.parseTimeStamp(point.timestamp));
activityPoint.setLocation(coordinate);
track.addTrackPoint(activityPoint);
}
String filename = FileUtils.makeValidFileName("workout_" + fileRequest.getWorkoutId() + "_" + points[0].timestamp + ".gpx");
File targetFile;
try {
targetFile = new File(
getDevice().getDeviceCoordinator().getWritableExportDirectory(getDevice()),
filename
);
} catch (IOException e) {
// TODO: Translatable string
GB.toast(context, "Could not open Workout GPS file to write to", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Could not open Workout GPS file to write to", e);
return;
}
GPXExporter exporter = new GPXExporter();
exporter.setCreator(GBApplication.app().getNameAndVersion());
try {
exporter.performExport(track, targetFile);
} catch (IOException | ActivityTrackExporter.GPXTrackEmptyException e) {
// TODO: Translatable string
GB.toast(context, "Failed to export Workout GPX file", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to export Workout GPX file", e);
return;
}
Long databaseId = fileRequest.getDatabaseId();
if (databaseId == null) {
// TODO: Translatable string
GB.toast(context, "Cannot link GPX to workout", Toast.LENGTH_SHORT, GB.ERROR);
LOG.error("Cannot link GPX to workout");
return;
}
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
HuaweiWorkoutSummarySample sample = daoSession.getHuaweiWorkoutSummarySampleDao().load(databaseId);
sample.setGpxFileLocation(targetFile.getAbsolutePath());
sample.update();
} catch (Exception e) {
// TODO: Translatable string
GB.toast(context, "Failed to save Workout GPX file location", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to save Workout GPX file location", e);
return;
}
new HuaweiWorkoutGbParser(getDevice()).parseWorkout(databaseId);
LOG.debug("Completed workout GPS parsing and inserting");
}
@Override
public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
super.downloadException(e);
syncState.stopWorkoutGpsDownload();
}
}
), true);
}
/**
* Called when there are no more files left to download
*/
public void downloadQueueEmpty() {
if (gbDevice.isBusy()) {
gbDevice.unsetBusyTask();
gbDevice.sendDeviceUpdateIntent(context);
}
}
public void downloadException(HuaweiFileDownloadManager.HuaweiFileDownloadException e) {
GB.toast("Error downloading file", Toast.LENGTH_SHORT, GB.ERROR, e);
if (e.fileRequest != null)
LOG.error("Error downloading file: {}{}", e.fileRequest.filename, e.fileRequest.newSync ? " (newsync)" : "", e);
else
LOG.error("Error in file download", e);
// We also reset the sync state, just to get back to working as nicely as possible
handleSyncFinished();
public void downloadQueueEmpty(boolean needSync) {
syncState.updateState(needSync);
}
public void onTestNewFunction() {
@ -2126,17 +2273,27 @@ public class HuaweiSupportProvider {
gbDevice.setBusyTask("Downloading file...");
gbDevice.sendDeviceUpdateIntent(getContext());
huaweiFileDownloadManager.downloadSleep(
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
"sleep_state.bin",
0,
(int) (System.currentTimeMillis() / 1000)
);
huaweiFileDownloadManager.downloadSleep(
(int) (System.currentTimeMillis() / 1000),
new HuaweiFileDownloadManager.FileDownloadCallback() {
@Override
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
// Handle file contents
}
}
), true);
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepDataFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
"sleep_data.bin",
0,
(int) (System.currentTimeMillis() / 1000)
);
(int) (System.currentTimeMillis() / 1000),
new HuaweiFileDownloadManager.FileDownloadCallback() {
@Override
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
// Handle file contents
}
}
), true);
}
}

View File

@ -304,6 +304,10 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser {
}
public void parseWorkout(Long workoutId) {
LOG.debug("Parsing workout ID {}", workoutId);
if (workoutId == null)
return;
try (DBHandler db = GBApplication.acquireDB()) {
final DaoSession session = db.getDaoSession();
final Device device = DBHelper.getDevice(gbDevice, session);
@ -718,6 +722,11 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser {
if (baseSummary.getName() == null) {
baseSummary.setName("Workout " + summary.getWorkoutNumber());
}
if (baseSummary.getGpxTrack() == null) {
baseSummary.setGpxTrack(summary.getGpxFileLocation());
}
// start time never changes
baseSummary.setEndTime(new Date(summary.getEndTimestamp() * 1000L));
baseSummary.setActivityKind(type.getCode());

View File

@ -29,7 +29,7 @@ public class GetFileBlockRequest extends Request {
public GetFileBlockRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
super(support);
if (request.newSync) {
if (request.isNewSync()) {
this.serviceId = FileDownloadService2C.id;
this.commandId = FileDownloadService2C.RequestBlock.id;
} else {
@ -42,20 +42,20 @@ public class GetFileBlockRequest extends Request {
@Override
protected List<byte[]> createRequest() throws Request.RequestCreationException {
try {
if (this.request.newSync)
if (this.request.isNewSync())
return new FileDownloadService2C.RequestBlock(
paramsProvider,
this.request.fileId,
this.request.buffer.position(),
this.request.currentBlockSize,
this.request.noEncrypt
this.request.getFileId(),
this.request.getCurrentOffset(),
this.request.getCurrentBlockSize(),
this.request.isNoEncrypt()
).serialize();
else
return new FileDownloadService0A.RequestBlock.Request(
paramsProvider,
this.request.filename,
this.request.buffer.position(),
this.request.currentBlockSize
this.request.getFilename(),
this.request.getCurrentOffset(),
this.request.getCurrentBlockSize()
).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new Request.RequestCreationException(e);

View File

@ -30,7 +30,7 @@ public class GetFileDownloadCompleteRequest extends Request {
public GetFileDownloadCompleteRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
super(support);
if (request.newSync) {
if (request.isNewSync()) {
this.serviceId = FileDownloadService2C.id;
this.commandId = FileDownloadService2C.FileDownloadCompleteRequest.id;
} else {
@ -43,8 +43,8 @@ public class GetFileDownloadCompleteRequest extends Request {
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
if (request.newSync)
return new FileDownloadService2C.FileDownloadCompleteRequest(paramsProvider, this.request.fileId).serialize();
if (request.isNewSync())
return new FileDownloadService2C.FileDownloadCompleteRequest(paramsProvider, this.request.getFileId()).serialize();
else
return new FileDownloadService0A.FileDownloadCompleteRequest(paramsProvider).serialize();
} catch (HuaweiPacket.CryptoException e) {

View File

@ -41,7 +41,7 @@ public class GetFileDownloadInitRequest extends Request {
public GetFileDownloadInitRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
super(support);
if (request.newSync) {
if (request.isNewSync()) {
this.serviceId = FileDownloadService2C.id;
this.commandId = FileDownloadService2C.FileDownloadInit.id;
} else {
@ -57,6 +57,8 @@ public class GetFileDownloadInitRequest extends Request {
return FileDownloadService2C.FileType.SLEEP_STATE;
case SLEEP_DATA:
return FileDownloadService2C.FileType.SLEEP_DATA;
case GPS:
return FileDownloadService2C.FileType.GPS;
default:
return FileDownloadService2C.FileType.UNKNOWN;
}
@ -68,6 +70,8 @@ public class GetFileDownloadInitRequest extends Request {
return HuaweiFileDownloadManager.FileType.SLEEP_STATE;
case SLEEP_DATA:
return HuaweiFileDownloadManager.FileType.SLEEP_DATA;
case GPS:
return HuaweiFileDownloadManager.FileType.GPS;
default:
return HuaweiFileDownloadManager.FileType.UNKNOWN;
}
@ -76,16 +80,18 @@ public class GetFileDownloadInitRequest extends Request {
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
if (this.request.newSync) {
FileDownloadService2C.FileType type = convertFileTypeTo2C(request.fileType);
if (this.request.isNewSync()) {
FileDownloadService2C.FileType type = convertFileTypeTo2C(request.getFileType());
if (type == FileDownloadService2C.FileType.UNKNOWN)
throw new RequestCreationException("Cannot convert type " + request.fileType);
return new FileDownloadService2C.FileDownloadInit.Request(paramsProvider, request.filename, type, request.startTime, request.endTime).serialize();
throw new RequestCreationException("Cannot convert type " + request.getFileType());
return new FileDownloadService2C.FileDownloadInit.Request(paramsProvider, request.getFilename(), type, request.getStartTime(), request.getEndTime()).serialize();
} else {
if (this.request.fileType == HuaweiFileDownloadManager.FileType.DEBUG)
if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.DEBUG)
return new FileDownloadService0A.FileDownloadInit.DebugFilesRequest(paramsProvider).serialize();
else if (this.request.fileType == HuaweiFileDownloadManager.FileType.SLEEP_STATE)
return new FileDownloadService0A.FileDownloadInit.SleepFilesRequest(paramsProvider, request.startTime, request.endTime).serialize();
else if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.SLEEP_STATE)
return new FileDownloadService0A.FileDownloadInit.SleepFilesRequest(paramsProvider, request.getStartTime(), request.getEndTime()).serialize();
else if (this.request.getFileType() == HuaweiFileDownloadManager.FileType.GPS)
return new FileDownloadService0A.FileDownloadInit.GpsFileRequest(paramsProvider, request.getWorkoutId()).serialize();
else
throw new RequestCreationException("Unknown file type");
}

View File

@ -40,7 +40,7 @@ public class GetFileInfoRequest extends Request {
public GetFileInfoRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
super(support);
if (request.newSync) {
if (request.isNewSync()) {
this.serviceId = FileDownloadService2C.id;
this.commandId = FileDownloadService2C.FileInfo.id;
} else {
@ -53,10 +53,10 @@ public class GetFileInfoRequest extends Request {
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
if (this.request.newSync)
return new FileDownloadService2C.FileInfo.Request(paramsProvider, this.request.fileId).serialize();
if (this.request.isNewSync())
return new FileDownloadService2C.FileInfo.Request(paramsProvider, this.request.getFileId()).serialize();
else
return new FileDownloadService0A.FileInfo.Request(paramsProvider, this.request.filename).serialize();
return new FileDownloadService0A.FileInfo.Request(paramsProvider, this.request.getFilename()).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new RequestCreationException(e);
}

View File

@ -24,19 +24,22 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupport
public class GetFileParametersRequest extends Request {
private final boolean truSleep;
private int maxBlockSize;
private int timeout;
public GetFileParametersRequest(HuaweiSupportProvider support) {
public GetFileParametersRequest(HuaweiSupportProvider support, boolean truSleep) {
super(support);
this.serviceId = FileDownloadService0A.id;
this.commandId = FileDownloadService0A.FileParameters.id;
this.truSleep = truSleep;
}
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
return new FileDownloadService0A.FileParameters.Request(paramsProvider).serialize();
return new FileDownloadService0A.FileParameters.Request(paramsProvider, truSleep).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new RequestCreationException(e);
}

View File

@ -56,5 +56,6 @@ public class GetSettingRelatedRequest extends Request {
LOG.debug("handle Setting Related");
supportProvider.getHuaweiCoordinator().setSupportsTruSleepNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).truSleepNewSync);
supportProvider.getHuaweiCoordinator().setSupportsGpsNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).gpsNewSync);
}
}

View File

@ -113,6 +113,7 @@ public class GetWorkoutDataRequest extends Request {
this.nextRequest(nextRequest);
} else {
new HuaweiWorkoutGbParser(getDevice()).parseWorkout(this.databaseId);
supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, this.databaseId);
if (!remainder.isEmpty()) {
GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(

View File

@ -90,6 +90,7 @@ public class GetWorkoutPaceRequest extends Request {
this.nextRequest(nextRequest);
} else {
new HuaweiWorkoutGbParser(getDevice()).parseWorkout(this.databaseId);
supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, this.databaseId);
if (!remainder.isEmpty()) {
GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(

View File

@ -104,6 +104,7 @@ public class GetWorkoutTotalsRequest extends Request {
this.nextRequest(nextRequest);
} else {
new HuaweiWorkoutGbParser(getDevice()).parseWorkout(databaseId);
supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, databaseId);
if (!remainder.isEmpty()) {
GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(