mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 20:36:51 +01:00
Huawei: Initial P2P service support, Calendar sync support.
This commit is contained in:
parent
ae3615a388
commit
f3aaeb5216
@ -173,6 +173,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
|
||||
return huaweiCoordinator.getContactsSlotCount(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return huaweiCoordinator.supportsCalendarEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
|
@ -88,12 +88,10 @@ public class HuaweiCoordinator {
|
||||
)));
|
||||
if (key.equals("maxContactsCount"))
|
||||
this.maxContactsCount = getCapabilitiesSharedPreferences().getInt(key, 0);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SharedPreferences getCapabilitiesSharedPreferences() {
|
||||
return GBApplication.getContext().getSharedPreferences("huawei_coordinator_capatilities" + parent.getDeviceType().name(), Context.MODE_PRIVATE);
|
||||
}
|
||||
@ -257,6 +255,11 @@ public class HuaweiCoordinator {
|
||||
dateTime.add(R.xml.devicesettings_timeformat);
|
||||
}
|
||||
|
||||
//Calendar
|
||||
if( supportsP2PService() && supportsCalendar()) {
|
||||
deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR, R.xml.devicesettings_sync_calendar);
|
||||
}
|
||||
|
||||
// Display
|
||||
if (supportsWearLocation(device))
|
||||
deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY, R.xml.devicesettings_wearlocation);
|
||||
@ -324,6 +327,10 @@ public class HuaweiCoordinator {
|
||||
return supportsCommandForService(0x03, 0x1);
|
||||
}
|
||||
|
||||
public boolean supportsCalendarEvents() {
|
||||
return supportsP2PService() && supportsCalendar();
|
||||
}
|
||||
|
||||
public boolean supportsAcceptAgreement() {
|
||||
return supportsCommandForService(0x01, 0x30);
|
||||
}
|
||||
@ -520,6 +527,22 @@ public class HuaweiCoordinator {
|
||||
return supportsCommandForService(0x32, 0x01);
|
||||
}
|
||||
|
||||
public boolean supportsP2PService() {
|
||||
return supportsCommandForService(0x34, 0x1);
|
||||
}
|
||||
|
||||
public boolean supportsExternalCalendarService() {
|
||||
if (supportsExpandCapability())
|
||||
return supportsExpandCapability(184);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsCalendar() {
|
||||
if (supportsExpandCapability())
|
||||
return supportsExpandCapability(171) || supportsExpandCapability(184);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsMultiDevice() {
|
||||
if (supportsExpandCapability())
|
||||
return supportsExpandCapability(109);
|
||||
|
@ -182,6 +182,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
|
||||
return huaweiCoordinator.getContactsSlotCount(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return huaweiCoordinator.supportsCalendarEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
|
@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Contacts;
|
||||
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.P2P;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
|
||||
@ -231,6 +232,9 @@ public class HuaweiPacket {
|
||||
}
|
||||
|
||||
public static class SerializeException extends Exception {
|
||||
public SerializeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public SerializeException(String message, Exception e) {
|
||||
super(message, e);
|
||||
}
|
||||
@ -652,6 +656,14 @@ public class HuaweiPacket {
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
}
|
||||
case P2P.id:
|
||||
switch (this.commandId) {
|
||||
case P2P.P2PCommand.id:
|
||||
return new P2P.P2PCommand.Response(paramsProvider).fromPacket(this);
|
||||
default:
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
}
|
||||
default:
|
||||
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
|
||||
return this;
|
||||
@ -777,14 +789,14 @@ public class HuaweiPacket {
|
||||
return retv;
|
||||
}
|
||||
|
||||
public List<byte[]> serializeFileChunk(byte[] fileChunk, int uploadPosition, short unitSize, byte fileId) {
|
||||
public List<byte[]> serializeFileChunk(byte[] fileChunk, int uploadPosition, short unitSize, byte fileId, boolean isEncrypted) throws SerializeException {
|
||||
List<byte[]> retv = new ArrayList<>();
|
||||
int headerLength = 5; // Magic + (short)(bodyLength + 1) + 0x00
|
||||
int sliceHeaderLenght =7;
|
||||
int sliceHeaderLength = 6;
|
||||
|
||||
int footerLength = 2; //CRC16
|
||||
|
||||
int packetCount = (int) Math.ceil(((double) fileChunk.length ) / (double) unitSize);
|
||||
int packetCount = (int) Math.ceil(((double) fileChunk.length) / (double) unitSize);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(fileChunk);
|
||||
|
||||
@ -793,7 +805,34 @@ public class HuaweiPacket {
|
||||
for (int i = 0; i < packetCount; i++) {
|
||||
|
||||
short contentSize = (short) Math.min(unitSize, buffer.remaining());
|
||||
short packetSize = (short)(contentSize + headerLength + sliceHeaderLenght + footerLength);
|
||||
|
||||
ByteBuffer payload = ByteBuffer.allocate(contentSize + sliceHeaderLength);
|
||||
payload.put(fileId); // Slice
|
||||
payload.put((byte)i); // Flag
|
||||
payload.putInt(sliceStart);
|
||||
|
||||
byte[] packetContent = new byte[contentSize];
|
||||
buffer.get(packetContent);
|
||||
payload.put(packetContent); // Packet databyte[] packetContent = new byte[contentSize];
|
||||
|
||||
|
||||
byte[] new_payload = null;
|
||||
if(isEncrypted) {
|
||||
try {
|
||||
HuaweiTLV encryptedTlv = HuaweiTLV.encryptRaw(this.paramsProvider, payload.array());
|
||||
new_payload = encryptedTlv.serialize();
|
||||
} catch (HuaweiCrypto.CryptoException e) {
|
||||
throw new HuaweiPacket.SerializeException("Error to encrypt TLV");
|
||||
}
|
||||
} else {
|
||||
new_payload = payload.array();
|
||||
}
|
||||
|
||||
if (new_payload == null) {
|
||||
throw new HuaweiPacket.SerializeException("new payload is null");
|
||||
}
|
||||
|
||||
short packetSize = (short)(new_payload.length + sliceHeaderLength + footerLength);
|
||||
ByteBuffer packet = ByteBuffer.allocate(packetSize);
|
||||
|
||||
int start = packet.position();
|
||||
@ -804,18 +843,11 @@ public class HuaweiPacket {
|
||||
packet.put(this.serviceId);
|
||||
packet.put(this.commandId);
|
||||
|
||||
packet.put(fileId); // Slice
|
||||
packet.put((byte)i); // Flag
|
||||
packet.putInt(sliceStart);
|
||||
|
||||
byte[] packetContent = new byte[contentSize];
|
||||
buffer.get(packetContent);
|
||||
packet.put(packetContent); // Packet databyte[] packetContent = new byte[contentSize];
|
||||
packet.put(new_payload);
|
||||
|
||||
int length = packet.position() - start;
|
||||
if (length != packetSize - footerLength) {
|
||||
// TODO: exception?
|
||||
LOG.error(String.format(GBApplication.getLanguage(), "Packet lengths don't match! %d != %d", length, packetSize + headerLength));
|
||||
throw new HuaweiPacket.SerializeException(String.format(GBApplication.getLanguage(), "Packet lengths don't match! %d != %d", length, packetSize + headerLength));
|
||||
}
|
||||
|
||||
byte[] complete = new byte[length];
|
||||
|
@ -300,13 +300,12 @@ public class HuaweiTLV {
|
||||
return msg.substring(0, msg.length() - 3);
|
||||
}
|
||||
|
||||
public HuaweiTLV encrypt(ParamsProvider paramsProvider) throws CryptoException {
|
||||
byte[] serializedTLV = serialize();
|
||||
public static HuaweiTLV encryptRaw(ParamsProvider paramsProvider, byte[] data) throws CryptoException {
|
||||
byte[] key = paramsProvider.getSecretKey();
|
||||
byte[] nonce = paramsProvider.getIv();
|
||||
byte[] encryptedTLV = HuaweiCrypto.encrypt(
|
||||
paramsProvider.getEncryptMethod() == 0x01 || paramsProvider.getDeviceSupportType() == 0x04,
|
||||
serializedTLV,
|
||||
data,
|
||||
key,
|
||||
nonce);
|
||||
return new HuaweiTLV()
|
||||
@ -315,6 +314,11 @@ public class HuaweiTLV {
|
||||
.put(CryptoTags.cipherText, encryptedTLV);
|
||||
}
|
||||
|
||||
public HuaweiTLV encrypt(ParamsProvider paramsProvider) throws CryptoException {
|
||||
byte[] serializedTLV = serialize();
|
||||
return encryptRaw(paramsProvider, serializedTLV);
|
||||
}
|
||||
|
||||
public byte[] decryptRaw(ParamsProvider paramsProvider) throws CryptoException, HuaweiPacket.MissingTagException {
|
||||
byte[] key = paramsProvider.getSecretKey();
|
||||
return HuaweiCrypto.decrypt(
|
||||
|
@ -30,8 +30,8 @@ public class FileUpload {
|
||||
public byte bitmap_enable = 0;
|
||||
public short unit_size = 0;
|
||||
public int max_apply_data_size = 0;
|
||||
public short interval =0;
|
||||
public int received_file_size =0;
|
||||
public short interval = 0;
|
||||
public int received_file_size = 0;
|
||||
public byte no_encrypt = 0;
|
||||
}
|
||||
|
||||
@ -46,12 +46,18 @@ public class FileUpload {
|
||||
|
||||
public static class FileInfoSend {
|
||||
public static final byte id = 0x02;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
|
||||
public Request(ParamsProvider paramsProvider,
|
||||
int fileSize,
|
||||
String fileName,
|
||||
byte fileType) {
|
||||
int fileSize,
|
||||
String fileName,
|
||||
byte fileType,
|
||||
String srcPackage,
|
||||
String dstPackage,
|
||||
String srcFingerprint,
|
||||
String dstFingerprint
|
||||
) {
|
||||
super(paramsProvider);
|
||||
this.serviceId = FileUpload.id;
|
||||
this.commandId = id;
|
||||
@ -68,15 +74,26 @@ public class FileUpload {
|
||||
.put(0x06, watchfaceVersion);
|
||||
}
|
||||
|
||||
if (srcPackage != null && dstPackage != null) {
|
||||
this.tlv.put(0x08, srcPackage)
|
||||
.put(0x09, dstPackage)
|
||||
.put(0x0a)
|
||||
.put(0x0b, srcFingerprint)
|
||||
.put(0x0c, dstFingerprint);
|
||||
}
|
||||
|
||||
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public int result = 0;
|
||||
public Response (ParamsProvider paramsProvider) {
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws HuaweiPacket.ParseException {
|
||||
this.result = this.tlv.getInteger(0x7f);
|
||||
@ -90,8 +107,8 @@ public class FileUpload {
|
||||
public static class Request extends HuaweiPacket {
|
||||
|
||||
public Request(ParamsProvider paramsProvider,
|
||||
byte[] hash,
|
||||
byte fileId) {
|
||||
byte[] hash,
|
||||
byte fileId) {
|
||||
super(paramsProvider);
|
||||
this.serviceId = FileUpload.id;
|
||||
this.commandId = id;
|
||||
@ -104,8 +121,9 @@ public class FileUpload {
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public byte fileId =0;
|
||||
public Response (ParamsProvider paramsProvider) {
|
||||
public byte fileId = 0;
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@ -119,6 +137,7 @@ public class FileUpload {
|
||||
|
||||
public static class FileUploadConsultAck {
|
||||
public static final byte id = 0x04;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
public Request(ParamsProvider paramsProvider, byte noEncryption, byte fileId) {
|
||||
super(paramsProvider);
|
||||
@ -128,7 +147,7 @@ public class FileUpload {
|
||||
.put(0x7f, 0x000186A0) //ok
|
||||
.put(0x01, fileId);
|
||||
if (noEncryption == 1)
|
||||
this.tlv.put(0x09, (byte)0x01); // need on devices which generally encrypted, but files
|
||||
this.tlv.put(0x09, (byte) 0x01); // need on devices which generally encrypted, but files
|
||||
this.complete = true;
|
||||
}
|
||||
}
|
||||
@ -136,22 +155,23 @@ public class FileUpload {
|
||||
public static class Response extends HuaweiPacket {
|
||||
|
||||
public FileUploadParams fileUploadParams = new FileUploadParams();
|
||||
public Response (ParamsProvider paramsProvider) {
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws HuaweiPacket.ParseException {
|
||||
this.fileUploadParams.file_id = this.tlv.getByte(0x01);
|
||||
this.fileUploadParams.protocolVersion = this.tlv.getString(0x02);
|
||||
this.fileUploadParams.app_wait_time = this.tlv.getShort(0x03);
|
||||
this.fileUploadParams.bitmap_enable = this.tlv.getByte(0x04);
|
||||
this.fileUploadParams.unit_size = this.tlv.getShort(0x05);
|
||||
this.fileUploadParams.max_apply_data_size = this.tlv.getInteger(0x06);
|
||||
this.fileUploadParams.interval = this.tlv.getShort(0x07);
|
||||
this.fileUploadParams.received_file_size = this.tlv.getInteger(0x08);
|
||||
if (this.tlv.contains(0x09)) // optional for older devices
|
||||
this.fileUploadParams.no_encrypt = this.tlv.getByte(0x09);
|
||||
this.fileUploadParams.file_id = this.tlv.getByte(0x01);
|
||||
this.fileUploadParams.protocolVersion = this.tlv.getString(0x02);
|
||||
this.fileUploadParams.app_wait_time = this.tlv.getShort(0x03);
|
||||
this.fileUploadParams.bitmap_enable = this.tlv.getByte(0x04);
|
||||
this.fileUploadParams.unit_size = this.tlv.getShort(0x05);
|
||||
this.fileUploadParams.max_apply_data_size = this.tlv.getInteger(0x06);
|
||||
this.fileUploadParams.interval = this.tlv.getShort(0x07);
|
||||
this.fileUploadParams.received_file_size = this.tlv.getInteger(0x08);
|
||||
if (this.tlv.contains(0x09)) // optional for older devices
|
||||
this.fileUploadParams.no_encrypt = this.tlv.getByte(0x09);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -161,12 +181,14 @@ public class FileUpload {
|
||||
|
||||
public int bytesUploaded = 0;
|
||||
public int nextchunkSize = 0;
|
||||
|
||||
public FileNextChunkParams(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
this.serviceId = FileUpload.id;
|
||||
this.commandId = id;
|
||||
this.complete = true;
|
||||
super(paramsProvider);
|
||||
this.serviceId = FileUpload.id;
|
||||
this.commandId = id;
|
||||
this.complete = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws HuaweiPacket.ParseException {
|
||||
this.bytesUploaded = this.tlv.getInteger(0x02);
|
||||
@ -203,7 +225,8 @@ public class FileUpload {
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
byte status = 0;
|
||||
public Response (ParamsProvider paramsProvider) {
|
||||
|
||||
public Response(ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,80 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||
|
||||
public class P2P {
|
||||
public static final byte id = 0x34;
|
||||
|
||||
public static class P2PCommand {
|
||||
public static final byte id = 0x01;
|
||||
|
||||
public static class Request extends HuaweiPacket {
|
||||
|
||||
|
||||
public Request(ParamsProvider paramsProvider,
|
||||
byte cmdId,
|
||||
short sequenceId,
|
||||
String srcPackage,
|
||||
String dstPackage,
|
||||
String srcFingerprint,
|
||||
String dstFingerprint,
|
||||
byte[] sendData,
|
||||
int sendCode) {
|
||||
super(paramsProvider);
|
||||
this.serviceId = P2P.id;
|
||||
this.commandId = id;
|
||||
|
||||
this.tlv = new HuaweiTLV()
|
||||
.put(0x01, cmdId)
|
||||
.put(0x02, sequenceId)
|
||||
.put(0x03, srcPackage)
|
||||
.put(0x04, dstPackage);
|
||||
if(cmdId == 0x2) {
|
||||
this.tlv.put(0x05, srcFingerprint);
|
||||
this.tlv.put(0x06, dstFingerprint);
|
||||
}
|
||||
if(sendData != null && sendData.length > 0)
|
||||
this.tlv.put(0x07, sendData);
|
||||
if(cmdId == 0x3) {
|
||||
this.tlv.put(0x08, sendCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Response extends HuaweiPacket {
|
||||
public byte cmdId;
|
||||
public short sequenceId;
|
||||
public String srcPackage;
|
||||
public String dstPackage;
|
||||
public String srcFingerprint = null;
|
||||
public String dstFingerprint = null;
|
||||
public byte[] respData = null;
|
||||
public int respCode = 0;
|
||||
public Response (ParamsProvider paramsProvider) {
|
||||
super(paramsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTlv() throws HuaweiPacket.ParseException {
|
||||
cmdId = this.tlv.getByte(0x01);
|
||||
sequenceId = this.tlv.getShort(0x02);
|
||||
srcPackage = this.tlv.getString(0x03);
|
||||
dstPackage = this.tlv.getString(0x04);
|
||||
if(this.tlv.contains(0x05))
|
||||
srcFingerprint = this.tlv.getString(0x05);
|
||||
if(this.tlv.contains(0x06))
|
||||
dstFingerprint = this.tlv.getString(0x06);
|
||||
if(this.tlv.contains(0x07))
|
||||
respData = this.tlv.getBytes(0x07);
|
||||
// NOTE: P2P service uses different data types in responseCode(TLV 0x08).
|
||||
// It sends byte from wearable but send integer to wearable
|
||||
// So we have a change that other device can send different type.
|
||||
if(this.tlv.contains(0x08))
|
||||
respCode = this.tlv.getByte(0x08);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -56,6 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
@ -121,6 +122,7 @@ public class AsynchronousResponse {
|
||||
handleWatchface(response);
|
||||
handleCameraRemote(response);
|
||||
handleApp(response);
|
||||
handleP2p(response);
|
||||
} catch (Request.ResponseParseException e) {
|
||||
LOG.error("Response parse exception", e);
|
||||
}
|
||||
@ -423,64 +425,91 @@ public class AsynchronousResponse {
|
||||
if (response.commandId == FileUpload.FileInfoSend.id) {
|
||||
if (!(response instanceof FileUpload.FileInfoSend.Response))
|
||||
throw new Request.ResponseTypeMismatchException(response, FileUpload.FileInfoSend.Response.class);
|
||||
FileUpload.FileInfoSend.Response resp = (FileUpload.FileInfoSend.Response) response;
|
||||
if (resp.result == 140004) {
|
||||
LOG.error("Too many watchfaces installed");
|
||||
support.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(support.getContext().getString(R.string.cannot_upload_watchface_too_many_watchfaces_installed), Toast.LENGTH_LONG, GB.ERROR));
|
||||
} else if (resp.result == 140009) {
|
||||
LOG.error("Insufficient space for upload");
|
||||
support.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(support.getContext().getString(R.string.insufficient_space_for_upload), Toast.LENGTH_LONG, GB.ERROR));
|
||||
if(support.huaweiUploadManager.getFileUploadInfo() == null) {
|
||||
LOG.error("Upload file info received but no file to upload");
|
||||
} else {
|
||||
FileUpload.FileInfoSend.Response resp = (FileUpload.FileInfoSend.Response) response;
|
||||
if (resp.result != 100000) {
|
||||
if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
|
||||
support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onError(resp.result);
|
||||
} else {
|
||||
LOG.error("Upload file info error without callback: {}", resp.result);
|
||||
}
|
||||
//Cleanup
|
||||
support.huaweiUploadManager.setFileUploadInfo(null);
|
||||
}
|
||||
}
|
||||
} else if (response.commandId == FileUpload.FileHashSend.id) {
|
||||
if (!(response instanceof FileUpload.FileHashSend.Response))
|
||||
throw new Request.ResponseTypeMismatchException(response, FileUpload.FileHashSend.Response.class);
|
||||
FileUpload.FileHashSend.Response resp = (FileUpload.FileHashSend.Response) response;
|
||||
support.huaweiUploadManager.setFileId(resp.fileId);
|
||||
try {
|
||||
SendFileUploadHash sendFileUploadHash = new SendFileUploadHash(support, support.huaweiUploadManager);
|
||||
sendFileUploadHash.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send fileupload hash request", e);
|
||||
if(support.huaweiUploadManager.getFileUploadInfo() == null) {
|
||||
LOG.error("Upload file hash requested but no file to upload");
|
||||
} else {
|
||||
FileUpload.FileHashSend.Response resp = (FileUpload.FileHashSend.Response) response;
|
||||
support.huaweiUploadManager.getFileUploadInfo().setFileId(resp.fileId);
|
||||
try {
|
||||
SendFileUploadHash sendFileUploadHash = new SendFileUploadHash(support, support.huaweiUploadManager);
|
||||
sendFileUploadHash.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send file upload hash request", e);
|
||||
}
|
||||
}
|
||||
} else if (response.commandId == FileUpload.FileUploadConsultAck.id) {
|
||||
if (!(response instanceof FileUpload.FileUploadConsultAck.Response))
|
||||
throw new Request.ResponseTypeMismatchException(response, FileUpload.FileUploadConsultAck.Response.class);
|
||||
FileUpload.FileUploadConsultAck.Response resp = (FileUpload.FileUploadConsultAck.Response) response;
|
||||
|
||||
support.huaweiUploadManager.setFileUploadParams(resp.fileUploadParams);
|
||||
|
||||
try {
|
||||
support.huaweiUploadManager.setDeviceBusy();
|
||||
SendFileUploadAck sendFileUploadAck = new SendFileUploadAck(support,
|
||||
resp.fileUploadParams.no_encrypt, support.huaweiUploadManager.getFileId());
|
||||
sendFileUploadAck.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send fileupload ack request", e);
|
||||
}
|
||||
if(support.huaweiUploadManager.getFileUploadInfo() == null) {
|
||||
LOG.error("Upload file ask requested but no file to upload");
|
||||
} else {
|
||||
FileUpload.FileUploadConsultAck.Response resp = (FileUpload.FileUploadConsultAck.Response) response;
|
||||
support.huaweiUploadManager.getFileUploadInfo().setFileUploadParams(resp.fileUploadParams);
|
||||
try {
|
||||
if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
|
||||
support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onUploadStart();
|
||||
}
|
||||
SendFileUploadAck sendFileUploadAck = new SendFileUploadAck(support,
|
||||
resp.fileUploadParams.no_encrypt, support.huaweiUploadManager.getFileUploadInfo().getFileId());
|
||||
sendFileUploadAck.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send file upload ack request", e);
|
||||
}
|
||||
}
|
||||
} else if (response.commandId == FileUpload.FileNextChunkParams.id) {
|
||||
if (!(response instanceof FileUpload.FileNextChunkParams))
|
||||
throw new Request.ResponseTypeMismatchException(response, FileUpload.FileNextChunkParams.class);
|
||||
FileUpload.FileNextChunkParams resp = (FileUpload.FileNextChunkParams) response;
|
||||
support.huaweiUploadManager.setUploadChunkSize(resp.nextchunkSize);
|
||||
support.huaweiUploadManager.setCurrentUploadPosition(resp.bytesUploaded);
|
||||
int progress = Math.round(((float)resp.bytesUploaded / (float)support.huaweiUploadManager.getFileSize())* 100);
|
||||
support.onUploadProgress(R.string.updatefirmwareoperation_update_in_progress, progress, true);
|
||||
|
||||
try {
|
||||
SendFileUploadChunk sendFileUploadChunk = new SendFileUploadChunk(support, support.huaweiUploadManager);
|
||||
sendFileUploadChunk.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send fileupload next chunk request", e);
|
||||
}
|
||||
if(support.huaweiUploadManager.getFileUploadInfo() == null) {
|
||||
LOG.error("Upload file next chunk requested but no file to upload");
|
||||
} else {
|
||||
FileUpload.FileNextChunkParams resp = (FileUpload.FileNextChunkParams) response;
|
||||
support.huaweiUploadManager.getFileUploadInfo().setUploadChunkSize(resp.nextchunkSize);
|
||||
support.huaweiUploadManager.getFileUploadInfo().setCurrentUploadPosition(resp.bytesUploaded);
|
||||
int progress = Math.round(((float) resp.bytesUploaded / (float) support.huaweiUploadManager.getFileUploadInfo().getFileSize()) * 100);
|
||||
try {
|
||||
if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
|
||||
support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onUploadProgress(progress);
|
||||
}
|
||||
SendFileUploadChunk sendFileUploadChunk = new SendFileUploadChunk(support, support.huaweiUploadManager);
|
||||
sendFileUploadChunk.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send fileupload next chunk request", e);
|
||||
}
|
||||
}
|
||||
} else if (response.commandId == FileUpload.FileUploadResult.id) {
|
||||
try {
|
||||
support.huaweiUploadManager.unsetDeviceBusy();
|
||||
support.onUploadProgress(R.string.updatefirmwareoperation_update_complete, 100, false);
|
||||
SendFileUploadComplete sendFileUploadComplete = new SendFileUploadComplete(this.support, support.huaweiUploadManager.getFileId());
|
||||
sendFileUploadComplete.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send fileupload result request", e);
|
||||
}
|
||||
if(support.huaweiUploadManager.getFileUploadInfo() == null) {
|
||||
LOG.error("Upload file result requested but no file to upload");
|
||||
} else {
|
||||
try {
|
||||
byte fileId = support.huaweiUploadManager.getFileUploadInfo().getFileId();
|
||||
if (support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback() != null) {
|
||||
support.huaweiUploadManager.getFileUploadInfo().getFileUploadCallback().onUploadComplete();
|
||||
}
|
||||
//Cleanup
|
||||
support.huaweiUploadManager.setFileUploadInfo(null);
|
||||
SendFileUploadComplete sendFileUploadComplete = new SendFileUploadComplete(this.support, fileId);
|
||||
sendFileUploadComplete.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Could not send file upload result request", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -523,6 +552,18 @@ public class AsynchronousResponse {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleP2p(HuaweiPacket response) throws Request.ResponseParseException {
|
||||
if (response.serviceId == P2P.id && response.commandId == P2P.P2PCommand.id) {
|
||||
if (!(response instanceof P2P.P2PCommand.Response))
|
||||
throw new Request.ResponseTypeMismatchException(response, P2P.P2PCommand.class);
|
||||
try {
|
||||
this.support.getHuaweiP2PManager().handlePacket((P2P.P2PCommand.Response) response);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error in P2P service", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWeatherCheck(HuaweiPacket response) {
|
||||
if (response.serviceId == Weather.id && response.commandId == 0x04) {
|
||||
support.huaweiWeatherManager.handleAsyncMessage(response);
|
||||
|
@ -30,6 +30,7 @@ import java.util.UUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
@ -177,6 +178,17 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
|
||||
supportProvider.onSetContacts(contacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
|
||||
supportProvider.onAddCalendarEvent(calendarEventSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(final byte type, long id) {
|
||||
supportProvider.onDeleteCalendarEvent(type, id);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
supportProvider.dispose();
|
||||
|
@ -33,6 +33,7 @@ import java.util.UUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
@ -184,6 +185,16 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
|
||||
supportProvider.onSetContacts(contacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
|
||||
supportProvider.onAddCalendarEvent(calendarEventSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(final byte type, long id) {
|
||||
supportProvider.onDeleteCalendarEvent(type, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
supportProvider.dispose();
|
||||
|
@ -0,0 +1,71 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiBaseP2PService;
|
||||
|
||||
public class HuaweiP2PManager {
|
||||
private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PManager.class);
|
||||
|
||||
private final HuaweiSupportProvider support;
|
||||
|
||||
private final List<HuaweiBaseP2PService> registeredServices;
|
||||
|
||||
private Short sequence = 1;
|
||||
|
||||
public synchronized Short getNextSequence() {
|
||||
return sequence++;
|
||||
}
|
||||
|
||||
public HuaweiP2PManager(HuaweiSupportProvider support) {
|
||||
this.support = support;
|
||||
this.registeredServices = new ArrayList<>();
|
||||
}
|
||||
|
||||
public HuaweiSupportProvider getSupportProvider() {
|
||||
return support;
|
||||
}
|
||||
|
||||
public void registerService(HuaweiBaseP2PService service) {
|
||||
for (HuaweiBaseP2PService svr : registeredServices) {
|
||||
if (svr.getModule().equals(service.getModule())) {
|
||||
LOG.error("P2P Service already registered, unregister: {}", service.getModule());
|
||||
svr.unregister();
|
||||
registeredServices.remove(svr);
|
||||
}
|
||||
}
|
||||
registeredServices.add(service);
|
||||
service.registered();
|
||||
}
|
||||
|
||||
public HuaweiBaseP2PService getRegisteredService(String module) {
|
||||
for (HuaweiBaseP2PService svr : registeredServices) {
|
||||
if (svr.getModule().equals(module)) {
|
||||
return svr;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void unregisterAllService() {
|
||||
for (HuaweiBaseP2PService svr : registeredServices) {
|
||||
svr.unregister();
|
||||
}
|
||||
registeredServices.clear();
|
||||
}
|
||||
|
||||
|
||||
public void handlePacket(P2P.P2PCommand.Response packet) {
|
||||
LOG.info("P2P Service message: Src: {} Dst: {} Seq: {}", packet.srcPackage, packet.dstPackage, packet.sequenceId);
|
||||
for (HuaweiBaseP2PService service : registeredServices) {
|
||||
if (service.getPackage().equals(packet.srcPackage)) {
|
||||
service.handlePacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
@ -90,6 +91,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p.HuaweiP2PCalendarService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAppInfoParams;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetContactsCount;
|
||||
@ -211,6 +213,9 @@ public class HuaweiSupportProvider {
|
||||
|
||||
protected HuaweiWeatherManager huaweiWeatherManager = new HuaweiWeatherManager(this);
|
||||
|
||||
//TODO: we need only one instance of manager and all it services.
|
||||
protected HuaweiP2PManager huaweiP2PManager = new HuaweiP2PManager(this);
|
||||
|
||||
public HuaweiCoordinatorSupplier getCoordinator() {
|
||||
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
|
||||
}
|
||||
@ -218,6 +223,11 @@ public class HuaweiSupportProvider {
|
||||
public HuaweiCoordinator getHuaweiCoordinator() {
|
||||
return getCoordinator().getHuaweiCoordinator();
|
||||
}
|
||||
|
||||
public HuaweiUploadManager getUploadManager() {
|
||||
return huaweiUploadManager;
|
||||
}
|
||||
|
||||
public HuaweiWatchfaceManager getHuaweiWatchfaceManager() {
|
||||
return huaweiWatchfaceManager;
|
||||
}
|
||||
@ -225,6 +235,11 @@ public class HuaweiSupportProvider {
|
||||
public HuaweiAppManager getHuaweiAppManager() {
|
||||
return huaweiAppManager;
|
||||
}
|
||||
|
||||
public HuaweiP2PManager getHuaweiP2PManager() {
|
||||
return huaweiP2PManager;
|
||||
}
|
||||
|
||||
public HuaweiSupportProvider(HuaweiBRSupport support) {
|
||||
this.brSupport = support;
|
||||
}
|
||||
@ -563,6 +578,7 @@ public class HuaweiSupportProvider {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
huaweiP2PManager.unregisterAllService();
|
||||
stopBatteryRunnerDelayed();
|
||||
GetBatteryLevelRequest batteryLevelReq = new GetBatteryLevelRequest(this);
|
||||
batteryLevelReq.setFinalizeReq(new RequestCallback() {
|
||||
@ -755,6 +771,15 @@ public class HuaweiSupportProvider {
|
||||
public void call() {
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
|
||||
|
||||
if(getHuaweiCoordinator().supportsP2PService()) {
|
||||
if(getHuaweiCoordinator().supportsCalendar()) {
|
||||
if (HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager) == null) {
|
||||
HuaweiP2PCalendarService calendarService = new HuaweiP2PCalendarService(huaweiP2PManager);
|
||||
calendarService.register();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1004,6 +1029,9 @@ public class HuaweiSupportProvider {
|
||||
case ActivityUser.PREF_USER_DATE_OF_BIRTH:
|
||||
sendUserInfo();
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR:
|
||||
HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager).restartSynchronization();
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO: Use translatable string
|
||||
@ -1815,13 +1843,47 @@ public class HuaweiSupportProvider {
|
||||
public void onInstallApp(Uri uri) {
|
||||
LOG.info("enter onAppInstall uri: "+uri);
|
||||
HuaweiFwHelper huaweiFwHelper = new HuaweiFwHelper(uri, getContext());
|
||||
huaweiUploadManager.setBytes(huaweiFwHelper.getBytes());
|
||||
huaweiUploadManager.setFileType(huaweiFwHelper.getFileType());
|
||||
|
||||
HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo();
|
||||
|
||||
fileInfo.setFileType(huaweiFwHelper.getFileType());
|
||||
if (huaweiFwHelper.isWatchface()) {
|
||||
huaweiUploadManager.setFileName(huaweiWatchfaceManager.getRandomName());
|
||||
fileInfo.setFileName(huaweiWatchfaceManager.getRandomName());
|
||||
} else {
|
||||
huaweiUploadManager.setFileName(huaweiFwHelper.getFileName());
|
||||
fileInfo.setFileName(huaweiFwHelper.getFileName());
|
||||
}
|
||||
fileInfo.setBytes(huaweiFwHelper.getBytes());
|
||||
|
||||
fileInfo.setFileUploadCallback(new HuaweiUploadManager.FileUploadCallback() {
|
||||
@Override
|
||||
public void onUploadStart() {
|
||||
HuaweiSupportProvider.this.huaweiUploadManager.setDeviceBusy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadProgress(int progress) {
|
||||
HuaweiSupportProvider.this.onUploadProgress(R.string.updatefirmwareoperation_update_in_progress, progress, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadComplete() {
|
||||
HuaweiSupportProvider.this.huaweiUploadManager.unsetDeviceBusy();
|
||||
HuaweiSupportProvider.this.onUploadProgress(R.string.updatefirmwareoperation_update_complete, 100, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int code) {
|
||||
if (code == 140004) {
|
||||
LOG.error("Too many watchfaces installed");
|
||||
HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.cannot_upload_watchface_too_many_watchfaces_installed), Toast.LENGTH_LONG, GB.ERROR));
|
||||
} else if (code == 140009) {
|
||||
LOG.error("Insufficient space for upload");
|
||||
HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.insufficient_space_for_upload), Toast.LENGTH_LONG, GB.ERROR));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
huaweiUploadManager.setFileUploadInfo(fileInfo);
|
||||
|
||||
try {
|
||||
SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(this, huaweiUploadManager);
|
||||
@ -1949,6 +2011,20 @@ public class HuaweiSupportProvider {
|
||||
|
||||
}
|
||||
|
||||
public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
|
||||
HuaweiP2PCalendarService service = HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager);
|
||||
if(service != null) {
|
||||
service.onAddCalendarEvent(calendarEventSpec);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleteCalendarEvent(final byte type, long id) {
|
||||
HuaweiP2PCalendarService service = HuaweiP2PCalendarService.getRegisteredInstance(huaweiP2PManager);
|
||||
if(service != null) {
|
||||
service.onDeleteCalendarEvent(type, id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean startBatteryRunnerDelayed() {
|
||||
int interval_minutes = GBApplication.getDevicePrefs(gbDevice).getBatteryPollingIntervalMinutes();
|
||||
int interval = interval_minutes * 60 * 1000;
|
||||
@ -1965,6 +2041,7 @@ public class HuaweiSupportProvider {
|
||||
public void dispose() {
|
||||
stopBatteryRunnerDelayed();
|
||||
huaweiFileDownloadManager.dispose();
|
||||
huaweiP2PManager.unregisterAllService();
|
||||
}
|
||||
|
||||
public boolean downloadTruSleepData(int start, int end) {
|
||||
|
@ -31,109 +31,165 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class HuaweiUploadManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuaweiUploadManager.class);
|
||||
|
||||
public interface FileUploadCallback {
|
||||
void onUploadStart();
|
||||
void onUploadProgress(int progress);
|
||||
void onUploadComplete();
|
||||
void onError(int code);
|
||||
}
|
||||
|
||||
public static class FileUploadInfo {
|
||||
private byte[] fileBin;
|
||||
private byte[] fileSHA256;
|
||||
private byte fileType = 1; // 1 - watchface, 2 - music, 3 - png for background , 7 - app
|
||||
private int fileSize = 0;
|
||||
private byte fileId = 0; // get on incoming (2803)
|
||||
|
||||
private String fileName = ""; //FIXME generate random name
|
||||
|
||||
private String srcPackage = null;
|
||||
private String dstPackage = null;
|
||||
private String srcFingerprint = null;
|
||||
private String dstFingerprint = null;
|
||||
private boolean isEncrypted = false;
|
||||
|
||||
private int currentUploadPosition = 0;
|
||||
private int uploadChunkSize = 0;
|
||||
|
||||
private FileUploadCallback fileUploadCallback = null;
|
||||
|
||||
//ack values set from 28 4 response
|
||||
private FileUploadParams fileUploadParams = null;
|
||||
|
||||
|
||||
public FileUploadCallback getFileUploadCallback() {
|
||||
return fileUploadCallback;
|
||||
}
|
||||
|
||||
public void setFileUploadCallback(FileUploadCallback fileUploadCallback) {
|
||||
this.fileUploadCallback = fileUploadCallback;
|
||||
}
|
||||
|
||||
public void setFileUploadParams(FileUploadParams params) {
|
||||
this.fileUploadParams = params;
|
||||
}
|
||||
|
||||
public short getUnitSize() {
|
||||
return fileUploadParams.unit_size;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] uploadArray) {
|
||||
|
||||
this.fileSize = uploadArray.length;
|
||||
this.fileBin = uploadArray;
|
||||
|
||||
try {
|
||||
MessageDigest m = MessageDigest.getInstance("SHA256");
|
||||
m.update(fileBin, 0, fileBin.length);
|
||||
fileSHA256 = m.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.error("Digest algorithm not found.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
currentUploadPosition = 0;
|
||||
uploadChunkSize = 0;
|
||||
|
||||
LOG.info("File ready for upload, SHA256: "+ GB.hexdump(fileSHA256) + " fileName: " + fileName + " filetype: ", fileType);
|
||||
|
||||
}
|
||||
|
||||
public int getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return this.fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public byte getFileType() {
|
||||
return this.fileType;
|
||||
}
|
||||
|
||||
public void setFileType(byte fileType) {
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
public byte getFileId() {
|
||||
return fileId;
|
||||
}
|
||||
|
||||
public void setFileId(byte fileId) {
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
public String getSrcPackage() { return srcPackage; }
|
||||
|
||||
public void setSrcPackage(String srcPackage) { this.srcPackage = srcPackage; }
|
||||
|
||||
public String getDstPackage() { return dstPackage; }
|
||||
|
||||
public void setDstPackage(String dstPackage) { this.dstPackage = dstPackage; }
|
||||
|
||||
public String getSrcFingerprint() { return srcFingerprint; }
|
||||
|
||||
public void setSrcFingerprint(String srcFingerprint) { this.srcFingerprint = srcFingerprint; }
|
||||
|
||||
public String getDstFingerprint() { return dstFingerprint;}
|
||||
|
||||
public void setDstFingerprint(String dstFingerprint) { this.dstFingerprint = dstFingerprint;}
|
||||
|
||||
public boolean isEncrypted() { return isEncrypted; }
|
||||
|
||||
public void setEncrypted(boolean encrypted) { isEncrypted = encrypted; }
|
||||
|
||||
public byte[] getFileSHA256() {
|
||||
return fileSHA256;
|
||||
}
|
||||
|
||||
public void setUploadChunkSize(int chunkSize) {
|
||||
uploadChunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public void setCurrentUploadPosition (int pos) {
|
||||
currentUploadPosition = pos;
|
||||
}
|
||||
|
||||
public int getCurrentUploadPosition() {
|
||||
return currentUploadPosition;
|
||||
}
|
||||
|
||||
public byte[] getCurrentChunk() {
|
||||
byte[] ret = new byte[uploadChunkSize];
|
||||
System.arraycopy(fileBin, currentUploadPosition, ret, 0, uploadChunkSize);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private final HuaweiSupportProvider support;
|
||||
byte[] fileBin;
|
||||
byte[] fileSHA256;
|
||||
byte fileType = 1; // 1 - watchface, 2 - music, 3 - png for background , 7 - app
|
||||
int fileSize = 0;
|
||||
byte fileId = 0; // get on incoming (2803)
|
||||
|
||||
int currentUploadPosition = 0;
|
||||
int uploadChunkSize =0;
|
||||
|
||||
String fileName = ""; //FIXME generate random name
|
||||
|
||||
//ack values set from 28 4 response
|
||||
FileUploadParams fileUploadParams;
|
||||
|
||||
FileUploadInfo fileUploadInfo = null;
|
||||
|
||||
public HuaweiUploadManager(HuaweiSupportProvider support) {
|
||||
this.support=support;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] uploadArray) {
|
||||
|
||||
this.fileSize = uploadArray.length;
|
||||
this.fileBin = uploadArray;
|
||||
|
||||
try {
|
||||
MessageDigest m = MessageDigest.getInstance("SHA256");
|
||||
m.update(fileBin, 0, fileBin.length);
|
||||
fileSHA256 = m.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.error("Digest alghoritm not found.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
currentUploadPosition = 0;
|
||||
uploadChunkSize = 0;
|
||||
|
||||
LOG.info("File ready for upload, SHA256: "+ GB.hexdump(fileSHA256) + " fileName: " + fileName + " filetype: ", fileType);
|
||||
|
||||
public FileUploadInfo getFileUploadInfo() {
|
||||
return fileUploadInfo;
|
||||
}
|
||||
|
||||
public int getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return this.fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public byte getFileType() {
|
||||
return this.fileType;
|
||||
}
|
||||
|
||||
public void setFileType(byte fileType) {
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
public byte getFileId() {
|
||||
return fileId;
|
||||
}
|
||||
|
||||
public void setFileId(byte fileId) {
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
public byte[] getFileSHA256() {
|
||||
return fileSHA256;
|
||||
}
|
||||
|
||||
public void setUploadChunkSize(int chunkSize) {
|
||||
uploadChunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public void setCurrentUploadPosition (int pos) {
|
||||
currentUploadPosition = pos;
|
||||
}
|
||||
|
||||
public int getCurrentUploadPosition() {
|
||||
return currentUploadPosition;
|
||||
}
|
||||
|
||||
public byte[] getCurrentChunk() {
|
||||
byte[] ret = new byte[uploadChunkSize];
|
||||
System.arraycopy(fileBin, currentUploadPosition, ret, 0, uploadChunkSize);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void setFileUploadParams(FileUploadParams params) {
|
||||
this.fileUploadParams = params;
|
||||
}
|
||||
|
||||
|
||||
public short getUnitSize() {
|
||||
return fileUploadParams.unit_size;
|
||||
public void setFileUploadInfo(FileUploadInfo fileUploadInfo) {
|
||||
this.fileUploadInfo = fileUploadInfo;
|
||||
}
|
||||
|
||||
public void setDeviceBusy() {
|
||||
final GBDevice device = support.getDevice();
|
||||
if(fileType == FileUpload.Filetype.watchface) {
|
||||
if(fileUploadInfo != null && fileUploadInfo.fileType == FileUpload.Filetype.watchface) {
|
||||
device.setBusyTask(support.getContext().getString(R.string.uploading_watchface));
|
||||
} else {
|
||||
device.setBusyTask(support.getContext().getString(R.string.updating_firmware));
|
||||
|
@ -0,0 +1,105 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendP2PCommand;
|
||||
|
||||
public abstract class HuaweiBaseP2PService {
|
||||
private final Logger LOG = LoggerFactory.getLogger(HuaweiBaseP2PService.class);
|
||||
|
||||
protected final HuaweiP2PManager manager;
|
||||
|
||||
protected HuaweiBaseP2PService(HuaweiP2PManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
manager.registerService(this);
|
||||
}
|
||||
|
||||
public abstract String getModule();
|
||||
|
||||
public abstract String getPackage();
|
||||
|
||||
public abstract String getFingerprint();
|
||||
|
||||
public abstract void registered();
|
||||
|
||||
public abstract void unregister();
|
||||
|
||||
public abstract void handleData(byte[] data);
|
||||
|
||||
public String getLocalFingerprint() {
|
||||
return "UniteDeviceManagement";
|
||||
}
|
||||
|
||||
public String getPingPackage() {
|
||||
return "com.huawei.health";
|
||||
}
|
||||
|
||||
private final Map<Short, HuaweiP2PCallback> waitPackets = new ConcurrentHashMap<>();
|
||||
|
||||
private Short getNextSequence() {
|
||||
return manager.getNextSequence();
|
||||
}
|
||||
|
||||
public void sendCommand(byte[] sendData, HuaweiP2PCallback callback) {
|
||||
try {
|
||||
short seq = this.getNextSequence();
|
||||
SendP2PCommand test = new SendP2PCommand(this.manager.getSupportProvider(), (byte) 2, seq, this.getModule(), this.getPackage(), this.getLocalFingerprint(), this.getFingerprint(), sendData, 0);
|
||||
if (callback != null) {
|
||||
this.waitPackets.put(seq, callback);
|
||||
}
|
||||
test.doPerform();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPing(HuaweiP2PCallback callback) {
|
||||
try {
|
||||
short seq = this.getNextSequence();
|
||||
SendP2PCommand test = new SendP2PCommand(this.manager.getSupportProvider(), (byte) 1, seq, this.getPingPackage(), this.getPackage(), null, null, null, 0);
|
||||
if (callback != null) {
|
||||
this.waitPackets.put(seq, callback);
|
||||
}
|
||||
test.doPerform();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendAck(short sequence, String srcPackage, String dstPackage, int code) {
|
||||
try {
|
||||
SendP2PCommand test = new SendP2PCommand(this.manager.getSupportProvider(), (byte) 3, sequence, srcPackage, dstPackage, null, null, null, code);
|
||||
test.doPerform();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void handlePacket(P2P.P2PCommand.Response packet) {
|
||||
LOG.info("HuaweiP2PCalendarService handlePacket: {} Code: {}", packet.cmdId, packet.respCode);
|
||||
if (waitPackets.containsKey(packet.sequenceId)) {
|
||||
LOG.info("HuaweiP2PCalendarService handlePacket find handler");
|
||||
HuaweiP2PCallback handle = waitPackets.remove(packet.sequenceId);
|
||||
handle.onResponse(packet.respCode, packet.respData);
|
||||
} else {
|
||||
|
||||
if (packet.cmdId == 1) { //Ping
|
||||
sendAck(packet.sequenceId, packet.dstPackage, packet.srcPackage, 0xca);
|
||||
} else if (packet.cmdId == 2) {
|
||||
handleData(packet.respData);
|
||||
sendAck(packet.sequenceId, packet.dstPackage, packet.srcPackage, 0xca);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
|
||||
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiP2PManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
|
||||
|
||||
public class HuaweiP2PCalendarService extends HuaweiBaseP2PService {
|
||||
private final Logger LOG = LoggerFactory.getLogger(HuaweiP2PCalendarService.class);
|
||||
|
||||
public static final String MODULE = "hw.unitedevice.calendarapp";
|
||||
|
||||
private final AtomicBoolean isRegistered = new AtomicBoolean(false);
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable syncCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendCalendarCmd((byte) 0x01, (byte) 0x01, null);
|
||||
}
|
||||
};
|
||||
|
||||
private List<CalendarEvent> lastCalendarEvents = null;
|
||||
|
||||
public HuaweiP2PCalendarService(HuaweiP2PManager manager) {
|
||||
super(manager);
|
||||
LOG.info("HuaweiP2PCalendarService");
|
||||
}
|
||||
|
||||
public static HuaweiP2PCalendarService getRegisteredInstance(HuaweiP2PManager manager) {
|
||||
return (HuaweiP2PCalendarService) manager.getRegisteredService(HuaweiP2PCalendarService.MODULE);
|
||||
}
|
||||
|
||||
public String getModule() {
|
||||
return HuaweiP2PCalendarService.MODULE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackage() {
|
||||
if (manager.getSupportProvider().getHuaweiCoordinator().supportsExternalCalendarService())
|
||||
return "com.huawei.ohos.calendar";
|
||||
return "in.huawei.calendar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
if (manager.getSupportProvider().getHuaweiCoordinator().supportsExternalCalendarService())
|
||||
return "com.huawei.ohos.calendar_BCgpfcWNSKWgvxsSILxooQZyAmKYsFQnMTibnfrKQqK9M0ABtXH+GbsOscsnVvVc5qIDiFEyEOYMSF7gJ7Vb5Mc=";
|
||||
return "SystemApp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registered() {
|
||||
isRegistered.set(true);
|
||||
startSynchronization();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
isRegistered.set(false);
|
||||
handler.removeCallbacks(syncCallback);
|
||||
}
|
||||
|
||||
private void startSynchronization() {
|
||||
sendCalendarCmd((byte) 0x02, (byte) 0x01, null); // download calendar request but it does not work on my device
|
||||
sendCalendarCmd((byte) 0x01, (byte) 0x01, null); // send sync upload request
|
||||
}
|
||||
|
||||
public void restartSynchronization() {
|
||||
if (isRegistered.get()) {
|
||||
scheduleUpdate(2000);
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleUpdate(long delay) {
|
||||
handler.removeCallbacks(syncCallback);
|
||||
if (isRegistered.get()) {
|
||||
handler.postDelayed(syncCallback, delay);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCalendarCmd(byte command, byte commandData, HuaweiP2PCallback callback) {
|
||||
sendPing(new HuaweiP2PCallback() {
|
||||
@Override
|
||||
public void onResponse(int code, byte[] data) {
|
||||
if ((byte) code != (byte) 0xca)
|
||||
return;
|
||||
// NOTE: basically this is TLV with one tag 0x1, But I have no reasons to create additional classes for this.
|
||||
sendCommand(new byte[]{command, 0x01, 0x01, commandData}, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onAddCalendarEvent(final CalendarEventSpec calendarEventSpec) {
|
||||
LOG.info("onAddCalendarEvent {}", calendarEventSpec.id);
|
||||
scheduleUpdate(2000);
|
||||
}
|
||||
|
||||
public void onDeleteCalendarEvent(final byte type, long id) {
|
||||
LOG.info("onDeleteCalendarEvent {} {}", type, id);
|
||||
scheduleUpdate(2000);
|
||||
}
|
||||
|
||||
private String truncateToHexBytes(String str, int count) {
|
||||
if(str == null)
|
||||
return "";
|
||||
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
|
||||
if (bytes.length > count) {
|
||||
int len = (int)Math.ceil(count/2.0) + 1;
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes, 0, len - 3);
|
||||
CharBuffer cb = CharBuffer.allocate(len);
|
||||
CharsetDecoder cd = StandardCharsets.UTF_8.newDecoder();
|
||||
cd.onMalformedInput(CodingErrorAction.IGNORE);
|
||||
cd.decode(buffer, cb, true);
|
||||
cd.flush(cb);
|
||||
return new String(cb.array(), 0, cb.position()) + "...";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
private String getFileCalendarName() {
|
||||
long millis = System.currentTimeMillis();
|
||||
return String.format(Locale.ROOT, "calendar_data_%d.json", millis);
|
||||
}
|
||||
|
||||
private JsonObject calendarEventToJson(CalendarEvent calendarEvent) {
|
||||
JsonObject ret = new JsonObject();
|
||||
|
||||
// NOTE: Calendar contain reminders already in required format. But GB reformat them.
|
||||
// So we need to reformat them back.
|
||||
StringBuilder reminders = new StringBuilder();
|
||||
for (long rem : calendarEvent.getRemindersAbsoluteTs()) {
|
||||
reminders.append(String.valueOf((calendarEvent.getBegin() - rem) / 60 / 1000L)).append(",");
|
||||
}
|
||||
|
||||
String rrule = calendarEvent.getRrule();
|
||||
if(rrule == null)
|
||||
rrule = "";
|
||||
|
||||
String eventUUID = String.valueOf(calendarEvent.getId()) + "_" + calendarEvent.getBegin();
|
||||
|
||||
ret.addProperty("account_name", truncateToHexBytes(calendarEvent.getCalAccountName(), 64));
|
||||
ret.addProperty("account_type", truncateToHexBytes(calendarEvent.getCalAccountType(), 64));
|
||||
ret.addProperty("all_day", calendarEvent.isAllDay() ? 1 : 0);
|
||||
ret.addProperty("calendar_color", calendarEvent.getColor());
|
||||
ret.addProperty("calendar_displayName", truncateToHexBytes(calendarEvent.getCalName(), 64));
|
||||
ret.addProperty("calendar_id", calendarEvent.getCalendarId());
|
||||
ret.addProperty("description", truncateToHexBytes(calendarEvent.getDescription(), 512));
|
||||
ret.addProperty("dtend", calendarEvent.getEnd());
|
||||
ret.addProperty("dtstart", calendarEvent.getBegin());
|
||||
ret.addProperty("event_id", String.valueOf(calendarEvent.getId()));
|
||||
ret.addProperty("event_location", truncateToHexBytes(calendarEvent.getLocation(), 256));
|
||||
ret.addProperty("event_uuid", eventUUID);
|
||||
ret.addProperty("has_alarm", (reminders.length() == 0) ? 0 : 1);
|
||||
ret.addProperty("minutes", reminders.toString());
|
||||
ret.addProperty("operation", 1); // 1 - add, 2 - delete
|
||||
ret.addProperty("rrule", rrule);
|
||||
// TODO: Retrieve from CalendarContract.CalendarAlerts, field state
|
||||
// TODO: see handleData function command ID 3 for details
|
||||
ret.addProperty("state", -1);
|
||||
ret.addProperty("title", truncateToHexBytes(calendarEvent.getTitle(), 500));
|
||||
return ret;
|
||||
}
|
||||
|
||||
private JsonObject calendarDeletedEventToJson(CalendarEvent calendarEvent) {
|
||||
JsonObject ret = new JsonObject();
|
||||
|
||||
String eventUUID = String.valueOf(calendarEvent.getId()) + "_" + calendarEvent.getBegin();
|
||||
|
||||
ret.addProperty("account_name", "");
|
||||
ret.addProperty("account_type", "");
|
||||
ret.addProperty("all_day", 0);
|
||||
ret.addProperty("calendar_color", 0);
|
||||
ret.addProperty("calendar_displayName", "");
|
||||
ret.addProperty("calendar_id", "");
|
||||
ret.addProperty("description", "");
|
||||
ret.addProperty("dtend", 0);
|
||||
ret.addProperty("dtstart", 0);
|
||||
ret.addProperty("event_id", String.valueOf(calendarEvent.getId()));
|
||||
ret.addProperty("event_location", "");
|
||||
ret.addProperty("event_uuid", eventUUID);
|
||||
ret.addProperty("has_alarm", 0);
|
||||
ret.addProperty("minutes", "");
|
||||
ret.addProperty("operation", 2); // 1 - add, 2 - delete
|
||||
ret.addProperty("rrule", "");
|
||||
ret.addProperty("state", 0);
|
||||
ret.addProperty("title", "");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private JsonArray getFullCalendarData() {
|
||||
final CalendarManager upcomingEvents = new CalendarManager(manager.getSupportProvider().getContext(), manager.getSupportProvider().getDevice().getAddress());
|
||||
final List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
|
||||
|
||||
JsonArray events = new JsonArray();
|
||||
for (final CalendarEvent calendarEvent : calendarEvents) {
|
||||
events.add(calendarEventToJson(calendarEvent));
|
||||
}
|
||||
|
||||
lastCalendarEvents = calendarEvents;
|
||||
return events;
|
||||
}
|
||||
|
||||
private JsonArray getUpdateCalendarData() {
|
||||
final CalendarManager upcomingEvents = new CalendarManager(manager.getSupportProvider().getContext(), manager.getSupportProvider().getDevice().getAddress());
|
||||
final List<CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList();
|
||||
|
||||
List<CalendarEvent> newEvents = new ArrayList<>(calendarEvents);
|
||||
newEvents.removeAll(lastCalendarEvents);
|
||||
|
||||
List<CalendarEvent> removedEvents = new ArrayList<>(lastCalendarEvents);
|
||||
removedEvents.removeAll(calendarEvents);
|
||||
|
||||
JsonArray events = new JsonArray();
|
||||
for (final CalendarEvent calendarEvent : newEvents) {
|
||||
events.add(calendarEventToJson(calendarEvent));
|
||||
}
|
||||
|
||||
for (final CalendarEvent calendarEvent : removedEvents) {
|
||||
events.add(calendarDeletedEventToJson(calendarEvent));
|
||||
}
|
||||
|
||||
lastCalendarEvents = calendarEvents;
|
||||
return events;
|
||||
}
|
||||
|
||||
private byte[] getCalendarFileContent(String majorVersion, short minorVersion, JsonArray scheduleList) {
|
||||
JsonObject syncData = new JsonObject();
|
||||
syncData.addProperty("major", majorVersion);
|
||||
syncData.addProperty("minor", minorVersion);
|
||||
syncData.add("scheduleList", scheduleList);
|
||||
|
||||
String data = new Gson().toJson(syncData);
|
||||
LOG.info(data);
|
||||
|
||||
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
ByteBuffer sendData = ByteBuffer.allocate(dataBytes.length + 8); // 8 is data header
|
||||
// NOTE: minor version is short in response but in this case it writes as integer
|
||||
sendData.putInt(minorVersion);
|
||||
sendData.putInt(dataBytes.length);
|
||||
sendData.put(dataBytes);
|
||||
|
||||
return sendData.array();
|
||||
}
|
||||
|
||||
private boolean sendCalendarFile(String majorVersion, short minorVersion, short scheduleCount) {
|
||||
LOG.info("Send calendar file upload info");
|
||||
HuaweiUploadManager huaweiUploadManager = this.manager.getSupportProvider().getUploadManager();
|
||||
|
||||
JsonArray calendarData;
|
||||
if (majorVersion == null || majorVersion.isEmpty() || lastCalendarEvents == null || minorVersion == 0) {
|
||||
calendarData = getFullCalendarData();
|
||||
if(calendarData.isEmpty()) {
|
||||
if(minorVersion == 0 && !(majorVersion == null || majorVersion.isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
minorVersion = 0;
|
||||
} else {
|
||||
minorVersion++;
|
||||
}
|
||||
} else {
|
||||
calendarData = getUpdateCalendarData();
|
||||
if (calendarData.isEmpty())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (majorVersion == null || majorVersion.isEmpty()) {
|
||||
majorVersion = new String(this.manager.getSupportProvider().getAndroidId(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
byte[] data = getCalendarFileContent(majorVersion, minorVersion, calendarData);
|
||||
|
||||
HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo();
|
||||
|
||||
fileInfo.setFileType((byte) 7);
|
||||
fileInfo.setFileName(getFileCalendarName());
|
||||
fileInfo.setBytes(data);
|
||||
fileInfo.setSrcPackage(this.getModule());
|
||||
fileInfo.setDstPackage(this.getPackage());
|
||||
fileInfo.setSrcFingerprint(this.getLocalFingerprint());
|
||||
fileInfo.setDstFingerprint(this.getFingerprint());
|
||||
fileInfo.setEncrypted(true);
|
||||
|
||||
fileInfo.setFileUploadCallback(new HuaweiUploadManager.FileUploadCallback() {
|
||||
@Override
|
||||
public void onUploadStart() {
|
||||
// TODO: set device as busy in this case. But maybe exists another way to do this. Currently user see text on device card.
|
||||
// Also text should be changed
|
||||
manager.getSupportProvider().getDevice().setBusyTask(manager.getSupportProvider().getContext().getString(R.string.updating_firmware));
|
||||
manager.getSupportProvider().getDevice().sendDeviceUpdateIntent(manager.getSupportProvider().getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadProgress(int progress) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadComplete() {
|
||||
if (manager.getSupportProvider().getDevice().isBusy()) {
|
||||
manager.getSupportProvider().getDevice().unsetBusyTask();
|
||||
manager.getSupportProvider().getDevice().sendDeviceUpdateIntent(manager.getSupportProvider().getContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int code) {
|
||||
// TODO: maybe we should retry resend on error, at least 3 times.
|
||||
// currently I don't understand the mandatory of this action because file sends always successfully,
|
||||
}
|
||||
});
|
||||
|
||||
huaweiUploadManager.setFileUploadInfo(fileInfo);
|
||||
|
||||
try {
|
||||
SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(this.manager.getSupportProvider(), huaweiUploadManager);
|
||||
sendFileUploadInfo.doPerform();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to send file upload info", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleData(byte[] data) {
|
||||
try {
|
||||
byte commandId = data[0];
|
||||
if (commandId == 1) {
|
||||
HuaweiTLV tlv = new HuaweiTLV();
|
||||
tlv.parse(data, 1, data.length - 1);
|
||||
byte operateMode = -1;
|
||||
String majorVersion = null;
|
||||
short minorVersion = -1;
|
||||
short scheduleCount = -1;
|
||||
|
||||
if (tlv.contains(0x1))
|
||||
operateMode = tlv.getByte(0x1);
|
||||
if (tlv.contains(0x2))
|
||||
majorVersion = tlv.getString(0x2).trim();
|
||||
if (tlv.contains(0x3))
|
||||
minorVersion = tlv.getShort(0x3);
|
||||
if (tlv.contains(0x4))
|
||||
scheduleCount = tlv.getShort(0x4);
|
||||
|
||||
LOG.info("Operate mode: {} Major: {} Minor: {} Schedule Count: {}", operateMode, majorVersion, minorVersion, scheduleCount);
|
||||
|
||||
// TODO: scheduleCount can be a max number of events to send, but I am not sure. Ignore it for now.
|
||||
// NOTE: device can initiate calendar sync. So we need to check and answer properly.
|
||||
final boolean syncEnabled = GBApplication.getDeviceSpecificSharedPrefs(manager.getSupportProvider().getDevice().getAddress()).getBoolean(PREF_SYNC_CALENDAR, false);
|
||||
if(!syncEnabled) {
|
||||
sendCalendarCmd((byte) 0x01, (byte) 0x07, null); //sync disabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (operateMode != -1 && majorVersion != null && minorVersion != -1 && scheduleCount != -1) {
|
||||
if (operateMode == 3) {
|
||||
sendCalendarCmd((byte) 0x01, (byte) 0x03, null);
|
||||
}
|
||||
if (operateMode == 2 || operateMode == 3) {
|
||||
|
||||
//TODO:
|
||||
//sendCalendarCmd((byte) 0x01, (byte) 0x06, null); //no permissions
|
||||
|
||||
//external calendar synchronization only supported on Harmony devices. I don't know how to deal with this.
|
||||
if (!manager.getSupportProvider().getHuaweiCoordinator().supportsExternalCalendarService()) {
|
||||
if (!sendCalendarFile(majorVersion, minorVersion, scheduleCount)) {
|
||||
sendCalendarCmd((byte) 0x01, (byte) 0x04, null); //No sync required
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (commandId == 3) {
|
||||
HuaweiTLV tlv = new HuaweiTLV();
|
||||
tlv.parse(data, 1, data.length - 1);
|
||||
//TODO: wearable sends this command if calendar event status changed.
|
||||
// For example one of the quick action buttons on the reminders notification wes pressed.
|
||||
// There are two buttons:
|
||||
// Snooze - status 0
|
||||
// Close - status 2
|
||||
// It should be send to Android or stored to local GB database. And should be used during next synchronization.
|
||||
// Should be saved to CalendarContract.CalendarAlerts, column "state".
|
||||
// CalendarContract.CalendarAlertsColumns.STATE
|
||||
// Values:
|
||||
// STATE_DISMISSED = 2
|
||||
// STATE_FIRED = 1
|
||||
// STATE_SCHEDULED = 0
|
||||
// Currently GB does not support CALENDAR_WRITE permission so it is not possible.
|
||||
// Additional research required.
|
||||
LOG.info("calendarRequest");
|
||||
if (tlv.contains(0x1))
|
||||
LOG.info("eventId: {}", tlv.getString(0x1).trim());
|
||||
if (tlv.contains(0x2))
|
||||
LOG.info("calendarId: {}", tlv.getString(0x2).trim());
|
||||
if (tlv.contains(0x3))
|
||||
LOG.info("accountName: {}", tlv.getString(0x3).trim());
|
||||
if (tlv.contains(0x4))
|
||||
LOG.info("accountType: {}", tlv.getString(0x4).trim());
|
||||
if (tlv.contains(0x5))
|
||||
LOG.info("state: {}", tlv.getByte(0x5)); //state: 0 - snooze, 2 - close. Quick actions on watch
|
||||
if (tlv.contains(0x6))
|
||||
LOG.info("eventUuid: {}", tlv.getString(0x6).trim());
|
||||
} else {
|
||||
LOG.info("Unknown command");
|
||||
}
|
||||
} catch (HuaweiPacket.MissingTagException e) {
|
||||
LOG.error("P2P handle packet: tag is missing");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
|
||||
|
||||
public interface HuaweiP2PCallback {
|
||||
void onResponse(int code, byte[] data);
|
||||
}
|
@ -19,6 +19,7 @@ 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.FileUpload;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager;
|
||||
@ -36,11 +37,16 @@ public class SendFileUploadChunk extends Request {
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
return new FileUpload.FileNextChunkSend(this.paramsProvider).serializeFileChunk(
|
||||
huaweiUploadManager.getCurrentChunk(),
|
||||
huaweiUploadManager.getCurrentUploadPosition(),
|
||||
huaweiUploadManager.getUnitSize(),
|
||||
huaweiUploadManager.getFileId()
|
||||
);
|
||||
try {
|
||||
return new FileUpload.FileNextChunkSend(this.paramsProvider).serializeFileChunk(
|
||||
huaweiUploadManager.getFileUploadInfo().getCurrentChunk(),
|
||||
huaweiUploadManager.getFileUploadInfo().getCurrentUploadPosition(),
|
||||
huaweiUploadManager.getFileUploadInfo().getUnitSize(),
|
||||
huaweiUploadManager.getFileUploadInfo().getFileId(),
|
||||
huaweiUploadManager.getFileUploadInfo().isEncrypted()
|
||||
);
|
||||
} catch(HuaweiPacket.SerializeException e) {
|
||||
throw new RequestCreationException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadM
|
||||
|
||||
public class SendFileUploadHash extends Request{
|
||||
HuaweiUploadManager huaweiUploadManager;
|
||||
private byte fileId;
|
||||
public SendFileUploadHash(HuaweiSupportProvider support,
|
||||
HuaweiUploadManager huaweiUploadManager) {
|
||||
super(support);
|
||||
this.huaweiUploadManager = huaweiUploadManager;
|
||||
this.fileId = fileId;
|
||||
this.serviceId = FileUpload.id;
|
||||
this.commandId = FileUpload.FileHashSend.id;
|
||||
this.addToResponse = false;
|
||||
@ -42,8 +40,8 @@ public class SendFileUploadHash extends Request{
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
return new FileUpload.FileHashSend.Request(this.paramsProvider,
|
||||
huaweiUploadManager.getFileSHA256(),
|
||||
huaweiUploadManager.getFileId()
|
||||
huaweiUploadManager.getFileUploadInfo().getFileSHA256(),
|
||||
huaweiUploadManager.getFileUploadInfo().getFileId()
|
||||
).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
|
@ -33,16 +33,19 @@ public class SendFileUploadInfo extends Request{
|
||||
this.serviceId = FileUpload.id;
|
||||
this.commandId = FileUpload.FileInfoSend.id;
|
||||
this.addToResponse = false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
return new FileUpload.FileInfoSend.Request(this.paramsProvider,
|
||||
huaweiUploadManager.getFileSize(),
|
||||
huaweiUploadManager.getFileName(),
|
||||
huaweiUploadManager.getFileType()
|
||||
huaweiUploadManager.getFileUploadInfo().getFileSize(),
|
||||
huaweiUploadManager.getFileUploadInfo().getFileName(),
|
||||
huaweiUploadManager.getFileUploadInfo().getFileType(),
|
||||
huaweiUploadManager.getFileUploadInfo().getSrcPackage(),
|
||||
huaweiUploadManager.getFileUploadInfo().getDstPackage(),
|
||||
huaweiUploadManager.getFileUploadInfo().getSrcFingerprint(),
|
||||
huaweiUploadManager.getFileUploadInfo().getDstFingerprint()
|
||||
).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
|
@ -0,0 +1,59 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.P2P;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
|
||||
|
||||
public class SendP2PCommand extends Request {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SendAccountRequest.class);
|
||||
|
||||
private byte cmdId;
|
||||
private short sequenceId;
|
||||
private String srcPackage;
|
||||
private String dstPackage;
|
||||
private String srcFingerprint = null;
|
||||
private String dstFingerprint = null;
|
||||
private byte[] sendData = null;
|
||||
private int sendCode = 0;
|
||||
|
||||
public SendP2PCommand(HuaweiSupportProvider support,
|
||||
byte cmdId,
|
||||
short sequenceId,
|
||||
String srcPackage,
|
||||
String dstPackage,
|
||||
String srcFingerprint,
|
||||
String dstFingerprint,
|
||||
byte[] sendData,
|
||||
int sendCode) {
|
||||
super(support);
|
||||
this.serviceId = P2P.id;
|
||||
this.commandId = P2P.P2PCommand.id;
|
||||
|
||||
this.cmdId = cmdId;
|
||||
this.sequenceId = sequenceId;
|
||||
this.srcPackage = srcPackage;
|
||||
this.dstPackage = dstPackage;
|
||||
this.srcFingerprint = srcFingerprint;
|
||||
this.dstFingerprint = dstFingerprint;
|
||||
this.sendData = sendData;
|
||||
this.sendCode = sendCode;
|
||||
this.addToResponse = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<byte[]> createRequest() throws RequestCreationException {
|
||||
try {
|
||||
return new P2P.P2PCommand.Request(paramsProvider, this.cmdId, this.sequenceId, this.srcPackage, this.dstPackage, this.srcFingerprint, this.dstFingerprint, this.sendData, this.sendCode).serialize();
|
||||
} catch (HuaweiPacket.CryptoException e) {
|
||||
throw new RequestCreationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -29,12 +29,15 @@ public class CalendarEvent {
|
||||
private final String location;
|
||||
private final String calName;
|
||||
private final String calAccountName;
|
||||
private final String calAccountType;
|
||||
private final String calendarId;
|
||||
private final String organizer;
|
||||
private final int color;
|
||||
private final boolean allDay;
|
||||
private final String rrule;
|
||||
private List<Long> remindersAbsoluteTs = new ArrayList<>();
|
||||
|
||||
public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, String calAccountName, int color, boolean allDay, String organizer) {
|
||||
public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, String calAccountName, int color, boolean allDay, String organizer, String calAccountType, String calendarId, String rrule) {
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
this.id = id;
|
||||
@ -46,6 +49,9 @@ public class CalendarEvent {
|
||||
this.color = color;
|
||||
this.allDay = allDay;
|
||||
this.organizer = organizer;
|
||||
this.calAccountType = calAccountType;
|
||||
this.calendarId = calendarId;
|
||||
this.rrule = rrule;
|
||||
}
|
||||
|
||||
public List<Long> getRemindersAbsoluteTs() {
|
||||
@ -125,6 +131,18 @@ public class CalendarEvent {
|
||||
return allDay;
|
||||
}
|
||||
|
||||
public String getCalAccountType() {
|
||||
return calAccountType;
|
||||
}
|
||||
|
||||
public String getCalendarId() {
|
||||
return calendarId;
|
||||
}
|
||||
|
||||
public String getRrule() {
|
||||
return rrule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof CalendarEvent) {
|
||||
@ -140,7 +158,10 @@ public class CalendarEvent {
|
||||
(this.getColor() == e.getColor()) &&
|
||||
(this.isAllDay() == e.isAllDay()) &&
|
||||
Objects.equals(this.getOrganizer(), e.getOrganizer()) &&
|
||||
Objects.equals(this.getRemindersAbsoluteTs(), e.getRemindersAbsoluteTs());
|
||||
Objects.equals(this.getRemindersAbsoluteTs(), e.getRemindersAbsoluteTs()) &&
|
||||
Objects.equals(this.getCalAccountType(), e.getCalAccountType()) &&
|
||||
Objects.equals(this.getCalendarId(), e.getCalendarId()) &&
|
||||
Objects.equals(this.getRrule(), e.getRrule());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -160,6 +181,9 @@ public class CalendarEvent {
|
||||
result = 31 * result + Boolean.valueOf(allDay).hashCode();
|
||||
result = 31 * result + Objects.hash(organizer);
|
||||
result = 31 * result + Objects.hash(remindersAbsoluteTs);
|
||||
result = 31 * result + Objects.hash(calAccountType);
|
||||
result = 31 * result + Objects.hash(calendarId);
|
||||
result = 31 * result + Objects.hash(rrule);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,10 @@ public class CalendarManager {
|
||||
CalendarContract.Calendars.ACCOUNT_NAME,
|
||||
Instances.CALENDAR_COLOR,
|
||||
Instances.ALL_DAY,
|
||||
Instances.EVENT_ID //needed for reminders
|
||||
Instances.EVENT_ID, //needed for reminders
|
||||
CalendarContract.Calendars.ACCOUNT_TYPE,
|
||||
Instances.CALENDAR_ID,
|
||||
Instances.RRULE
|
||||
};
|
||||
|
||||
private static final int lookahead_days = 7;
|
||||
@ -126,6 +129,7 @@ public class CalendarManager {
|
||||
time.parse(evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.DURATION)));
|
||||
end = start + time.toMillis(false);
|
||||
}
|
||||
|
||||
CalendarEvent calEvent = new CalendarEvent(
|
||||
start,
|
||||
end,
|
||||
@ -137,10 +141,12 @@ public class CalendarManager {
|
||||
evtCursor.getString(evtCursor.getColumnIndexOrThrow(CalendarContract.Calendars.ACCOUNT_NAME)),
|
||||
evtCursor.getInt(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_COLOR)),
|
||||
!evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ALL_DAY)).equals("0"),
|
||||
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ORGANIZER))
|
||||
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ORGANIZER)),
|
||||
evtCursor.getString(evtCursor.getColumnIndexOrThrow(CalendarContract.Calendars.ACCOUNT_TYPE)),
|
||||
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_ID)),
|
||||
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.RRULE))
|
||||
);
|
||||
|
||||
|
||||
// Query reminders for this event
|
||||
final Cursor reminderCursor = mContext.getContentResolver().query(
|
||||
CalendarContract.Reminders.CONTENT_URI,
|
||||
|
Loading…
Reference in New Issue
Block a user