mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-13 11:17:33 +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;
|
byte notificationCapabilities = -0x01;
|
||||||
ByteBuffer notificationConstraints = null;
|
ByteBuffer notificationConstraints = null;
|
||||||
|
|
||||||
|
private boolean supportsTruSleepNewSync = false;
|
||||||
|
|
||||||
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
|
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
|
||||||
|
|
||||||
private final HuaweiCoordinatorSupplier parent;
|
private final HuaweiCoordinatorSupplier parent;
|
||||||
@ -603,4 +605,11 @@ public class HuaweiCoordinator {
|
|||||||
return handler.isValid() ? handler : null;
|
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.AccountRelated;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
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.GpsAndTime;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
@ -241,7 +243,7 @@ public class HuaweiPacket {
|
|||||||
protected HuaweiTLV tlv = null;
|
protected HuaweiTLV tlv = null;
|
||||||
|
|
||||||
private byte[] partialPacket = null;
|
private byte[] partialPacket = null;
|
||||||
private byte[] payload = null;
|
protected byte[] payload = null;
|
||||||
|
|
||||||
public boolean complete = false;
|
public boolean complete = false;
|
||||||
|
|
||||||
@ -384,9 +386,11 @@ public class HuaweiPacket {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(serviceId == 0x0a && commandId == 0x05) ||
|
(serviceId == 0x0a && commandId == 0x05) ||
|
||||||
(serviceId == 0x28 && commandId == 0x06)
|
(serviceId == 0x28 && commandId == 0x06) ||
|
||||||
|
(serviceId == 0x2c && commandId == 0x05)
|
||||||
) {
|
) {
|
||||||
// TODO: this doesn't seem to be TLV
|
// TODO: this doesn't seem to be TLV
|
||||||
|
this.payload = newPayload;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,6 +491,22 @@ public class HuaweiPacket {
|
|||||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||||
return this;
|
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:
|
case FindPhone.id:
|
||||||
if (this.commandId == FindPhone.Response.id)
|
if (this.commandId == FindPhone.Response.id)
|
||||||
return new FindPhone.Response(paramsProvider).fromPacket(this);
|
return new FindPhone.Response(paramsProvider).fromPacket(this);
|
||||||
@ -580,6 +600,18 @@ public class HuaweiPacket {
|
|||||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||||
return this;
|
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:
|
default:
|
||||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||||
return this;
|
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
|
* The timestamp of the other marker, if it's larger this is the begin, otherwise the end
|
||||||
* - `source`
|
* - `source`
|
||||||
* The source of the data, which Huawei Band message the data came from
|
* 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 {
|
private static class RawTypes {
|
||||||
@ -155,7 +156,16 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
|||||||
Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
|
Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
|
||||||
Property activityTypeProperty = HuaweiActivitySampleDao.Properties.RawKind;
|
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);
|
return getLastFetchTimestamp(qb);
|
||||||
}
|
}
|
||||||
@ -365,7 +375,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
|||||||
private List<HuaweiActivitySample> interpolate(List<HuaweiActivitySample> processedSamples) {
|
private List<HuaweiActivitySample> interpolate(List<HuaweiActivitySample> processedSamples) {
|
||||||
List<HuaweiActivitySample> retv = new ArrayList<>();
|
List<HuaweiActivitySample> retv = new ArrayList<>();
|
||||||
|
|
||||||
if (processedSamples.size() == 0)
|
if (processedSamples.isEmpty())
|
||||||
return retv;
|
return retv;
|
||||||
|
|
||||||
HuaweiActivitySample lastSample = processedSamples.get(0);
|
HuaweiActivitySample lastSample = processedSamples.get(0);
|
||||||
@ -463,7 +473,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
|
|||||||
if (sample.getTimestamp() > sample.getOtherTimestamp())
|
if (sample.getTimestamp() > sample.getOtherTimestamp())
|
||||||
sample.setTimestamp(sample.getTimestamp() - 1);
|
sample.setTimestamp(sample.getTimestamp() - 1);
|
||||||
|
|
||||||
if (processedSamples.size() > 0)
|
if (!processedSamples.isEmpty())
|
||||||
lastSample = processedSamples.get(processedSamples.size() - 1);
|
lastSample = processedSamples.get(processedSamples.size() - 1);
|
||||||
if (lastSample != null && lastSample.getTimestamp() == sample.getTimestamp()) {
|
if (lastSample != null && lastSample.getTimestamp() == sample.getTimestamp()) {
|
||||||
// Merge the samples - only if there isn't any data yet, except the kind
|
// 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);
|
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)
|
if (isStartMarker)
|
||||||
state.sleepModifier = sample.getRawKind();
|
state.sleepModifier = sample.getRawKind();
|
||||||
else
|
else
|
||||||
|
@ -307,13 +307,17 @@ public class HuaweiTLV {
|
|||||||
.put(CryptoTags.cipherText, encryptedTLV);
|
.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[] key = paramsProvider.getSecretKey();
|
||||||
byte[] decryptedTLV = HuaweiCrypto.decrypt(
|
return HuaweiCrypto.decrypt(
|
||||||
paramsProvider.getEncryptMethod() == 0x01 || paramsProvider.getDeviceSupportType() == 0x04,
|
paramsProvider.getEncryptMethod() == 0x01 || paramsProvider.getDeviceSupportType() == 0x04,
|
||||||
getBytes(CryptoTags.cipherText),
|
getBytes(CryptoTags.cipherText),
|
||||||
key,
|
key,
|
||||||
getBytes(CryptoTags.initVector));
|
getBytes(CryptoTags.initVector));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decrypt(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
|
||||||
|
byte[] decryptedTLV = decryptRaw(paramsProvider);
|
||||||
this.valueMap = new ArrayList<>();
|
this.valueMap = new ArrayList<>();
|
||||||
parse(decryptedTLV);
|
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 byte[] selfAuthId;
|
||||||
private final String groupId;
|
private final String groupId;
|
||||||
private JSONObject version = null;
|
private JSONObject version = null;
|
||||||
private JSONObject payload = null;
|
private JSONObject jsonPayload = null;
|
||||||
private JSONObject value = null;
|
private JSONObject value = null;
|
||||||
|
|
||||||
public Request (int operationCode, long requestId, byte[] selfAuthId, String groupId) {
|
public Request (int operationCode, long requestId, byte[] selfAuthId, String groupId) {
|
||||||
@ -969,7 +969,7 @@ public class DeviceConfig {
|
|||||||
this.isEncrypted = false;
|
this.isEncrypted = false;
|
||||||
this.complete = true;
|
this.complete = true;
|
||||||
version = new JSONObject();
|
version = new JSONObject();
|
||||||
payload = new JSONObject();
|
jsonPayload = new JSONObject();
|
||||||
value = new JSONObject();
|
value = new JSONObject();
|
||||||
createJson(messageId);
|
createJson(messageId);
|
||||||
}
|
}
|
||||||
@ -986,14 +986,14 @@ public class DeviceConfig {
|
|||||||
super(paramsProvider, messageId);
|
super(paramsProvider, messageId);
|
||||||
// createJson(1); //messageId);
|
// createJson(1); //messageId);
|
||||||
try {
|
try {
|
||||||
payload
|
jsonPayload
|
||||||
.put("isoSalt", StringUtils.bytesToHex(isoSalt))
|
.put("isoSalt", StringUtils.bytesToHex(isoSalt))
|
||||||
.put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
|
.put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
|
||||||
.put("operationCode", operationCode)
|
.put("operationCode", operationCode)
|
||||||
.put("seed", StringUtils.bytesToHex(seed))
|
.put("seed", StringUtils.bytesToHex(seed))
|
||||||
.put("peerUserType", 0x00);
|
.put("peerUserType", 0x00);
|
||||||
if (operationCode == 0x02) {
|
if (operationCode == 0x02) {
|
||||||
payload
|
jsonPayload
|
||||||
.put("pkgName", "com.huawei.devicegroupmanage")
|
.put("pkgName", "com.huawei.devicegroupmanage")
|
||||||
.put("serviceType", groupId)
|
.put("serviceType", groupId)
|
||||||
.put("keyLength", 0x20);
|
.put("keyLength", 0x20);
|
||||||
@ -1020,7 +1020,7 @@ public class DeviceConfig {
|
|||||||
super(paramsProvider, messageId);
|
super(paramsProvider, messageId);
|
||||||
// createJson(2); //messageId);
|
// createJson(2); //messageId);
|
||||||
try {
|
try {
|
||||||
payload
|
jsonPayload
|
||||||
.put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
|
.put("peerAuthId", StringUtils.bytesToHex(selfAuthId))
|
||||||
.put("token", StringUtils.bytesToHex(token));
|
.put("token", StringUtils.bytesToHex(token));
|
||||||
if (operationCode == 0x02) value.put("isDeviceLevel", false);
|
if (operationCode == 0x02) value.put("isDeviceLevel", false);
|
||||||
@ -1044,7 +1044,7 @@ public class DeviceConfig {
|
|||||||
super(paramsProvider, messageId);
|
super(paramsProvider, messageId);
|
||||||
// createJson(3);
|
// createJson(3);
|
||||||
try {
|
try {
|
||||||
payload
|
jsonPayload
|
||||||
.put("nonce", StringUtils.bytesToHex(nonce))
|
.put("nonce", StringUtils.bytesToHex(nonce))
|
||||||
.put("encData", StringUtils.bytesToHex(encData));
|
.put("encData", StringUtils.bytesToHex(encData));
|
||||||
this.tlv = new HuaweiTLV()
|
this.tlv = new HuaweiTLV()
|
||||||
@ -1071,7 +1071,7 @@ public class DeviceConfig {
|
|||||||
// createJson(3);
|
// createJson(3);
|
||||||
// }
|
// }
|
||||||
try {
|
try {
|
||||||
payload
|
jsonPayload
|
||||||
.put("nonce", StringUtils.bytesToHex(nonce)) //generateRandom
|
.put("nonce", StringUtils.bytesToHex(nonce)) //generateRandom
|
||||||
.put("encResult", StringUtils.bytesToHex(encResult))
|
.put("encResult", StringUtils.bytesToHex(encResult))
|
||||||
.put("operationCode", operationCode);
|
.put("operationCode", operationCode);
|
||||||
@ -1093,11 +1093,11 @@ public class DeviceConfig {
|
|||||||
version
|
version
|
||||||
.put("minVersion", "1.0.0")
|
.put("minVersion", "1.0.0")
|
||||||
.put("currentVersion", "2.0.16");
|
.put("currentVersion", "2.0.16");
|
||||||
payload
|
jsonPayload
|
||||||
.put("version", version);
|
.put("version", version);
|
||||||
value
|
value
|
||||||
.put("authForm", 0x00)
|
.put("authForm", 0x00)
|
||||||
.put("payload", payload)
|
.put("payload", jsonPayload)
|
||||||
.put("groupAndModuleVersion", "2.0.1")
|
.put("groupAndModuleVersion", "2.0.1")
|
||||||
.put("message", messageId);
|
.put("message", messageId);
|
||||||
if (operationCode == 0x01) {
|
if (operationCode == 0x01) {
|
||||||
@ -1201,7 +1201,7 @@ public class DeviceConfig {
|
|||||||
public byte type;
|
public byte type;
|
||||||
|
|
||||||
public JSONObject value;
|
public JSONObject value;
|
||||||
public JSONObject payload;
|
public JSONObject jsonPayload;
|
||||||
|
|
||||||
public byte step;
|
public byte step;
|
||||||
// public int operationCode; // TODO
|
// public int operationCode; // TODO
|
||||||
@ -1225,18 +1225,18 @@ public class DeviceConfig {
|
|||||||
if (this.type == 0x00) {
|
if (this.type == 0x00) {
|
||||||
try {
|
try {
|
||||||
this.value = new JSONObject(this.tlv.getString(0x01));
|
this.value = new JSONObject(this.tlv.getString(0x01));
|
||||||
this.payload = value.getJSONObject("payload");
|
this.jsonPayload = value.getJSONObject("payload");
|
||||||
|
|
||||||
// Ugly, but should work
|
// Ugly, but should work
|
||||||
if (payload.has("isoSalt")) {
|
if (jsonPayload.has("isoSalt")) {
|
||||||
this.step = 0x01;
|
this.step = 0x01;
|
||||||
this.step1Data = new Step1Data(payload);
|
this.step1Data = new Step1Data(jsonPayload);
|
||||||
} else if (payload.has("returnCodeMac")) {
|
} else if (jsonPayload.has("returnCodeMac")) {
|
||||||
this.step = 0x02;
|
this.step = 0x02;
|
||||||
this.step2Data = new Step2Data(payload);
|
this.step2Data = new Step2Data(jsonPayload);
|
||||||
} else if (payload.has("encAuthToken")) {
|
} else if (jsonPayload.has("encAuthToken")) {
|
||||||
this.step = 0x03;
|
this.step = 0x03;
|
||||||
this.step3Data = new Step3Data(payload);
|
this.step3Data = new Step3Data(jsonPayload);
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new JsonException("", e);
|
throw new JsonException("", e);
|
||||||
@ -1344,7 +1344,7 @@ public class DeviceConfig {
|
|||||||
public long requestId;
|
public long requestId;
|
||||||
public byte[] selfAuthId;
|
public byte[] selfAuthId;
|
||||||
public String groupId;
|
public String groupId;
|
||||||
public JSONObject payload = null;
|
public JSONObject jsonPayload = null;
|
||||||
public JSONObject value = null;
|
public JSONObject value = null;
|
||||||
|
|
||||||
public Step1Data step1Data;
|
public Step1Data step1Data;
|
||||||
@ -1361,20 +1361,20 @@ public class DeviceConfig {
|
|||||||
public void parseTlv() throws ParseException {
|
public void parseTlv() throws ParseException {
|
||||||
try {
|
try {
|
||||||
value = new JSONObject(this.tlv.getString(0x01));
|
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.step = 1;
|
||||||
this.step1Data = new Step1Data(payload);
|
this.step1Data = new Step1Data(jsonPayload);
|
||||||
} else if (payload.has("token")) {
|
} else if (jsonPayload.has("token")) {
|
||||||
this.step = 2;
|
this.step = 2;
|
||||||
this.step2Data = new Step2Data(payload);
|
this.step2Data = new Step2Data(jsonPayload);
|
||||||
} else if (payload.has("encData")) {
|
} else if (jsonPayload.has("encData")) {
|
||||||
this.step = 3;
|
this.step = 3;
|
||||||
this.step3Data = new Step3Data(payload);
|
this.step3Data = new Step3Data(jsonPayload);
|
||||||
} else if (payload.has("encResult")) {
|
} else if (jsonPayload.has("encResult")) {
|
||||||
this.step = 4;
|
this.step = 4;
|
||||||
this.step4Data = new Step4Data(payload);
|
this.step4Data = new Step4Data(jsonPayload);
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
throw new JsonException("Cannot parse JSON", e);
|
throw new JsonException("Cannot parse JSON", e);
|
||||||
@ -1493,6 +1493,8 @@ public class DeviceConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Response extends HuaweiPacket {
|
public static class Response extends HuaweiPacket {
|
||||||
|
public boolean truSleepNewSync = false;
|
||||||
|
|
||||||
public Response(ParamsProvider paramsProvider) {
|
public Response(ParamsProvider paramsProvider) {
|
||||||
super(paramsProvider);
|
super(paramsProvider);
|
||||||
this.serviceId = DeviceConfig.id;
|
this.serviceId = DeviceConfig.id;
|
||||||
@ -1501,8 +1503,16 @@ public class DeviceConfig {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void parseTlv() throws ParseException {
|
public void parseTlv() throws ParseException {
|
||||||
|
// Works with bitmaps
|
||||||
|
|
||||||
// Tag 1 -> LegalStuff
|
// 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 3 -> SmartWatchVersion
|
||||||
// Tag 4 to 6 are HMS related
|
// 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();
|
supportProvider.dispose();
|
||||||
super.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();
|
supportProvider.dispose();
|
||||||
super.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.HuaweiCrypto;
|
||||||
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.packets.CameraRemote;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||||
@ -200,6 +201,8 @@ public class HuaweiSupportProvider {
|
|||||||
|
|
||||||
protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager(this);
|
protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager(this);
|
||||||
|
|
||||||
|
protected HuaweiFileDownloadManager huaweiFileDownloadManager = new HuaweiFileDownloadManager(this);
|
||||||
|
|
||||||
public HuaweiCoordinatorSupplier getCoordinator() {
|
public HuaweiCoordinatorSupplier getCoordinator() {
|
||||||
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
|
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
|
||||||
}
|
}
|
||||||
@ -1092,7 +1095,7 @@ public class HuaweiSupportProvider {
|
|||||||
private void fetchActivityData() {
|
private void fetchActivityData() {
|
||||||
int sleepStart = 0;
|
int sleepStart = 0;
|
||||||
int stepStart = 0;
|
int stepStart = 0;
|
||||||
int end = (int) (System.currentTimeMillis() / 1000);
|
final int end = (int) (System.currentTimeMillis() / 1000);
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
|
||||||
long prefLastSyncTime = sharedPreferences.getLong("lastSyncTimeMillis", 0);
|
long prefLastSyncTime = sharedPreferences.getLong("lastSyncTimeMillis", 0);
|
||||||
@ -1129,12 +1132,15 @@ public class HuaweiSupportProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final GetStepDataCountRequest getStepDataCountRequest = new GetStepDataCountRequest(this, stepStart, end);
|
final GetStepDataCountRequest getStepDataCountRequest = new GetStepDataCountRequest(this, stepStart, end);
|
||||||
|
//noinspection ExtractMethodRecommender
|
||||||
final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
|
final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
|
||||||
|
|
||||||
|
final int start = sleepStart;
|
||||||
getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
|
getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
handleSyncFinished();
|
if (!downloadTruSleepData(start, end))
|
||||||
|
handleSyncFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1455,18 +1461,20 @@ public class HuaweiSupportProvider {
|
|||||||
responseManager.addHandler(request);
|
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()) {
|
try (DBHandler db = GBApplication.acquireDB()) {
|
||||||
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
|
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
|
||||||
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
|
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
|
||||||
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
|
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
|
||||||
|
|
||||||
HuaweiActivitySample activitySample = new HuaweiActivitySample(
|
HuaweiActivitySample activitySample = new HuaweiActivitySample(
|
||||||
timestamp,
|
timestamp_start,
|
||||||
deviceId,
|
deviceId,
|
||||||
userId,
|
userId,
|
||||||
timestamp + duration,
|
timestamp_end,
|
||||||
FitnessData.MessageData.sleepId,
|
source,
|
||||||
type,
|
type,
|
||||||
1,
|
1,
|
||||||
ActivitySample.NOT_MEASURED,
|
ActivitySample.NOT_MEASURED,
|
||||||
@ -2071,5 +2079,78 @@ public class HuaweiSupportProvider {
|
|||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
stopBatteryRunnerDelayed();
|
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.
|
* Parses the data into a Huawei Packet.
|
||||||
* If the packet is complete, it will be handled by the first request that accepts it,
|
* If the packet is complete, it will be handled by the first request that accepts it,
|
||||||
@ -102,8 +112,10 @@ public class ResponseManager {
|
|||||||
} else {
|
} else {
|
||||||
LOG.debug("Service: " + Integer.toHexString(receivedPacket.serviceId & 0xff) + ", command: " + Integer.toHexString(receivedPacket.commandId & 0xff) + ", handled by: " + handler.getClass());
|
LOG.debug("Service: " + Integer.toHexString(receivedPacket.serviceId & 0xff) + ", command: " + Integer.toHexString(receivedPacket.commandId & 0xff) + ", handled by: " + handler.getClass());
|
||||||
|
|
||||||
synchronized (handlers) {
|
if (handler.autoRemoveFromResponseHandler()) {
|
||||||
handlers.remove(handler);
|
synchronized (handlers) {
|
||||||
|
handlers.remove(handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.handleResponse();
|
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
|
@Override
|
||||||
protected void processResponse() throws ResponseParseException {
|
protected void processResponse() throws ResponseParseException {
|
||||||
|
if (!(receivedPacket instanceof DeviceConfig.SettingRelated.Response))
|
||||||
|
throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.SettingRelated.Response.class);
|
||||||
|
|
||||||
LOG.debug("handle Setting Related");
|
LOG.debug("handle Setting Related");
|
||||||
|
|
||||||
|
supportProvider.getHuaweiCoordinator().setSupportsTruSleepNewSync(((DeviceConfig.SettingRelated.Response) receivedPacket).truSleepNewSync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ public class GetSleepDataRequest extends Request {
|
|||||||
(timestampInts[5]);
|
(timestampInts[5]);
|
||||||
short duration = (short) (durationInt * 60);
|
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) {
|
if (count + 1 < maxCount) {
|
||||||
|
@ -79,6 +79,10 @@ public class Request {
|
|||||||
public ResponseTypeMismatchException(HuaweiPacket a, Class<?> b) {
|
public ResponseTypeMismatchException(HuaweiPacket a, Class<?> b) {
|
||||||
super("Response type mismatch, packet is of type " + a.getClass() + " but expected " + 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 {
|
public static class WorkoutParseException extends ResponseParseException {
|
||||||
@ -111,10 +115,13 @@ public class Request {
|
|||||||
public RequestCallback(HuaweiSupportProvider supportProvider) {
|
public RequestCallback(HuaweiSupportProvider supportProvider) {
|
||||||
support = 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) {
|
public void handleException(ResponseParseException e) {
|
||||||
LOG.error("Callback request exception", e);
|
LOG.error("Callback request exception", e);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Request(HuaweiSupportProvider supportProvider, nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
|
public Request(HuaweiSupportProvider supportProvider, nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
|
||||||
@ -213,7 +220,7 @@ public class Request {
|
|||||||
if (nextRequest == null || stopChain) {
|
if (nextRequest == null || stopChain) {
|
||||||
operationStatus = OperationStatus.FINISHED;
|
operationStatus = OperationStatus.FINISHED;
|
||||||
if (finalizeReq != null) {
|
if (finalizeReq != null) {
|
||||||
finalizeReq.call();
|
finalizeReq.call(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,4 +307,8 @@ public class Request {
|
|||||||
this.supportProvider.performConnected(transaction);
|
this.supportProvider.performConnected(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean autoRemoveFromResponseHandler() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ public class SetTruSleepRequest extends Request {
|
|||||||
@Override
|
@Override
|
||||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||||
boolean truSleepSwitch = GBApplication
|
boolean truSleepSwitch = GBApplication
|
||||||
.getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
|
.getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
|
||||||
.getBoolean(HuaweiConstants.PREF_HUAWEI_TRUSLEEP, false);
|
.getBoolean(HuaweiConstants.PREF_HUAWEI_TRUSLEEP, false);
|
||||||
try {
|
try {
|
||||||
return new FitnessData.TruSleep.Request(paramsProvider, truSleepSwitch).serialize();
|
return new FitnessData.TruSleep.Request(paramsProvider, truSleepSwitch).serialize();
|
||||||
} catch (HuaweiPacket.CryptoException e) {
|
} catch (HuaweiPacket.CryptoException e) {
|
||||||
|
@ -2468,7 +2468,7 @@
|
|||||||
<string name="huawei_trusleep_title">HUAWEI TruSleep ™</string>
|
<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">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_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="prefs_activity_recognition">Activity recognition settings</string>
|
||||||
<string name="pref_activity_recognize_running">recognize running</string>
|
<string name="pref_activity_recognize_running">recognize running</string>
|
||||||
<string name="pref_activity_recognize_biking">recognize biking</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
|
@Override
|
||||||
public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
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.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
|
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;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
@ -151,11 +153,6 @@ public class TestResponseManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addSleepActivity(int timestamp, short duration, byte type) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
|
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));
|
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
|
@Test
|
||||||
public void testHandleDataCompletePacketSynchronous() throws Exception {
|
public void testHandleDataCompletePacketSynchronous() throws Exception {
|
||||||
// Note that this is not a proper packet, but that doesn't matter as we're not testing
|
// 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);
|
Request request1 = Mockito.mock(Request.class);
|
||||||
when(request1.handleResponse((HuaweiPacket) any()))
|
when(request1.handleResponse((HuaweiPacket) any()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
|
when(request1.autoRemoveFromResponseHandler())
|
||||||
|
.thenReturn(true);
|
||||||
Request request2 = Mockito.mock(Request.class);
|
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>());
|
List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
|
||||||
inputHandlers.add(request1);
|
inputHandlers.add(request1);
|
||||||
@ -342,10 +362,9 @@ public class TestResponseManager {
|
|||||||
Request request1 = Mockito.mock(Request.class);
|
Request request1 = Mockito.mock(Request.class);
|
||||||
when(request1.handleResponse((HuaweiPacket) any()))
|
when(request1.handleResponse((HuaweiPacket) any()))
|
||||||
.thenReturn(true);
|
.thenReturn(true);
|
||||||
|
when(request1.autoRemoveFromResponseHandler())
|
||||||
|
.thenReturn(true);
|
||||||
Request request2 = Mockito.mock(Request.class);
|
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>());
|
List<Request> inputHandlers = Collections.synchronizedList(new ArrayList<Request>());
|
||||||
inputHandlers.add(request1);
|
inputHandlers.add(request1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user