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 { 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 userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@ -137,8 +137,8 @@ public class GBDaoGenerator {
addHuaweiActivitySample(schema, user, device); addHuaweiActivitySample(schema, user, device);
Entity huaweiWorkoutSummary = addHuaweiWorkoutSummarySample(schema, user, device); Entity huaweiWorkoutSummary = addHuaweiWorkoutSummarySample(schema, user, device);
addHuaweiWorkoutDataSample(schema, user, device, huaweiWorkoutSummary); addHuaweiWorkoutDataSample(schema, huaweiWorkoutSummary);
addHuaweiWorkoutPaceSample(schema, user, device, huaweiWorkoutSummary); addHuaweiWorkoutPaceSample(schema, huaweiWorkoutSummary);
addCalendarSyncState(schema, device); addCalendarSyncState(schema, device);
addAlarms(schema, user, device); addAlarms(schema, user, device);
@ -1307,10 +1307,12 @@ public class GBDaoGenerator {
workoutSummary.addByteArrayProperty("rawData"); workoutSummary.addByteArrayProperty("rawData");
workoutSummary.addStringProperty("gpxFileLocation");
return workoutSummary; 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"); Entity workoutDataSample = addEntity(schema, "HuaweiWorkoutDataSample");
workoutDataSample.setJavaDoc("Contains Huawei Workout data samples (multiple per workout)"); workoutDataSample.setJavaDoc("Contains Huawei Workout data samples (multiple per workout)");
@ -1345,7 +1347,7 @@ public class GBDaoGenerator {
return workoutDataSample; 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"); Entity workoutPaceSample = addEntity(schema, "HuaweiWorkoutPaceSample");
workoutPaceSample.setJavaDoc("Contains Huawei Workout pace data samples (one per workout)"); 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.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; 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.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; 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.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
@ -87,21 +80,7 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
@Override @Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
long deviceId = device.getId(); huaweiCoordinator.deleteDevice(gbDevice, device, session);
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();
} }
@Override @Override

View File

@ -29,7 +29,9 @@ import java.util.TreeMap;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity; import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; 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;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface; 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.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
import androidx.annotation.NonNull;
public class HuaweiCoordinator { public class HuaweiCoordinator {
Logger LOG = LoggerFactory.getLogger(HuaweiCoordinator.class); Logger LOG = LoggerFactory.getLogger(HuaweiCoordinator.class);
@ -56,6 +68,7 @@ public class HuaweiCoordinator {
ByteBuffer notificationConstraints = null; ByteBuffer notificationConstraints = null;
private boolean supportsTruSleepNewSync = false; private boolean supportsTruSleepNewSync = false;
private boolean supportsGpsNewSync = false;
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams; 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() { private SharedPreferences getCapabilitiesSharedPreferences() {
return GBApplication.getContext().getSharedPreferences("huawei_coordinator_capatilities" + parent.getDeviceType().name(), Context.MODE_PRIVATE); 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) { public void setSupportsTruSleepNewSync(boolean supportsTruSleepNewSync) {
this.supportsTruSleepNewSync = 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.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; 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.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; 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.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
@ -96,21 +89,7 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
@Override @Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
long deviceId = device.getId(); huaweiCoordinator.deleteDevice(gbDevice, device, session);
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();
} }
@Override @Override

View File

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

View File

@ -31,6 +31,7 @@ public class FileDownloadService0A {
Type of files that can be downloaded through here: Type of files that can be downloaded through here:
- debug files - debug files
- sleep files - sleep files
- gps files
- rrisqi file - 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 static class Response extends HuaweiPacket {
public String[] fileNames; public String[] fileNames;
@ -77,7 +98,11 @@ public class FileDownloadService0A {
@Override @Override
public void parseTlv() throws ParseException { 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(";"); fileNames = possibleNames.split(";");
} }
} }
@ -87,13 +112,16 @@ public class FileDownloadService0A {
public static final int id = 0x02; public static final int id = 0x02;
public static class Request extends HuaweiPacket { public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider) { public Request(ParamsProvider paramsProvider, boolean truSleep) {
super(paramsProvider); super(paramsProvider);
this.serviceId = FileDownloadService0A.id; this.serviceId = FileDownloadService0A.id;
this.commandId = 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; this.complete = true;
} }
@ -151,6 +179,7 @@ public class FileDownloadService0A {
@Override @Override
public void parseTlv() throws ParseException { public void parseTlv() throws ParseException {
if (this.tlv.contains(0x02))
this.fileLength = this.tlv.getInteger(0x02); this.fileLength = this.tlv.getInteger(0x02);
if (this.tlv.contains(0x04)) if (this.tlv.contains(0x04))
this.transferType = this.tlv.getByte(0x04); this.transferType = this.tlv.getByte(0x04);

View File

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

View File

@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
@ -102,48 +103,153 @@ public class HuaweiFileDownloadManager {
} }
} }
/**
* Only for internal use
*/
public enum FileType { public enum FileType {
DEBUG, DEBUG,
SLEEP_STATE, SLEEP_STATE,
SLEEP_DATA, SLEEP_DATA,
GPS,
UNKNOWN // Never for input! UNKNOWN // Never for input!
} }
/** public static class FileDownloadCallback {
* Only for internal use, though also used in exception 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 { public static class FileRequest {
// Inputs // Inputs
public String filename; private final String filename;
public FileType fileType; private final FileType fileType;
public boolean newSync; private final boolean newSync;
// Sleep type only FileDownloadCallback fileDownloadCallback = null;
public int startTime;
public int endTime;
// 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 // Retrieved
public int fileSize; private int fileSize;
public int maxBlockSize; private int maxBlockSize;
public int timeout; // TODO: unit? private int timeout; // TODO: unit?
public ByteBuffer buffer; private ByteBuffer buffer;
public int startOfBlockOffset; private int startOfBlockOffset;
public int currentBlockSize; private int currentBlockSize;
// Old sync only // Old sync only
public String[] filenames; private String[] filenames;
public byte lastPacketNumber; private byte lastPacketNumber;
// New sync only // New sync only
public byte fileId; private byte fileId;
public boolean noEncrypt; 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 final ArrayList<FileRequest> fileRequests;
private FileRequest currentFileRequest; 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) { public HuaweiFileDownloadManager(HuaweiSupportProvider supportProvider) {
this.supportProvider = supportProvider; this.supportProvider = supportProvider;
handler = new Handler(Looper.getMainLooper()); handler = new Handler(Looper.getMainLooper());
isBusy = false; isBusy = false;
fileRequests = new ArrayList<>(); fileRequests = new ArrayList<>();
timeout = () -> { timeout = () -> {
this.supportProvider.downloadException(new HuaweiFileDownloadTimeoutException(currentFileRequest)); this.currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadTimeoutException(currentFileRequest));
reset(); reset();
}; };
} }
public void downloadDebug(String filename) { public void addToQueue(FileRequest fileRequest, boolean needSync) {
FileRequest request = new FileRequest();
request.filename = filename;
request.fileType = FileType.DEBUG;
request.newSync = false;
synchronized (supportProvider) { synchronized (supportProvider) {
fileRequests.add(request); fileRequests.add(fileRequest);
} if (needSync)
startDownload(); this.needSync = true;
}
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);
} }
startDownload(); startDownload();
} }
@ -275,7 +369,7 @@ public class HuaweiFileDownloadManager {
@Override @Override
public void handleException(Request.ResponseParseException e) { 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 initFileDataReceiver(); // Make sure the fileDataReceiver is ready
synchronized (this.supportProvider) { 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 return; // Already downloading, this file will come eventually
}
if (this.fileRequests.isEmpty()) { if (this.fileRequests.isEmpty()) {
// No more files to download // No more files to download
supportProvider.downloadQueueEmpty(); supportProvider.downloadQueueEmpty(this.needSync);
// Don't need sync after this anymore
this.needSync = false;
return; return;
} }
this.isBusy = true; this.isBusy = true;
@ -305,7 +403,7 @@ public class HuaweiFileDownloadManager {
GetFileDownloadInitRequest r = (GetFileDownloadInitRequest) request; GetFileDownloadInitRequest r = (GetFileDownloadInitRequest) request;
if (r.newSync) { if (r.newSync) {
if (!currentFileRequest.filename.equals(r.filename)) { if (!currentFileRequest.filename.equals(r.filename)) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException( currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest, currentFileRequest,
r.filename r.filename
)); ));
@ -313,7 +411,7 @@ public class HuaweiFileDownloadManager {
return; return;
} }
if (currentFileRequest.fileType != r.fileType) { if (currentFileRequest.fileType != r.fileType) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException( currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest, currentFileRequest,
r.fileType r.fileType
)); ));
@ -328,15 +426,41 @@ public class HuaweiFileDownloadManager {
return; return;
} }
getFileInfo(); getFileInfo();
} else {
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 { } else {
if (!arrayContains(r.filenames, currentFileRequest.filename)) { if (!arrayContains(r.filenames, currentFileRequest.filename)) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException( currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(
currentFileRequest, currentFileRequest,
r.filenames r.filenames
)); ));
reset(); reset();
return; return;
} }
}
currentFileRequest.filenames = r.filenames; currentFileRequest.filenames = r.filenames;
getDownloadParameters(); getDownloadParameters();
} }
@ -344,13 +468,13 @@ public class HuaweiFileDownloadManager {
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(currentFileRequest, this.getClass(), e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(currentFileRequest, this.getClass(), e));
} }
}); });
try { try {
getFileDownloadInitRequest.doPerform(); getFileDownloadInitRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadInitRequest, e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadInitRequest, e));
reset(); reset();
} }
} }
@ -359,7 +483,10 @@ public class HuaweiFileDownloadManager {
// Old sync only, can never be multiple at the same time // Old sync only, can never be multiple at the same time
// Assuming currentRequest is the correct one the entire 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 // 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() { getFileParametersRequest.setFinalizeReq(new Request.RequestCallback() {
@Override @Override
public void call() { public void call() {
@ -370,14 +497,14 @@ public class HuaweiFileDownloadManager {
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
reset(); reset();
} }
}); });
try { try {
getFileParametersRequest.doPerform(); getFileParametersRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileParametersRequest, e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileParametersRequest, e));
reset(); reset();
} }
} }
@ -390,7 +517,7 @@ public class HuaweiFileDownloadManager {
GetFileInfoRequest r = (GetFileInfoRequest) request; GetFileInfoRequest r = (GetFileInfoRequest) request;
if (r.newSync) { if (r.newSync) {
if (currentFileRequest.fileId != r.fileId) { if (currentFileRequest.fileId != r.fileId) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, r.fileId, true)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, r.fileId, true));
reset(); reset();
return; return;
} }
@ -412,21 +539,19 @@ public class HuaweiFileDownloadManager {
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
reset(); reset();
} }
}); });
try { try {
getFileInfoRequest.doPerform(); getFileInfoRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileInfoRequest, e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileInfoRequest, e));
reset(); reset();
} }
} }
private void downloadNextFileBlock() { private void downloadNextFileBlock() {
handler.removeCallbacks(this.timeout);
if (currentFileRequest.buffer == null) // New file if (currentFileRequest.buffer == null) // New file
currentFileRequest.buffer = ByteBuffer.allocate(currentFileRequest.fileSize); currentFileRequest.buffer = ByteBuffer.allocate(currentFileRequest.fileSize);
currentFileRequest.lastPacketNumber = -1; // Counts per block currentFileRequest.lastPacketNumber = -1; // Counts per block
@ -439,6 +564,9 @@ public class HuaweiFileDownloadManager {
// Start listening for file data // Start listening for file data
this.supportProvider.addInProgressRequest(fileDataReceiver); this.supportProvider.addInProgressRequest(fileDataReceiver);
handler.removeCallbacks(this.timeout);
handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
GetFileBlockRequest getFileBlockRequest = new GetFileBlockRequest(supportProvider, currentFileRequest); GetFileBlockRequest getFileBlockRequest = new GetFileBlockRequest(supportProvider, currentFileRequest);
getFileBlockRequest.setFinalizeReq(new Request.RequestCallback() { getFileBlockRequest.setFinalizeReq(new Request.RequestCallback() {
@Override @Override
@ -451,20 +579,20 @@ public class HuaweiFileDownloadManager {
try { try {
getFileBlockRequest.doPerform(); getFileBlockRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileBlockRequest, e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileBlockRequest, e));
reset(); reset();
} }
} }
private void handleFileData(boolean newSync, byte number, byte[] data) { private void handleFileData(boolean newSync, byte number, byte[] data) {
if (newSync && currentFileRequest.fileId != number) { if (newSync && currentFileRequest.fileId != number) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, true)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, true));
reset(); reset();
return; return;
} }
if (!newSync) { if (!newSync) {
if (currentFileRequest.lastPacketNumber != number - 1) { if (currentFileRequest.lastPacketNumber != number - 1) {
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, false)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, false));
reset(); reset();
return; return;
} }
@ -496,13 +624,12 @@ public class HuaweiFileDownloadManager {
try { try {
getFileDownloadCompleteRequest.doPerform(); getFileDownloadCompleteRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadCompleteRequest, e)); currentFileRequest.fileDownloadCallback.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadCompleteRequest, e));
reset(); reset();
} }
// Handle file data // Handle file data
if (currentFileRequest.buffer != null) // File size was zero currentFileRequest.fileDownloadCallback.downloadComplete(currentFileRequest);
supportProvider.downloadComplete(currentFileRequest.filename, currentFileRequest.buffer.array());
if (!this.currentFileRequest.newSync && !this.fileRequests.isEmpty() && !this.fileRequests.get(0).newSync) { if (!this.currentFileRequest.newSync && !this.fileRequests.isEmpty() && !this.fileRequests.get(0).newSync) {
// Old sync can potentially take a shortcut // Old sync can potentially take a shortcut

View File

@ -30,6 +30,7 @@ import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; 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;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto; 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.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTruSleepParser; 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.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample; import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao; 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.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService; import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; 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.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; 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.devices.huawei.packets.FitnessData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWorkModeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWorkModeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; 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.GB;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager; import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@ -173,6 +182,48 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class HuaweiSupportProvider { public class HuaweiSupportProvider {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiSupportProvider.class); 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 final int initTimeout = 2000;
private HuaweiBRSupport brSupport; private HuaweiBRSupport brSupport;
@ -1097,6 +1148,8 @@ public class HuaweiSupportProvider {
} }
private void fetchActivityData() { private void fetchActivityData() {
syncState.setActivitySync(true);
int sleepStart = 0; int sleepStart = 0;
int stepStart = 0; int stepStart = 0;
final int end = (int) (System.currentTimeMillis() / 1000); final int end = (int) (System.currentTimeMillis() / 1000);
@ -1144,13 +1197,13 @@ public class HuaweiSupportProvider {
@Override @Override
public void call() { public void call() {
if (!downloadTruSleepData(start, end)) if (!downloadTruSleepData(start, end))
handleSyncFinished(); syncState.setActivitySync(false);
} }
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
LOG.error("Fitness totals exception", e); LOG.error("Fitness totals exception", e);
handleSyncFinished(); syncState.setActivitySync(false);
} }
}); });
@ -1161,14 +1214,14 @@ public class HuaweiSupportProvider {
getFitnessTotalsRequest.doPerform(); getFitnessTotalsRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Exception on starting fitness totals request", e); LOG.error("Exception on starting fitness totals request", e);
handleSyncFinished(); syncState.setActivitySync(false);
} }
} }
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
LOG.error("Step data count exception", e); LOG.error("Step data count exception", e);
handleSyncFinished(); syncState.setActivitySync(false);
} }
}); });
@ -1179,14 +1232,14 @@ public class HuaweiSupportProvider {
getStepDataCountRequest.doPerform(); getStepDataCountRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Exception on starting step data count request", e); LOG.error("Exception on starting step data count request", e);
handleSyncFinished(); syncState.setActivitySync(false);
} }
} }
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
LOG.error("Sleep data count exception", e); LOG.error("Sleep data count exception", e);
handleSyncFinished(); syncState.setActivitySync(false);
} }
}); });
@ -1194,11 +1247,13 @@ public class HuaweiSupportProvider {
getSleepDataCountRequest.doPerform(); getSleepDataCountRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Exception on starting sleep data count request", e); LOG.error("Exception on starting sleep data count request", e);
handleSyncFinished(); syncState.setActivitySync(false);
} }
} }
private void fetchWorkoutData() { private void fetchWorkoutData() {
syncState.setWorkoutSync(true);
int start = 0; int start = 0;
int end = (int) (System.currentTimeMillis() / 1000); int end = (int) (System.currentTimeMillis() / 1000);
@ -1261,13 +1316,13 @@ public class HuaweiSupportProvider {
getWorkoutCountRequest.setFinalizeReq(new RequestCallback() { getWorkoutCountRequest.setFinalizeReq(new RequestCallback() {
@Override @Override
public void call() { public void call() {
handleSyncFinished(); syncState.setWorkoutSync(false);
} }
@Override @Override
public void handleException(Request.ResponseParseException e) { public void handleException(Request.ResponseParseException e) {
LOG.error("Workout parsing exception", e); LOG.error("Workout parsing exception", e);
handleSyncFinished(); syncState.setWorkoutSync(false);
} }
}); });
@ -1275,18 +1330,10 @@ public class HuaweiSupportProvider {
getWorkoutCountRequest.doPerform(); getWorkoutCountRequest.doPerform();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Exception on starting workout count request", 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) { public void onReset(int flags) {
try { try {
if(flags== GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) { if(flags== GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) {
@ -1559,7 +1606,8 @@ public class HuaweiSupportProvider {
packet.poolLength, packet.poolLength,
packet.laps, packet.laps,
packet.avgSwolf, packet.avgSwolf,
raw raw,
null
); );
db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample); db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample);
@ -2073,52 +2121,151 @@ public class HuaweiSupportProvider {
if (!getHuaweiCoordinator().supportsTruSleep()) if (!getHuaweiCoordinator().supportsTruSleep())
return false; return false;
huaweiFileDownloadManager.downloadSleep( huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(), getHuaweiCoordinator().getSupportsTruSleepNewSync(),
"sleep_state.bin", // new String[] {"sleep_state.bin"}, // Later also "sleep_data.bin", but we don't use it right now
start, 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; return true;
} }
/** public void downloadWorkoutGpsFiles(short workoutId, Long databaseId) {
* Called when a file download is complete syncState.startWorkoutGpsDownload();
* @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));
if (fileName.equals("sleep_state.bin")) { huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.workoutGpsFileRequest(
HuaweiTruSleepParser.TruSleepStatus[] results = HuaweiTruSleepParser.parseState(fileContents); getHuaweiCoordinator().isSupportsGpsNewSync(),
for (HuaweiTruSleepParser.TruSleepStatus status : results) workoutId,
addSleepActivity(status.startTime, status.endTime, (byte) 0x06, (byte) 0x0a); databaseId,
// This should only be called once per sync - also if we start downloading more sleep data new HuaweiFileDownloadManager.FileDownloadCallback() {
GB.signalActivityDataFinish(getDevice()); @Override
// Unsetting busy is done at the end of all downloads public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
} // "sleep_data.bin" later as well 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 * Called when there are no more files left to download
*/ */
public void downloadQueueEmpty() { public void downloadQueueEmpty(boolean needSync) {
if (gbDevice.isBusy()) { syncState.updateState(needSync);
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 onTestNewFunction() { public void onTestNewFunction() {
@ -2126,17 +2273,27 @@ public class HuaweiSupportProvider {
gbDevice.setBusyTask("Downloading file..."); gbDevice.setBusyTask("Downloading file...");
gbDevice.sendDeviceUpdateIntent(getContext()); gbDevice.sendDeviceUpdateIntent(getContext());
huaweiFileDownloadManager.downloadSleep( huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepStateFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(), getHuaweiCoordinator().getSupportsTruSleepNewSync(),
"sleep_state.bin",
0, 0,
(int) (System.currentTimeMillis() / 1000) (int) (System.currentTimeMillis() / 1000),
); new HuaweiFileDownloadManager.FileDownloadCallback() {
huaweiFileDownloadManager.downloadSleep( @Override
public void downloadComplete(HuaweiFileDownloadManager.FileRequest fileRequest) {
// Handle file contents
}
}
), true);
huaweiFileDownloadManager.addToQueue(HuaweiFileDownloadManager.FileRequest.sleepDataFileRequest(
getHuaweiCoordinator().getSupportsTruSleepNewSync(), getHuaweiCoordinator().getSupportsTruSleepNewSync(),
"sleep_data.bin",
0, 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) { public void parseWorkout(Long workoutId) {
LOG.debug("Parsing workout ID {}", workoutId);
if (workoutId == null)
return;
try (DBHandler db = GBApplication.acquireDB()) { try (DBHandler db = GBApplication.acquireDB()) {
final DaoSession session = db.getDaoSession(); final DaoSession session = db.getDaoSession();
final Device device = DBHelper.getDevice(gbDevice, session); final Device device = DBHelper.getDevice(gbDevice, session);
@ -718,6 +722,11 @@ public class HuaweiWorkoutGbParser implements ActivitySummaryParser {
if (baseSummary.getName() == null) { if (baseSummary.getName() == null) {
baseSummary.setName("Workout " + summary.getWorkoutNumber()); baseSummary.setName("Workout " + summary.getWorkoutNumber());
} }
if (baseSummary.getGpxTrack() == null) {
baseSummary.setGpxTrack(summary.getGpxFileLocation());
}
// start time never changes // start time never changes
baseSummary.setEndTime(new Date(summary.getEndTimestamp() * 1000L)); baseSummary.setEndTime(new Date(summary.getEndTimestamp() * 1000L));
baseSummary.setActivityKind(type.getCode()); baseSummary.setActivityKind(type.getCode());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -56,5 +56,6 @@ public class GetSettingRelatedRequest extends Request {
LOG.debug("handle Setting Related"); LOG.debug("handle Setting Related");
supportProvider.getHuaweiCoordinator().setSupportsTruSleepNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).truSleepNewSync); 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); this.nextRequest(nextRequest);
} else { } else {
new HuaweiWorkoutGbParser(getDevice()).parseWorkout(this.databaseId); new HuaweiWorkoutGbParser(getDevice()).parseWorkout(this.databaseId);
supportProvider.downloadWorkoutGpsFiles(this.workoutNumbers.workoutNumber, this.databaseId);
if (!remainder.isEmpty()) { if (!remainder.isEmpty()) {
GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest( GetWorkoutTotalsRequest nextRequest = new GetWorkoutTotalsRequest(

View File

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

View File

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