mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 10:05:49 +01:00
Huawei: Simple TruSleep support
Only supports start and end time of sleep periods.
This commit is contained in:
parent
fbe727644e
commit
6f83fc815f
@ -55,6 +55,8 @@ public class HuaweiCoordinator {
|
||||
byte notificationCapabilities = -0x01;
|
||||
ByteBuffer notificationConstraints = null;
|
||||
|
||||
private boolean supportsTruSleepNewSync = false;
|
||||
|
||||
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
|
||||
|
||||
private final HuaweiCoordinatorSupplier parent;
|
||||
@ -603,4 +605,11 @@ public class HuaweiCoordinator {
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
public boolean getSupportsTruSleepNewSync() {
|
||||
return supportsTruSleepNewSync;
|
||||
}
|
||||
|
||||
public void setSupportsTruSleepNewSync(boolean supportsTruSleepNewSync) {
|
||||
this.supportsTruSleepNewSync = supportsTruSleepNewSync;
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||
@ -241,7 +243,7 @@ public class HuaweiPacket {
|
||||
protected HuaweiTLV tlv = null;
|
||||
|
||||
private byte[] partialPacket = null;
|
||||
private byte[] payload = null;
|
||||
protected byte[] payload = null;
|
||||
|
||||
public boolean complete = false;
|
||||
|
||||
@ -384,9 +386,11 @@ public class HuaweiPacket {
|
||||
|
||||
if (
|
||||
(serviceId == 0x0a && commandId == 0x05) ||
|
||||
(serviceId == 0x28 && commandId == 0x06)
|
||||
(serviceId == 0x28 && commandId == 0x06) ||
|
||||
(serviceId == 0x2c && commandId == 0x05)
|
||||
) {
|
||||
// TODO: this doesn't seem to be TLV
|
||||
this.payload = newPayload;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -487,6 +491,22 @@ public class HuaweiPacket {
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
}
|
||||
case FileDownloadService0A.id:
|
||||
switch (this.commandId) {
|
||||
case FileDownloadService0A.FileDownloadInit.id:
|
||||
return new FileDownloadService0A.FileDownloadInit.Response(paramsProvider).fromPacket(this);
|
||||
case FileDownloadService0A.FileParameters.id:
|
||||
return new FileDownloadService0A.FileParameters.Response(paramsProvider).fromPacket(this);
|
||||
case FileDownloadService0A.FileInfo.id:
|
||||
return new FileDownloadService0A.FileInfo.Response(paramsProvider).fromPacket(this);
|
||||
case FileDownloadService0A.RequestBlock.id:
|
||||
return new FileDownloadService0A.RequestBlock.Response(paramsProvider).fromPacket(this);
|
||||
case FileDownloadService0A.BlockResponse.id:
|
||||
return new FileDownloadService0A.BlockResponse(paramsProvider).fromPacket(this);
|
||||
default:
|
||||
this.isEncrypted = this.attemptDecrypt();
|
||||
return this;
|
||||
}
|
||||
case FindPhone.id:
|
||||
if (this.commandId == FindPhone.Response.id)
|
||||
return new FindPhone.Response(paramsProvider).fromPacket(this);
|
||||
@ -580,6 +600,18 @@ public class HuaweiPacket {
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
}
|
||||
case FileDownloadService2C.id:
|
||||
switch (this.commandId) {
|
||||
case FileDownloadService2C.FileDownloadInit.id:
|
||||
return new FileDownloadService2C.FileDownloadInit.Response(paramsProvider).fromPacket(this);
|
||||
case FileDownloadService2C.FileInfo.id:
|
||||
return new FileDownloadService2C.FileInfo.Response(paramsProvider).fromPacket(this);
|
||||
case FileDownloadService2C.BlockResponse.id:
|
||||
return new FileDownloadService2C.BlockResponse(paramsProvider).fromPacket(this);
|
||||
default:
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
}
|
||||
default:
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
|
@ -55,6 +55,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
* The timestamp of the other marker, if it's larger this is the begin, otherwise the end
|
||||
* - `source`
|
||||
* The source of the data, which Huawei Band message the data came from
|
||||
* 0x0d for sleep data from activity, 0x0a for TruSleep data
|
||||
*/
|
||||
|
||||
private static class RawTypes {
|
||||
@ -155,7 +156,16 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
|
||||
Property activityTypeProperty = HuaweiActivitySampleDao.Properties.RawKind;
|
||||
|
||||
qb.where(sourceProperty.eq(0x0d), activityTypeProperty.eq(0x01));
|
||||
qb.where(
|
||||
qb.or(
|
||||
sourceProperty.eq(0x0d),
|
||||
sourceProperty.eq(0x0a)
|
||||
),
|
||||
qb.or(
|
||||
activityTypeProperty.eq(RawTypes.LIGHT_SLEEP),
|
||||
activityTypeProperty.eq(RawTypes.DEEP_SLEEP)
|
||||
)
|
||||
);
|
||||
|
||||
return getLastFetchTimestamp(qb);
|
||||
}
|
||||
@ -365,7 +375,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
private List<HuaweiActivitySample> interpolate(List<HuaweiActivitySample> processedSamples) {
|
||||
List<HuaweiActivitySample> retv = new ArrayList<>();
|
||||
|
||||
if (processedSamples.size() == 0)
|
||||
if (processedSamples.isEmpty())
|
||||
return retv;
|
||||
|
||||
HuaweiActivitySample lastSample = processedSamples.get(0);
|
||||
@ -463,7 +473,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
if (sample.getTimestamp() > sample.getOtherTimestamp())
|
||||
sample.setTimestamp(sample.getTimestamp() - 1);
|
||||
|
||||
if (processedSamples.size() > 0)
|
||||
if (!processedSamples.isEmpty())
|
||||
lastSample = processedSamples.get(processedSamples.size() - 1);
|
||||
if (lastSample != null && lastSample.getTimestamp() == sample.getTimestamp()) {
|
||||
// Merge the samples - only if there isn't any data yet, except the kind
|
||||
@ -496,7 +506,10 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
||||
processedSamples.add(sample);
|
||||
}
|
||||
|
||||
if (sample.getSource() == FitnessData.MessageData.sleepId && (sample.getRawKind() == RawTypes.LIGHT_SLEEP || sample.getRawKind() == RawTypes.DEEP_SLEEP)) {
|
||||
if (
|
||||
(sample.getSource() == FitnessData.MessageData.sleepId || sample.getSource() == 0x0a) // Sleep sources
|
||||
&& (sample.getRawKind() == RawTypes.LIGHT_SLEEP || sample.getRawKind() == RawTypes.DEEP_SLEEP) // Sleep types
|
||||
) {
|
||||
if (isStartMarker)
|
||||
state.sleepModifier = sample.getRawKind();
|
||||
else
|
||||
|
@ -307,13 +307,17 @@ public class HuaweiTLV {
|
||||
.put(CryptoTags.cipherText, encryptedTLV);
|
||||
}
|
||||
|
||||
public void decrypt(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
|
||||
public byte[] decryptRaw(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
|
||||
byte[] key = paramsProvider.getSecretKey();
|
||||
byte[] decryptedTLV = HuaweiCrypto.decrypt(
|
||||
return HuaweiCrypto.decrypt(
|
||||
paramsProvider.getEncryptMethod() == 0x01 || paramsProvider.getDeviceSupportType() == 0x04,
|
||||
getBytes(CryptoTags.cipherText),
|
||||
key,
|
||||
getBytes(CryptoTags.initVector));
|
||||
}
|
||||
|
||||
public void decrypt(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
|
||||
byte[] decryptedTLV = decryptRaw(paramsProvider);
|
||||
this.valueMap = new ArrayList<>();
|
||||
parse(decryptedTLV);
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
/* 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.devices.huawei;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class HuaweiTruSleepParser {
|
||||
|
||||
public static class TruSleepStatus {
|
||||
public final int startTime;
|
||||
public final int endTime;
|
||||
|
||||
public TruSleepStatus(int startTime, int endTime) {
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TruSleepStatus that = (TruSleepStatus) o;
|
||||
return startTime == that.startTime && endTime == that.endTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TruSleepStatus{" +
|
||||
"endTime=" + endTime +
|
||||
", startTime=" + startTime +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static TruSleepStatus[] parseState(byte[] stateData) {
|
||||
/*
|
||||
Format:
|
||||
- Start time (int)
|
||||
- End time (int)
|
||||
- Unknown (short)
|
||||
- Unknown (byte)
|
||||
- Padding (5 bytes)
|
||||
Could be multiple available
|
||||
*/
|
||||
ByteBuffer buffer = ByteBuffer.wrap(stateData);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
TruSleepStatus[] retv = new TruSleepStatus[buffer.remaining() / 0x10];
|
||||
int c = 0;
|
||||
while (stateData.length - buffer.position() >= 0x10) {
|
||||
int startTime = buffer.getInt();
|
||||
int endTime = buffer.getInt();
|
||||
// Throw away for now because we don't know what it means, and we don't think we can implement this soon
|
||||
buffer.get(); buffer.get(); buffer.get();
|
||||
buffer.get(); buffer.get(); buffer.get(); buffer.get(); buffer.get();
|
||||
|
||||
retv[c++] = new TruSleepStatus(startTime, endTime);
|
||||
}
|
||||
return retv;
|
||||
}
|
||||
}
|
@ -950,7 +950,7 @@ public class DeviceConfig {
|
||||
private final byte[] selfAuthId;
|
||||
private final String groupId;
|
||||
private JSONObject version = null;
|
||||
private JSONObject payload = null;
|
||||
private JSONObject jsonPayload = null;
|
||||
private JSONObject value = null;
|
||||
|
||||
public Request (int operationCode, long requestId, byte[] selfAuthId, String groupId) {
|
||||
@ -969,7 +969,7 @@ public class DeviceConfig {
|
||||
this.isEncrypted = false;
|
||||
this.complete = true;
|
||||
version = new JSONObject();
|
||||
payload = new JSONObject();
|
||||
jsonPayload = new JSONObject();
|
||||
value = new JSONObject();
|
||||
createJson(messageId);
|
||||
}
|
||||
@ -986,14 +986,14 @@ public class DeviceConfig {
|
||||
super(paramsProvider, messageId);
|
||||
// createJson(1); //messageId);
|
||||
try {
|
||||
payload
|
||||
jsonPayload
|
||||
.put("isoSalt", StringUtils.bytesToHex(isoSalt))
|
||||
.put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
|
||||
.put("operationCode", operationCode)
|
||||
.put("seed", StringUtils.bytesToHex(seed))
|
||||
.put("peerUserType", 0x00);
|
||||
if (operationCode == 0x02) {
|
||||
payload
|
||||
jsonPayload
|
||||
.put("pkgName", "com.huawei.devicegroupmanage")
|
||||
.put("serviceType", groupId)
|
||||
.put("keyLength", 0x20);
|
||||
@ -1020,7 +1020,7 @@ public class DeviceConfig {
|
||||
super(paramsProvider, messageId);
|
||||
// createJson(2); //messageId);
|
||||
try {
|
||||
payload
|
||||
jsonPayload
|
||||
.put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
|
||||
.put("token", StringUtils.bytesToHex(token));
|
||||
if (operationCode == 0x02) value.put("isDeviceLevel", false);
|
||||
@ -1044,7 +1044,7 @@ public class DeviceConfig {
|
||||
super(paramsProvider, messageId);
|
||||
// createJson(3);
|
||||
try {
|
||||
payload
|
||||
jsonPayload
|
||||
.put("nonce", StringUtils.bytesToHex(nonce))
|
||||
.put("encData", StringUtils.bytesToHex(encData));
|
||||
this.tlv = new HuaweiTLV()
|
||||
@ -1071,7 +1071,7 @@ public class DeviceConfig {
|
||||
// createJson(3);
|
||||
// }
|
||||
try {
|
||||
payload
|
||||
jsonPayload
|
||||
.put("nonce", StringUtils.bytesToHex(nonce)) //generateRandom
|
||||
.put("encResult", StringUtils.bytesToHex(encResult))
|
||||
.put("operationCode", operationCode);
|
||||
@ -1093,11 +1093,11 @@ public class DeviceConfig {
|
||||
version
|
||||
.put("minVersion", "1.0.0")
|
||||
.put("currentVersion", "2.0.16");
|
||||
payload
|
||||
jsonPayload
|
||||
.put("version", version);
|
||||
value
|
||||
.put("authForm", 0x00)
|
||||
.put("payload", payload)
|
||||
.put("payload", jsonPayload)
|
||||
.put("groupAndModuleVersion", "2.0.1")
|
||||
.put("message", messageId);
|
||||
if (operationCode == 0x01) {
|
||||
@ -1201,7 +1201,7 @@ public class DeviceConfig {
|
||||
public byte type;
|
||||
|
||||
public JSONObject value;
|
||||
public JSONObject payload;
|
||||
public JSONObject jsonPayload;
|
||||
|
||||
public byte step;
|
||||
// public int operationCode; // TODO
|
||||
@ -1225,18 +1225,18 @@ public class DeviceConfig {
|
||||
if (this.type == 0x00) {
|
||||
try {
|
||||
this.value = new JSONObject(this.tlv.getString(0x01));
|
||||
this.payload = value.getJSONObject("payload");
|
||||
this.jsonPayload = value.getJSONObject("payload");
|
||||
|
||||
// Ugly, but should work
|
||||
if (payload.has("isoSalt")) {
|
||||
if (jsonPayload.has("isoSalt")) {
|
||||
this.step = 0x01;
|
||||
this.step1Data = new Step1Data(payload);
|
||||
} else if (payload.has("returnCodeMac")) {
|
||||
this.step1Data = new Step1Data(jsonPayload);
|
||||
} else if (jsonPayload.has("returnCodeMac")) {
|
||||
this.step = 0x02;
|
||||
this.step2Data = new Step2Data(payload);
|
||||
} else if (payload.has("encAuthToken")) {
|
||||
this.step2Data = new Step2Data(jsonPayload);
|
||||
} else if (jsonPayload.has("encAuthToken")) {
|
||||
this.step = 0x03;
|
||||
this.step3Data = new Step3Data(payload);
|
||||
this.step3Data = new Step3Data(jsonPayload);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new JsonException("", e);
|
||||
@ -1344,7 +1344,7 @@ public class DeviceConfig {
|
||||
public long requestId;
|
||||
public byte[] selfAuthId;
|
||||
public String groupId;
|
||||
public JSONObject payload = null;
|
||||
public JSONObject jsonPayload = null;
|
||||
public JSONObject value = null;
|
||||
|
||||
public Step1Data step1Data;
|
||||
@ -1361,20 +1361,20 @@ public class DeviceConfig {
|
||||
public void parseTlv() throws ParseException {
|
||||
try {
|
||||
value = new JSONObject(this.tlv.getString(0x01));
|
||||
payload = value.getJSONObject("payload");
|
||||
jsonPayload = value.getJSONObject("payload");
|
||||
|
||||
if (payload.has("isoSalt")) {
|
||||
if (jsonPayload.has("isoSalt")) {
|
||||
this.step = 1;
|
||||
this.step1Data = new Step1Data(payload);
|
||||
} else if (payload.has("token")) {
|
||||
this.step1Data = new Step1Data(jsonPayload);
|
||||
} else if (jsonPayload.has("token")) {
|
||||
this.step = 2;
|
||||
this.step2Data = new Step2Data(payload);
|
||||
} else if (payload.has("encData")) {
|
||||
this.step2Data = new Step2Data(jsonPayload);
|
||||
} else if (jsonPayload.has("encData")) {
|
||||
this.step = 3;
|
||||
this.step3Data = new Step3Data(payload);
|
||||
} else if (payload.has("encResult")) {
|
||||
this.step3Data = new Step3Data(jsonPayload);
|
||||
} else if (jsonPayload.has("encResult")) {
|
||||
this.step = 4;
|
||||
this.step4Data = new Step4Data(payload);
|
||||
this.step4Data = new Step4Data(jsonPayload);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new JsonException("Cannot parse JSON", e);
|
||||
@ -1493,6 +1493,8 @@ public class DeviceConfig {
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public boolean truSleepNewSync = false;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
this.serviceId = DeviceConfig.id;
|
||||
@ -1501,8 +1503,16 @@ public class DeviceConfig {
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
// Works with bitmaps
|
||||
|
||||
// Tag 1 -> LegalStuff
|
||||
// Tag 2 -> File support
|
||||
|
||||
if (this.tlv.contains(0x02)) {
|
||||
// Tag 2 -> File support
|
||||
byte value = this.tlv.getByte(0x02);
|
||||
truSleepNewSync = (value & 2) != 0;
|
||||
}
|
||||
|
||||
// Tag 3 -> SmartWatchVersion
|
||||
// Tag 4 to 6 are HMS related
|
||||
}
|
||||
|
@ -0,0 +1,237 @@
|
||||
/* 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.devices.huawei.packets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||
|
||||
/**
|
||||
* File downloading for "older" devices/implementations
|
||||
* Newer ones might be using service id 0x2c
|
||||
* Which one is used is reported by the band in 0x01 0x31
|
||||
*/
|
||||
public class FileDownloadService0A {
|
||||
public static final int id = 0x0a;
|
||||
|
||||
/*
|
||||
Type of files that can be downloaded through here:
|
||||
- debug files
|
||||
- sleep files
|
||||
- rrisqi file
|
||||
*/
|
||||
|
||||
public static class FileDownloadInit {
|
||||
public static final int id = 0x01;
|
||||
|
||||
public static class DebugFilesRequest extends HuaweiPacket {
|
||||
public DebugFilesRequest(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV(); // Empty TLV
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SleepFilesRequest extends HuaweiPacket {
|
||||
public SleepFilesRequest(ParamsProvider paramsProvider, int startTime, int endTime) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x02, (byte) 0x01)
|
||||
.put(0x83, new HuaweiTLV()
|
||||
.put(0x04, startTime)
|
||||
.put(0x05, endTime)
|
||||
);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public String[] fileNames;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
String possibleNames = this.tlv.getString(0x01);
|
||||
fileNames = possibleNames.split(";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileParameters {
|
||||
public static final int id = 0x02;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV().put(0x06, (byte) 1);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public String version;
|
||||
public boolean unknown;
|
||||
public short packetSize;
|
||||
public short maxBlockSize;
|
||||
public short timeout;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
// TODO: below could be different for AW70?
|
||||
|
||||
this.version = this.tlv.getString(0x01);
|
||||
this.unknown = this.tlv.getBoolean(0x02);
|
||||
this.packetSize = this.tlv.getShort(0x03);
|
||||
this.maxBlockSize = this.tlv.getShort(0x04);
|
||||
this.timeout = this.tlv.getShort(0x05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileInfo {
|
||||
public static final int id = 0x03;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider, String fileName) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV().put(0x01, fileName);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public int fileLength;
|
||||
public byte transferType = -1;
|
||||
public int fileCreateTime = -1;
|
||||
public byte unknown = -1;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
this.fileLength = this.tlv.getInteger(0x02);
|
||||
if (this.tlv.contains(0x04))
|
||||
this.transferType = this.tlv.getByte(0x04);
|
||||
if (this.tlv.contains(0x05))
|
||||
this.fileCreateTime = this.tlv.getInteger(0x05);
|
||||
if (this.tlv.contains(0x06))
|
||||
this.unknown = this.tlv.getByte(0x06);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestBlock {
|
||||
public static final int id = 0x04;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider, String fileName, int offset, int size) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, fileName)
|
||||
.put(0x02, offset) // TODO: not for AW70
|
||||
.put(0x03, size); // TODO: not for AW70
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public boolean isOk;
|
||||
public String filename;
|
||||
public int offset;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
public void parseTlv() throws ParseException {
|
||||
isOk = this.tlv.getInteger(0x7f) == 0x000186A0;
|
||||
if (isOk) {
|
||||
if (this.tlv.contains(0x01))
|
||||
filename = this.tlv.getString(0x01);
|
||||
offset = this.tlv.getInteger(0x02);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockResponse extends HuaweiPacket {
|
||||
public static final int id = 0x05;
|
||||
|
||||
public byte number;
|
||||
public byte[] data;
|
||||
|
||||
public BlockResponse(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
// Note that this packet does not contain TLV data
|
||||
this.number = this.payload[2];
|
||||
this.data = new byte[this.payload.length - 3];
|
||||
System.arraycopy(this.payload, 3, this.data, 0, this.payload.length - 3);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileDownloadCompleteRequest extends HuaweiPacket {
|
||||
public static final int id = 0x06;
|
||||
|
||||
public FileDownloadCompleteRequest(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV().put(0x06, true);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
/* 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.devices.huawei.packets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||
|
||||
/**
|
||||
* File downloading for "older" devices/implementations
|
||||
* Newer ones might be using service id 0x2c
|
||||
* Which one is used is reported by the band in 0x01 0x31
|
||||
*/
|
||||
public class FileDownloadService2C {
|
||||
public static final int id = 0x2c;
|
||||
|
||||
public enum FileType {
|
||||
SLEEP,
|
||||
UNKNOWN; // Never use this as input
|
||||
|
||||
static byte fileTypeToByte(FileType fileType) {
|
||||
switch (fileType) {
|
||||
case SLEEP:
|
||||
return (byte) 0x0e;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
static FileType byteToFileType(byte b) {
|
||||
switch (b) {
|
||||
case 0x0e:
|
||||
return FileType.SLEEP;
|
||||
default:
|
||||
return FileType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileDownloadInit {
|
||||
public static final int id = 0x01;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider, String filename, FileType filetype, int startTime, int endTime) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = id;
|
||||
|
||||
// TODO: start and end time might be optional?
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, filename)
|
||||
.put(0x02, FileType.fileTypeToByte(filetype))
|
||||
.put(0x05, startTime)
|
||||
.put(0x06, endTime);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public String fileName;
|
||||
public FileType fileType;
|
||||
public byte fileId;
|
||||
public int fileSize;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
fileName = this.tlv.getString(0x01);
|
||||
fileType = FileType.byteToFileType(this.tlv.getByte(0x02));
|
||||
fileId = this.tlv.getByte(0x03);
|
||||
fileSize = this.tlv.getInteger(0x04);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileInfo {
|
||||
public static final int id = 0x03;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider, byte fileId) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, fileId)
|
||||
.put(0x02)
|
||||
.put(0x03)
|
||||
.put(0x04)
|
||||
.put(0x05);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public byte fileId;
|
||||
public byte timeout; // TODO: not sure about unit here - maybe seconds?
|
||||
// TODO: following two might not have the best names...
|
||||
public short burstSize; // How large each 0x2c 0x05 will be
|
||||
public int maxBlockSize; // How much we can ask for before needing another 0x2c 0x04
|
||||
public boolean noEncrypt;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
fileId = this.tlv.getByte(0x01);
|
||||
timeout = this.tlv.getByte(0x02);
|
||||
burstSize = this.tlv.getShort(0x03);
|
||||
maxBlockSize = this.tlv.getInteger(0x04);
|
||||
noEncrypt = this.tlv.getBoolean(0x05); // True if command 0x04 cannot be encrypted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestBlock extends HuaweiPacket {
|
||||
public static final int id = 0x04;
|
||||
|
||||
public RequestBlock(ParamsProvider paramsProvider, byte fileId, int offset, int size, boolean noEncrypt) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, fileId)
|
||||
.put(0x02, offset)
|
||||
.put(0x03, size);
|
||||
|
||||
this.complete = true;
|
||||
this.isEncrypted = !noEncrypt;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockResponse extends HuaweiPacket {
|
||||
public static final int id = 0x05;
|
||||
|
||||
public byte fileId;
|
||||
public int offset;
|
||||
public byte unknown;
|
||||
public byte[] data;
|
||||
|
||||
public BlockResponse(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws ParseException {
|
||||
ByteBuffer byteBuffer;
|
||||
if (this.payload[2] == 0x7c && this.payload[3] == 0x01 && this.payload[4] == 0x01) {
|
||||
// Encrypted TLV, so we decrypt first
|
||||
this.tlv = new HuaweiTLV();
|
||||
this.tlv.parse(this.payload, 2, this.payload.length - 2);
|
||||
try {
|
||||
byteBuffer = ByteBuffer.wrap(this.tlv.decryptRaw(paramsProvider));
|
||||
} catch (HuaweiCrypto.CryptoException e) {
|
||||
throw new CryptoException("File download decryption exception", e);
|
||||
}
|
||||
this.tlv = null; // Prevent using it accidentally
|
||||
} else {
|
||||
byteBuffer = ByteBuffer.wrap(this.payload, 2, this.payload.length - 2);
|
||||
}
|
||||
|
||||
fileId = byteBuffer.get();
|
||||
offset = byteBuffer.getInt();
|
||||
unknown = byteBuffer.get();
|
||||
data = new byte[byteBuffer.remaining()];
|
||||
System.arraycopy(byteBuffer.array(), byteBuffer.position(), data, 0, byteBuffer.remaining());
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileDownloadCompleteRequest extends HuaweiPacket {
|
||||
public static final int id = 0x06;
|
||||
|
||||
public FileDownloadCompleteRequest(ParamsProvider paramsProvider, byte fileId) {
|
||||
super(paramsProvider);
|
||||
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, fileId)
|
||||
.put(0x02, (byte) 1);
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -175,4 +175,8 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
|
||||
supportProvider.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void onTestNewFunction() {
|
||||
supportProvider.onTestNewFunction();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,547 @@
|
||||
/* 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.service.devices.huawei;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileBlockRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileDownloadCompleteRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileDownloadInitRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileInfoRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFileParametersRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class HuaweiFileDownloadManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuaweiFileDownloadManager.class);
|
||||
|
||||
public static class HuaweiFileDownloadException extends Exception {
|
||||
@Nullable
|
||||
public final FileRequest fileRequest;
|
||||
|
||||
HuaweiFileDownloadException(@Nullable FileRequest fileRequest, String message) {
|
||||
super(message);
|
||||
this.fileRequest = fileRequest;
|
||||
}
|
||||
|
||||
HuaweiFileDownloadException(@Nullable FileRequest fileRequest, String message, Exception e) {
|
||||
super(message, e);
|
||||
this.fileRequest = fileRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HuaweiFileDownloadSendException extends HuaweiFileDownloadException {
|
||||
HuaweiFileDownloadSendException(@Nullable FileRequest fileRequest, Request request, Exception e) {
|
||||
super(fileRequest, "Error sending request " + request.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HuaweiFileDownloadRequestException extends HuaweiFileDownloadException {
|
||||
HuaweiFileDownloadRequestException(@Nullable FileRequest fileRequest, Class<?> requestClass, Exception e) {
|
||||
super(fileRequest, "Error in request of class " + requestClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HuaweiFileDownloadTimeoutException extends HuaweiFileDownloadException {
|
||||
HuaweiFileDownloadTimeoutException(@Nullable FileRequest fileRequest) {
|
||||
super(fileRequest, "Timeout was hit!");
|
||||
}
|
||||
}
|
||||
|
||||
public static class HuaweiFileDownloadFileMismatchException extends HuaweiFileDownloadException {
|
||||
HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, String filename) {
|
||||
super(fileRequest, "Data for wrong file received. Expected name " + fileRequest.filename + ", got name " + filename);
|
||||
}
|
||||
|
||||
HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, FileType fileType) {
|
||||
super(fileRequest, "Data for wrong file received. Expected type " + fileRequest.fileType + ", got type " + fileType);
|
||||
}
|
||||
|
||||
HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, String[] filenames) {
|
||||
super(fileRequest, "File " + fileRequest.filename + " cannot be downloaded using this method. Only the following files are supported: " + Arrays.toString(filenames));
|
||||
}
|
||||
|
||||
HuaweiFileDownloadFileMismatchException(@NonNull FileRequest fileRequest, int number, boolean newSync) {
|
||||
super(
|
||||
fileRequest,
|
||||
"Data for wrong file received. Expected " +
|
||||
(newSync ?
|
||||
"id " + fileRequest.fileId + ", got id " + number :
|
||||
"packet number " + (fileRequest.lastPacketNumber + 1) + ", got " + number)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for internal use
|
||||
*/
|
||||
public enum FileType {
|
||||
DEBUG,
|
||||
SLEEP,
|
||||
UNKNOWN // Never for input!
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for internal use, though also used in exception
|
||||
*/
|
||||
public static class FileRequest {
|
||||
// Inputs
|
||||
|
||||
public String filename;
|
||||
public FileType fileType;
|
||||
public boolean newSync;
|
||||
|
||||
// Sleep type only
|
||||
public int startTime;
|
||||
public int endTime;
|
||||
|
||||
|
||||
// Retrieved
|
||||
|
||||
public int fileSize;
|
||||
public int maxBlockSize;
|
||||
public int timeout; // TODO: unit?
|
||||
public ByteBuffer buffer;
|
||||
|
||||
public int startOfBlockOffset;
|
||||
public int currentBlockSize;
|
||||
|
||||
// Old sync only
|
||||
public String[] filenames;
|
||||
public byte lastPacketNumber;
|
||||
|
||||
// New sync only
|
||||
public byte fileId;
|
||||
public boolean noEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually receives the file data
|
||||
*/
|
||||
private static class FileDataReceiver extends Request {
|
||||
|
||||
public boolean newSync;
|
||||
public byte[] data;
|
||||
|
||||
// Packet number for old sync
|
||||
// fileId for new sync
|
||||
public byte number;
|
||||
|
||||
public FileDataReceiver(HuaweiSupportProvider supportProvider) {
|
||||
super(supportProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleResponse(HuaweiPacket response) {
|
||||
if (
|
||||
(response.serviceId == FileDownloadService0A.id &&
|
||||
response.commandId == FileDownloadService0A.BlockResponse.id) ||
|
||||
(response.serviceId == FileDownloadService2C.id &&
|
||||
response.commandId == FileDownloadService2C.BlockResponse.id)
|
||||
) {
|
||||
receivedPacket = response;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean autoRemoveFromResponseHandler() {
|
||||
// This needs to be removed manually
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processResponse() throws ResponseParseException {
|
||||
if (this.receivedPacket instanceof FileDownloadService0A.BlockResponse) {
|
||||
this.newSync = false;
|
||||
this.number = ((FileDownloadService0A.BlockResponse) this.receivedPacket).number;
|
||||
this.data = ((FileDownloadService0A.BlockResponse) this.receivedPacket).data;
|
||||
} else if (this.receivedPacket instanceof FileDownloadService2C.BlockResponse) {
|
||||
this.newSync = true;
|
||||
this.number = ((FileDownloadService2C.BlockResponse) this.receivedPacket).fileId;
|
||||
this.data = ((FileDownloadService2C.BlockResponse) this.receivedPacket).data;
|
||||
} else {
|
||||
throw new ResponseTypeMismatchException(
|
||||
this.receivedPacket,
|
||||
FileDownloadService0A.BlockResponse.class,
|
||||
FileDownloadService2C.BlockResponse.class
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final HuaweiSupportProvider supportProvider;
|
||||
private final Handler handler;
|
||||
private final Runnable timeout;
|
||||
|
||||
// Cannot be final as we need the device to be connected before creating this
|
||||
private FileDataReceiver fileDataReceiver;
|
||||
|
||||
// For old sync we cannot download multiple files at the same time, for new sync we don't want
|
||||
// to, so we limit that. We also do not allow old and new sync at the same time.
|
||||
// Note that old and new sync are already split by the serviceID and commandID that are used for
|
||||
// all the requests.
|
||||
// Note that the timeout is not ready for concurrent downloads
|
||||
private boolean isBusy;
|
||||
|
||||
private final ArrayList<FileRequest> fileRequests;
|
||||
private FileRequest currentFileRequest;
|
||||
|
||||
public HuaweiFileDownloadManager(HuaweiSupportProvider supportProvider) {
|
||||
this.supportProvider = supportProvider;
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
isBusy = false;
|
||||
fileRequests = new ArrayList<>();
|
||||
timeout = () -> {
|
||||
this.supportProvider.downloadException(new HuaweiFileDownloadTimeoutException(currentFileRequest));
|
||||
reset();
|
||||
};
|
||||
}
|
||||
|
||||
public void downloadDebug(String filename) {
|
||||
FileRequest request = new FileRequest();
|
||||
request.filename = filename;
|
||||
request.fileType = FileType.DEBUG;
|
||||
request.newSync = false;
|
||||
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 = FileType.SLEEP;
|
||||
request.newSync = supportsTruSleepNewSync;
|
||||
request.startTime = startTime;
|
||||
request.endTime = endTime;
|
||||
synchronized (supportProvider) {
|
||||
fileRequests.add(request);
|
||||
}
|
||||
startDownload();
|
||||
}
|
||||
|
||||
private boolean arrayContains(String[] haystack, String needle) {
|
||||
return Arrays.stream(haystack).anyMatch(s -> s.equals(needle));
|
||||
}
|
||||
|
||||
private void initFileDataReceiver() {
|
||||
if (fileDataReceiver == null) {
|
||||
// We can only init fileDataReceiver if the device is already connected
|
||||
fileDataReceiver = new FileDataReceiver(supportProvider);
|
||||
fileDataReceiver.setFinalizeReq(new Request.RequestCallback() {
|
||||
@Override
|
||||
public void call() {
|
||||
// Reset timeout
|
||||
handler.removeCallbacks(HuaweiFileDownloadManager.this.timeout);
|
||||
handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
|
||||
|
||||
// Handle data
|
||||
handleFileData(fileDataReceiver.newSync, fileDataReceiver.number, fileDataReceiver.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Request.ResponseParseException e) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void startDownload() {
|
||||
initFileDataReceiver(); // Make sure the fileDataReceiver is ready
|
||||
|
||||
synchronized (this.supportProvider) {
|
||||
if (this.isBusy)
|
||||
return; // Already downloading, this file will come eventually
|
||||
if (this.fileRequests.isEmpty()) {
|
||||
// No more files to download
|
||||
supportProvider.downloadQueueEmpty();
|
||||
return;
|
||||
}
|
||||
this.isBusy = true;
|
||||
}
|
||||
|
||||
this.currentFileRequest = this.fileRequests.remove(0);
|
||||
|
||||
GetFileDownloadInitRequest getFileDownloadInitRequest = new GetFileDownloadInitRequest(supportProvider, currentFileRequest);
|
||||
getFileDownloadInitRequest.setFinalizeReq(new Request.RequestCallback() {
|
||||
@Override
|
||||
public void call(Request request) {
|
||||
// For multi-download, match to file instead of assuming current
|
||||
GetFileDownloadInitRequest r = (GetFileDownloadInitRequest) request;
|
||||
if (r.newSync) {
|
||||
if (!currentFileRequest.filename.equals(r.filename)) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(
|
||||
currentFileRequest,
|
||||
r.filename
|
||||
));
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
if (currentFileRequest.fileType != r.fileType) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(
|
||||
currentFileRequest,
|
||||
r.fileType
|
||||
));
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
currentFileRequest.fileId = r.fileId;
|
||||
currentFileRequest.fileSize = r.fileSize;
|
||||
if (r.fileSize == 0) {
|
||||
// Nothing to download, go to end
|
||||
fileComplete();
|
||||
return;
|
||||
}
|
||||
getFileInfo();
|
||||
} else {
|
||||
if (!arrayContains(r.filenames, currentFileRequest.filename)) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(
|
||||
currentFileRequest,
|
||||
r.filenames
|
||||
));
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
currentFileRequest.filenames = r.filenames;
|
||||
getDownloadParameters();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Request.ResponseParseException e) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadRequestException(currentFileRequest, this.getClass(), e));
|
||||
}
|
||||
});
|
||||
try {
|
||||
getFileDownloadInitRequest.doPerform();
|
||||
} catch (IOException e) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileDownloadInitRequest, e));
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void getDownloadParameters() {
|
||||
// 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.setFinalizeReq(new Request.RequestCallback() {
|
||||
@Override
|
||||
public void call() {
|
||||
currentFileRequest.maxBlockSize = getFileParametersRequest.getMaxBlockSize();
|
||||
currentFileRequest.timeout = getFileParametersRequest.getTimeout();
|
||||
getFileInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Request.ResponseParseException e) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
|
||||
reset();
|
||||
}
|
||||
});
|
||||
try {
|
||||
getFileParametersRequest.doPerform();
|
||||
} catch (IOException e) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadSendException(currentFileRequest, getFileParametersRequest, e));
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void getFileInfo() {
|
||||
GetFileInfoRequest getFileInfoRequest = new GetFileInfoRequest(supportProvider, currentFileRequest);
|
||||
getFileInfoRequest.setFinalizeReq(new Request.RequestCallback() {
|
||||
@Override
|
||||
public void call(Request request) {
|
||||
GetFileInfoRequest r = (GetFileInfoRequest) request;
|
||||
if (r.newSync) {
|
||||
if (currentFileRequest.fileId != r.fileId) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, r.fileId, true));
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
currentFileRequest.timeout = r.timeout;
|
||||
currentFileRequest.maxBlockSize = r.maxBlockSize;
|
||||
currentFileRequest.noEncrypt = r.noEncrypt;
|
||||
} else {
|
||||
// currentFileRequest MUST BE correct here
|
||||
currentFileRequest.fileSize = r.fileLength;
|
||||
|
||||
if (currentFileRequest.fileSize == 0) {
|
||||
// Nothing to download, go to complete
|
||||
fileComplete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
downloadNextFileBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleException(Request.ResponseParseException e) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadRequestException(null, this.getClass(), e));
|
||||
reset();
|
||||
}
|
||||
});
|
||||
try {
|
||||
getFileInfoRequest.doPerform();
|
||||
} catch (IOException e) {
|
||||
supportProvider.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
|
||||
currentFileRequest.startOfBlockOffset = currentFileRequest.buffer.position();
|
||||
currentFileRequest.currentBlockSize = Math.min(
|
||||
currentFileRequest.fileSize - currentFileRequest.buffer.position(), // Remaining file size
|
||||
currentFileRequest.maxBlockSize // Max we can ask for
|
||||
);
|
||||
|
||||
// Start listening for file data
|
||||
this.supportProvider.addInProgressRequest(fileDataReceiver);
|
||||
|
||||
GetFileBlockRequest getFileBlockRequest = new GetFileBlockRequest(supportProvider, currentFileRequest);
|
||||
getFileBlockRequest.setFinalizeReq(new Request.RequestCallback() {
|
||||
@Override
|
||||
public void call() {
|
||||
// Reset timeout
|
||||
handler.removeCallbacks(HuaweiFileDownloadManager.this.timeout);
|
||||
handler.postDelayed(HuaweiFileDownloadManager.this.timeout, currentFileRequest.timeout * 1000L);
|
||||
}
|
||||
});
|
||||
try {
|
||||
getFileBlockRequest.doPerform();
|
||||
} catch (IOException e) {
|
||||
supportProvider.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));
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
if (!newSync) {
|
||||
if (currentFileRequest.lastPacketNumber != number - 1) {
|
||||
supportProvider.downloadException(new HuaweiFileDownloadFileMismatchException(currentFileRequest, number, false));
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
currentFileRequest.lastPacketNumber = number;
|
||||
}
|
||||
|
||||
currentFileRequest.buffer.put(data);
|
||||
|
||||
if (currentFileRequest.buffer.position() >= currentFileRequest.fileSize) {
|
||||
// File complete
|
||||
LOG.info("Download complete for file {}", currentFileRequest.filename);
|
||||
if (currentFileRequest.buffer.position() != currentFileRequest.fileSize) {
|
||||
GB.toast("Downloaded file is larger than expected", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
LOG.error("Downloaded file is larger than expected: {}", currentFileRequest.filename);
|
||||
}
|
||||
fileComplete();
|
||||
} else if (currentFileRequest.buffer.position() - currentFileRequest.startOfBlockOffset >= currentFileRequest.maxBlockSize) {
|
||||
// Block complete, need to request a new file block
|
||||
downloadNextFileBlock();
|
||||
} // Else we're expecting more data to arrive automatically
|
||||
}
|
||||
|
||||
private void fileComplete() {
|
||||
// Stop timeout from hitting
|
||||
this.handler.removeCallbacks(this.timeout);
|
||||
|
||||
// File complete request
|
||||
GetFileDownloadCompleteRequest getFileDownloadCompleteRequest = new GetFileDownloadCompleteRequest(supportProvider, currentFileRequest);
|
||||
try {
|
||||
getFileDownloadCompleteRequest.doPerform();
|
||||
} catch (IOException e) {
|
||||
supportProvider.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());
|
||||
|
||||
if (!this.currentFileRequest.newSync && !this.fileRequests.isEmpty() && !this.fileRequests.get(0).newSync) {
|
||||
// Old sync can potentially take a shortcut
|
||||
if (arrayContains(this.currentFileRequest.filenames, this.fileRequests.get(0).filename)) {
|
||||
// Shortcut to next download
|
||||
// - No init
|
||||
// - No getting parameters
|
||||
// Just copy over the info and go directly to GetFileInfo
|
||||
FileRequest nextRequest = this.fileRequests.remove(0);
|
||||
|
||||
nextRequest.filenames = this.currentFileRequest.filenames;
|
||||
nextRequest.maxBlockSize = this.currentFileRequest.maxBlockSize;
|
||||
nextRequest.timeout = this.currentFileRequest.timeout;
|
||||
|
||||
this.currentFileRequest = nextRequest;
|
||||
getFileInfo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
// Stop listening for file data, if we were doing that
|
||||
this.supportProvider.removeInProgressRequests(this.fileDataReceiver);
|
||||
|
||||
// Reset current request
|
||||
this.currentFileRequest = null;
|
||||
|
||||
// Unset busy, otherwise the next download will never start
|
||||
synchronized (this.supportProvider) {
|
||||
this.isBusy = false;
|
||||
}
|
||||
// Try to download next file
|
||||
startDownload();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
// Stop timeout from hitting, nothing else to really do
|
||||
this.handler.removeCallbacks(this.timeout);
|
||||
}
|
||||
}
|
@ -183,4 +183,8 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
|
||||
supportProvider.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void onTestNewFunction() {
|
||||
supportProvider.onTestNewFunction();
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupp
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTruSleepParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||
@ -200,6 +201,8 @@ public class HuaweiSupportProvider {
|
||||
|
||||
protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager(this);
|
||||
|
||||
protected HuaweiFileDownloadManager huaweiFileDownloadManager = new HuaweiFileDownloadManager(this);
|
||||
|
||||
public HuaweiCoordinatorSupplier getCoordinator() {
|
||||
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
|
||||
}
|
||||
@ -1092,7 +1095,7 @@ public class HuaweiSupportProvider {
|
||||
private void fetchActivityData() {
|
||||
int sleepStart = 0;
|
||||
int stepStart = 0;
|
||||
int end = (int) (System.currentTimeMillis() / 1000);
|
||||
final int end = (int) (System.currentTimeMillis() / 1000);
|
||||
|
||||
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
||||
long prefLastSyncTime = sharedPreferences.getLong("lastSyncTimeMillis", 0);
|
||||
@ -1129,12 +1132,15 @@ public class HuaweiSupportProvider {
|
||||
}
|
||||
|
||||
final GetStepDataCountRequest getStepDataCountRequest = new GetStepDataCountRequest(this, stepStart, end);
|
||||
//noinspection ExtractMethodRecommender
|
||||
final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
|
||||
|
||||
final int start = sleepStart;
|
||||
getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
|
||||
@Override
|
||||
public void call() {
|
||||
handleSyncFinished();
|
||||
if (!downloadTruSleepData(start, end))
|
||||
handleSyncFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1455,18 +1461,20 @@ public class HuaweiSupportProvider {
|
||||
responseManager.addHandler(request);
|
||||
}
|
||||
|
||||
public void addSleepActivity(int timestamp, short duration, byte type) {
|
||||
public void addSleepActivity(int timestamp_start, int timestamp_end, byte type, byte source) {
|
||||
LOG.debug("Adding sleep activity between {} and {}", timestamp_start, timestamp_end);
|
||||
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
|
||||
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
|
||||
|
||||
HuaweiActivitySample activitySample = new HuaweiActivitySample(
|
||||
timestamp,
|
||||
timestamp_start,
|
||||
deviceId,
|
||||
userId,
|
||||
timestamp + duration,
|
||||
FitnessData.MessageData.sleepId,
|
||||
timestamp_end,
|
||||
source,
|
||||
type,
|
||||
1,
|
||||
ActivitySample.NOT_MEASURED,
|
||||
@ -2071,5 +2079,78 @@ public class HuaweiSupportProvider {
|
||||
|
||||
public void dispose() {
|
||||
stopBatteryRunnerDelayed();
|
||||
huaweiFileDownloadManager.dispose();
|
||||
}
|
||||
|
||||
public boolean downloadTruSleepData(int start, int end) {
|
||||
// We only get the data if TruSleep is supported
|
||||
if (!getHuaweiCoordinator().supportsTruSleep())
|
||||
return false;
|
||||
|
||||
huaweiFileDownloadManager.downloadSleep(
|
||||
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
|
||||
);
|
||||
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));
|
||||
|
||||
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();
|
||||
// Unsetting busy is done at the end of all downloads
|
||||
} // "sleep_data.bin" later as well
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 onTestNewFunction() {
|
||||
// Show to user
|
||||
gbDevice.setBusyTask("Downloading file...");
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
huaweiFileDownloadManager.downloadSleep(
|
||||
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
|
||||
"sleep_state.bin",
|
||||
0,
|
||||
(int) (System.currentTimeMillis() / 1000)
|
||||
);
|
||||
huaweiFileDownloadManager.downloadSleep(
|
||||
getHuaweiCoordinator().getSupportsTruSleepNewSync(),
|
||||
"sleep_data.bin",
|
||||
0,
|
||||
(int) (System.currentTimeMillis() / 1000)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,16 @@ public class ResponseManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all requests with specified class from the response handler list
|
||||
* @param handlerClass The class of which the requests are removed
|
||||
*/
|
||||
public void removeHandler(Class<?> handlerClass) {
|
||||
synchronized (handlers) {
|
||||
handlers.removeIf(request -> request.getClass() == handlerClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the data into a Huawei Packet.
|
||||
* If the packet is complete, it will be handled by the first request that accepts it,
|
||||
@ -102,8 +112,10 @@ public class ResponseManager {
|
||||
} else {
|
||||
LOG.debug("Service: " + Integer.toHexString(receivedPacket.serviceId & 0xff) + ", command: " + Integer.toHexString(receivedPacket.commandId & 0xff) + ", handled by: " + handler.getClass());
|
||||
|
||||
synchronized (handlers) {
|
||||
handlers.remove(handler);
|
||||
if (handler.autoRemoveFromResponseHandler()) {
|
||||
synchronized (handlers) {
|
||||
handlers.remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
handler.handleResponse();
|
||||
|
@ -0,0 +1,69 @@
|
||||
/* 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.service.devices.huawei.requests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
|
||||
public class GetFileBlockRequest extends Request {
|
||||
private final HuaweiFileDownloadManager.FileRequest request;
|
||||
|
||||
public GetFileBlockRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
|
||||
super(support);
|
||||
if (request.newSync) {
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = FileDownloadService2C.RequestBlock.id;
|
||||
} else {
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = FileDownloadService0A.RequestBlock.id;
|
||||
}
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws Request.RequestCreationException {
|
||||
try {
|
||||
if (this.request.newSync)
|
||||
return new FileDownloadService2C.RequestBlock(
|
||||
paramsProvider,
|
||||
this.request.fileId,
|
||||
this.request.buffer.position(),
|
||||
this.request.currentBlockSize,
|
||||
this.request.noEncrypt
|
||||
).serialize();
|
||||
else
|
||||
return new FileDownloadService0A.RequestBlock.Request(
|
||||
paramsProvider,
|
||||
this.request.filename,
|
||||
this.request.buffer.position(),
|
||||
this.request.currentBlockSize
|
||||
).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new Request.RequestCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processResponse() throws Request.ResponseParseException {
|
||||
// TODO: handle data?
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* 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.service.devices.huawei.requests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
|
||||
public class GetFileDownloadCompleteRequest extends Request {
|
||||
|
||||
private final HuaweiFileDownloadManager.FileRequest request;
|
||||
|
||||
public GetFileDownloadCompleteRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
|
||||
super(support);
|
||||
if (request.newSync) {
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = FileDownloadService2C.FileDownloadCompleteRequest.id;
|
||||
} else {
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = FileDownloadService0A.FileDownloadCompleteRequest.id;
|
||||
}
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
if (request.newSync)
|
||||
return new FileDownloadService2C.FileDownloadCompleteRequest(paramsProvider, this.request.fileId).serialize();
|
||||
else
|
||||
return new FileDownloadService0A.FileDownloadCompleteRequest(paramsProvider).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/* 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.service.devices.huawei.requests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
|
||||
public class GetFileDownloadInitRequest extends Request {
|
||||
|
||||
final HuaweiFileDownloadManager.FileRequest request;
|
||||
|
||||
public boolean newSync;
|
||||
|
||||
// Old sync
|
||||
public String[] filenames;
|
||||
|
||||
// New sync
|
||||
public String filename;
|
||||
public HuaweiFileDownloadManager.FileType fileType;
|
||||
public byte fileId;
|
||||
public int fileSize;
|
||||
|
||||
public GetFileDownloadInitRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
|
||||
super(support);
|
||||
if (request.newSync) {
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = FileDownloadService2C.FileDownloadInit.id;
|
||||
} else {
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = FileDownloadService0A.FileDownloadInit.id;
|
||||
}
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
private FileDownloadService2C.FileType convertFileTypeTo2C(HuaweiFileDownloadManager.FileType type) {
|
||||
switch (type) {
|
||||
case SLEEP:
|
||||
return FileDownloadService2C.FileType.SLEEP;
|
||||
default:
|
||||
return FileDownloadService2C.FileType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private HuaweiFileDownloadManager.FileType convertFileTypeFrom2C(FileDownloadService2C.FileType type) {
|
||||
switch (type) {
|
||||
case SLEEP:
|
||||
return HuaweiFileDownloadManager.FileType.SLEEP;
|
||||
default:
|
||||
return HuaweiFileDownloadManager.FileType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
if (this.request.newSync) {
|
||||
FileDownloadService2C.FileType type = convertFileTypeTo2C(request.fileType);
|
||||
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();
|
||||
} else {
|
||||
if (this.request.fileType == HuaweiFileDownloadManager.FileType.DEBUG)
|
||||
return new FileDownloadService0A.FileDownloadInit.DebugFilesRequest(paramsProvider).serialize();
|
||||
else if (this.request.fileType == HuaweiFileDownloadManager.FileType.SLEEP)
|
||||
return new FileDownloadService0A.FileDownloadInit.SleepFilesRequest(paramsProvider, request.startTime, request.endTime).serialize();
|
||||
else
|
||||
throw new RequestCreationException("Unknown file type");
|
||||
}
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processResponse() throws ResponseParseException {
|
||||
// In case of multiple downloads, the response here does not need to match the original request
|
||||
// So we cannot use the request at all!
|
||||
|
||||
if (this.receivedPacket instanceof FileDownloadService0A.FileDownloadInit.Response) {
|
||||
this.newSync = false;
|
||||
this.filenames = ((FileDownloadService0A.FileDownloadInit.Response) this.receivedPacket).fileNames;
|
||||
} else if (this.receivedPacket instanceof FileDownloadService2C.FileDownloadInit.Response) {
|
||||
this.newSync = true;
|
||||
FileDownloadService2C.FileDownloadInit.Response packet = (FileDownloadService2C.FileDownloadInit.Response) this.receivedPacket;
|
||||
this.filename = packet.fileName;
|
||||
this.fileType = convertFileTypeFrom2C(packet.fileType);
|
||||
this.fileId = packet.fileId;
|
||||
this.fileSize = packet.fileSize;
|
||||
} else {
|
||||
throw new ResponseTypeMismatchException(receivedPacket, FileDownloadService2C.FileDownloadInit.Response.class);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/* 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.service.devices.huawei.requests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService2C;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFileDownloadManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
|
||||
public class GetFileInfoRequest extends Request {
|
||||
private final HuaweiFileDownloadManager.FileRequest request;
|
||||
|
||||
public boolean newSync;
|
||||
|
||||
// Old sync
|
||||
public int fileLength;
|
||||
|
||||
// New sync
|
||||
public byte fileId;
|
||||
public byte timeout;
|
||||
public int maxBlockSize;
|
||||
public boolean noEncrypt;
|
||||
|
||||
public GetFileInfoRequest(HuaweiSupportProvider support, HuaweiFileDownloadManager.FileRequest request) {
|
||||
super(support);
|
||||
if (request.newSync) {
|
||||
this.serviceId = FileDownloadService2C.id;
|
||||
this.commandId = FileDownloadService2C.FileInfo.id;
|
||||
} else {
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = FileDownloadService0A.FileInfo.id;
|
||||
}
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
if (this.request.newSync)
|
||||
return new FileDownloadService2C.FileInfo.Request(paramsProvider, this.request.fileId).serialize();
|
||||
else
|
||||
return new FileDownloadService0A.FileInfo.Request(paramsProvider, this.request.filename).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processResponse() throws ResponseParseException {
|
||||
if (this.receivedPacket instanceof FileDownloadService0A.FileInfo.Response) {
|
||||
this.newSync = false;
|
||||
this.fileLength = ((FileDownloadService0A.FileInfo.Response) this.receivedPacket).fileLength;
|
||||
} else if (this.receivedPacket instanceof FileDownloadService2C.FileInfo.Response) {
|
||||
this.newSync = true;
|
||||
FileDownloadService2C.FileInfo.Response packet = (FileDownloadService2C.FileInfo.Response) this.receivedPacket;
|
||||
this.fileId = packet.fileId;
|
||||
this.timeout = packet.timeout;
|
||||
this.maxBlockSize = packet.maxBlockSize;
|
||||
this.noEncrypt = packet.noEncrypt;
|
||||
} else {
|
||||
throw new ResponseTypeMismatchException(this.receivedPacket, FileDownloadService2C.FileInfo.Response.class);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/* 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.service.devices.huawei.requests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
|
||||
public class GetFileParametersRequest extends Request {
|
||||
|
||||
private int maxBlockSize;
|
||||
private int timeout;
|
||||
|
||||
public GetFileParametersRequest(HuaweiSupportProvider support) {
|
||||
super(support);
|
||||
this.serviceId = FileDownloadService0A.id;
|
||||
this.commandId = FileDownloadService0A.FileParameters.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
return new FileDownloadService0A.FileParameters.Request(paramsProvider).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processResponse() throws ResponseParseException {
|
||||
if (!(this.receivedPacket instanceof FileDownloadService0A.FileParameters.Response))
|
||||
throw new ResponseTypeMismatchException(this.receivedPacket, FileDownloadService0A.FileParameters.Response.class);
|
||||
this.maxBlockSize = ((FileDownloadService0A.FileParameters.Response) this.receivedPacket).maxBlockSize;
|
||||
this.timeout = ((FileDownloadService0A.FileParameters.Response) this.receivedPacket).timeout;
|
||||
}
|
||||
|
||||
public int getMaxBlockSize() {
|
||||
return maxBlockSize;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
}
|
@ -46,6 +46,11 @@ public class GetSettingRelatedRequest extends Request {
|
||||
|
||||
@Override
|
||||
protected void processResponse() throws ResponseParseException {
|
||||
if (!(receivedPacket instanceof DeviceConfig.SettingRelated.Response))
|
||||
throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.SettingRelated.Response.class);
|
||||
|
||||
LOG.debug("handle Setting Related");
|
||||
|
||||
supportProvider.getHuaweiCoordinator().setSupportsTruSleepNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).truSleepNewSync);
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public class GetSleepDataRequest extends Request {
|
||||
(timestampInts[5]);
|
||||
short duration = (short) (durationInt * 60);
|
||||
|
||||
this.supportProvider.addSleepActivity(timestamp, duration, subContainer.type);
|
||||
this.supportProvider.addSleepActivity(timestamp, timestamp + duration, subContainer.type, FitnessData.MessageData.sleepId);
|
||||
}
|
||||
|
||||
if (count + 1 < maxCount) {
|
||||
|
@ -79,6 +79,10 @@ public class Request {
|
||||
public ResponseTypeMismatchException(HuaweiPacket a, Class<?> b) {
|
||||
super("Response type mismatch, packet is of type " + a.getClass() + " but expected " + b);
|
||||
}
|
||||
|
||||
public ResponseTypeMismatchException(HuaweiPacket a, Class<?> b, Class<?> c) {
|
||||
super("Response type mismatch, packet is of type " + a.getClass() + " but expected " + b + " or " + c);
|
||||
}
|
||||
}
|
||||
|
||||
public static class WorkoutParseException extends ResponseParseException {
|
||||
@ -111,10 +115,13 @@ public class Request {
|
||||
public RequestCallback(HuaweiSupportProvider supportProvider) {
|
||||
support = supportProvider;
|
||||
}
|
||||
public void call() {};
|
||||
public void call() {}
|
||||
public void call(Request request) {
|
||||
call(); // To keep everything working as it was as well
|
||||
}
|
||||
public void handleException(ResponseParseException e) {
|
||||
LOG.error("Callback request exception", e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Request(HuaweiSupportProvider supportProvider, nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
|
||||
@ -213,7 +220,7 @@ public class Request {
|
||||
if (nextRequest == null || stopChain) {
|
||||
operationStatus = OperationStatus.FINISHED;
|
||||
if (finalizeReq != null) {
|
||||
finalizeReq.call();
|
||||
finalizeReq.call(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -300,4 +307,8 @@ public class Request {
|
||||
this.supportProvider.performConnected(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean autoRemoveFromResponseHandler() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ public class SetTruSleepRequest extends Request {
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
boolean truSleepSwitch = GBApplication
|
||||
.getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
|
||||
.getBoolean(HuaweiConstants.PREF_HUAWEI_TRUSLEEP, false);
|
||||
.getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
|
||||
.getBoolean(HuaweiConstants.PREF_HUAWEI_TRUSLEEP, false);
|
||||
try {
|
||||
return new FitnessData.TruSleep.Request(paramsProvider, truSleepSwitch).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
|
@ -2468,7 +2468,7 @@
|
||||
<string name="huawei_trusleep_title">HUAWEI TruSleep ™</string>
|
||||
<string name="huawei_trusleep_summary">Monitor your sleep quality and breathing pattern in real time.\nAnalyze your sleep patterns and accurately diagnose 6 types of sleeping problems.</string>
|
||||
<string name="huawei_trusleep_summary_light">Improved sleep monitoring</string>
|
||||
<string name="huawei_trusleep_warning">Warning: enabling this will stop sleep from showing up in Gadgetbridge! Click here if you accept this.</string>
|
||||
<string name="huawei_trusleep_warning">Warning: enabling this will make all sleep show up as light sleep in Gadgetbridge! Click here if you accept this.</string>
|
||||
<string name="prefs_activity_recognition">Activity recognition settings</string>
|
||||
<string name="pref_activity_recognize_running">recognize running</string>
|
||||
<string name="pref_activity_recognize_biking">recognize biking</string>
|
||||
|
@ -0,0 +1,39 @@
|
||||
/* 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.devices.huawei;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class TestHuaweiTruSleepParser {
|
||||
|
||||
@Test
|
||||
public void testParseState() {
|
||||
byte[] test = GB.hexStringToByteArray("0100000002000000000000000000000003000000050000000000000000000000");
|
||||
|
||||
HuaweiTruSleepParser.TruSleepStatus[] expected = new HuaweiTruSleepParser.TruSleepStatus[] {
|
||||
new HuaweiTruSleepParser.TruSleepStatus(1, 2),
|
||||
new HuaweiTruSleepParser.TruSleepStatus(3, 5)
|
||||
};
|
||||
|
||||
HuaweiTruSleepParser.TruSleepStatus[] result = HuaweiTruSleepParser.parseState(test);
|
||||
|
||||
Assert.assertArrayEquals(expected, result);
|
||||
}
|
||||
}
|
@ -140,11 +140,6 @@ public class TestDebugRequestParser {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSleepActivity(int timestamp, short duration, byte type) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
|
||||
|
||||
|
@ -36,6 +36,7 @@ import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
@ -44,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ -151,11 +153,6 @@ public class TestResponseManager {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSleepActivity(int timestamp, short duration, byte type) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
|
||||
|
||||
@ -233,6 +230,30 @@ public class TestResponseManager {
|
||||
Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveHandlerClass() throws IllegalAccessException {
|
||||
Request input1 = new GetEventAlarmList(supportProvider);
|
||||
Request input2 = new GetEventAlarmList(supportProvider);
|
||||
Request extra = new Request(supportProvider);
|
||||
|
||||
List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
|
||||
inputHandlers.add(extra);
|
||||
inputHandlers.add(input1);
|
||||
inputHandlers.add(extra);
|
||||
inputHandlers.add(input2);
|
||||
|
||||
List<Request> expectedHandlers = Collections.synchronizedList(new ArrayList<Request>());
|
||||
expectedHandlers.add(extra);
|
||||
expectedHandlers.add(extra);
|
||||
|
||||
ResponseManager responseManager = new ResponseManager(supportProvider);
|
||||
handlersField.set(responseManager, inputHandlers);
|
||||
|
||||
responseManager.removeHandler(GetEventAlarmList.class);
|
||||
|
||||
Assert.assertEquals(expectedHandlers, handlersField.get(responseManager));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleDataCompletePacketSynchronous() throws Exception {
|
||||
// Note that this is not a proper packet, but that doesn't matter as we're not testing
|
||||
@ -249,10 +270,9 @@ public class TestResponseManager {
|
||||
Request request1 = Mockito.mock(Request.class);
|
||||
when(request1.handleResponse((HuaweiPacket) any()))
|
||||
.thenReturn(true);
|
||||
when(request1.autoRemoveFromResponseHandler())
|
||||
.thenReturn(true);
|
||||
Request request2 = Mockito.mock(Request.class);
|
||||
// FIXME: Removed due to UnnecessaryStubbingException after mockito-core update
|
||||
//when(request2.handleResponse((HuaweiPacket) any()))
|
||||
// .thenReturn(false);
|
||||
|
||||
List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
|
||||
inputHandlers.add(request1);
|
||||
@ -342,10 +362,9 @@ public class TestResponseManager {
|
||||
Request request1 = Mockito.mock(Request.class);
|
||||
when(request1.handleResponse((HuaweiPacket) any()))
|
||||
.thenReturn(true);
|
||||
when(request1.autoRemoveFromResponseHandler())
|
||||
.thenReturn(true);
|
||||
Request request2 = Mockito.mock(Request.class);
|
||||
// FIXME: Removed due to UnnecessaryStubbingException after mockito-core update
|
||||
//when(request2.handleResponse((HuaweiPacket) any()))
|
||||
// .thenReturn(false);
|
||||
|
||||
List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
|
||||
inputHandlers.add(request1);
|
||||
|
Loading…
Reference in New Issue
Block a user