From f3aaeb5216bdc7f70609fab37cd5d38a784ba5d3 Mon Sep 17 00:00:00 2001 From: Me7c7 Date: Fri, 27 Sep 2024 08:54:51 +0300 Subject: [PATCH] Huawei: Initial P2P service support, Calendar sync support. --- .../devices/huawei/HuaweiBRCoordinator.java | 5 + .../devices/huawei/HuaweiCoordinator.java | 27 +- .../devices/huawei/HuaweiLECoordinator.java | 5 + .../devices/huawei/HuaweiPacket.java | 58 ++- .../devices/huawei/HuaweiTLV.java | 10 +- .../devices/huawei/packets/FileUpload.java | 77 +-- .../devices/huawei/packets/P2P.java | 80 ++++ .../devices/huawei/AsynchronousResponse.java | 133 ++++-- .../devices/huawei/HuaweiBRSupport.java | 12 + .../devices/huawei/HuaweiLESupport.java | 11 + .../devices/huawei/HuaweiP2PManager.java | 71 +++ .../devices/huawei/HuaweiSupportProvider.java | 85 +++- .../devices/huawei/HuaweiUploadManager.java | 236 ++++++---- .../huawei/p2p/HuaweiBaseP2PService.java | 105 +++++ .../huawei/p2p/HuaweiP2PCalendarService.java | 441 ++++++++++++++++++ .../devices/huawei/p2p/HuaweiP2PCallback.java | 5 + .../huawei/requests/SendFileUploadChunk.java | 18 +- .../huawei/requests/SendFileUploadHash.java | 6 +- .../huawei/requests/SendFileUploadInfo.java | 11 +- .../huawei/requests/SendP2PCommand.java | 59 +++ .../util/calendar/CalendarEvent.java | 28 +- .../util/calendar/CalendarManager.java | 12 +- 22 files changed, 1291 insertions(+), 204 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCallback.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java index 7c16f3142..e4a47808a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiBRCoordinator.java @@ -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; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java index c475ae5cd..d799c574c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java @@ -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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java index f14815beb..16f35e377 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiLECoordinator.java @@ -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; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java index b86a0d259..eb960ca08 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java @@ -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 serializeFileChunk(byte[] fileChunk, int uploadPosition, short unitSize, byte fileId) { + public List serializeFileChunk(byte[] fileChunk, int uploadPosition, short unitSize, byte fileId, boolean isEncrypted) throws SerializeException { List 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]; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java index d89e463f9..c56cb157d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiTLV.java @@ -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( diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java index 600e59b8a..ab9ecc053 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java @@ -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); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java new file mode 100644 index 000000000..30eabb527 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/P2P.java @@ -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); + } + + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java index 61107ee94..d1dffbdbb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java @@ -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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java index 89e45f68b..46988e060 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java @@ -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(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java index 3ead56d17..c25049cf7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java @@ -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(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java new file mode 100644 index 000000000..3710e29e4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiP2PManager.java @@ -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 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); + } + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index aeddf4602..5d7015a6f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -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) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java index 9be852a0e..2e98ba2fe 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java @@ -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)); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java new file mode 100644 index 000000000..11e63c0cd --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiBaseP2PService.java @@ -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 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); + } + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java new file mode 100644 index 000000000..d3cbc4eb2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCalendarService.java @@ -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 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 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 calendarEvents = upcomingEvents.getCalendarEventList(); + + List newEvents = new ArrayList<>(calendarEvents); + newEvents.removeAll(lastCalendarEvents); + + List 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"); + } + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCallback.java new file mode 100644 index 000000000..175336074 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/p2p/HuaweiP2PCallback.java @@ -0,0 +1,5 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p; + +public interface HuaweiP2PCallback { + void onResponse(int code, byte[] data); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java index 5a3ef31e9..7fb663719 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadChunk.java @@ -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 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()); + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java index 074f1892e..5a90af62f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadHash.java @@ -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 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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java index c8c38ecff..c5417e1e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendFileUploadInfo.java @@ -33,16 +33,19 @@ public class SendFileUploadInfo extends Request{ this.serviceId = FileUpload.id; this.commandId = FileUpload.FileInfoSend.id; this.addToResponse = false; - } @Override protected List 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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java new file mode 100644 index 000000000..f73658470 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendP2PCommand.java @@ -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 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); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java index 2e8f6f5f0..3ab7c4b94 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarEvent.java @@ -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 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 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; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java index 7488ac348..cfe6865cb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/calendar/CalendarManager.java @@ -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,