1
0
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:
Me7c7 2024-09-27 08:54:51 +03:00 committed by José Rebelo
parent ae3615a388
commit f3aaeb5216
22 changed files with 1291 additions and 204 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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];

View File

@ -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(

View File

@ -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);
}

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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);
}
}
}
}

View File

@ -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) {

View File

@ -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));

View File

@ -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);
}
}
}
}

View File

@ -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");
}
}
}

View File

@ -0,0 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
public interface HuaweiP2PCallback {
void onResponse(int code, byte[] data);
}

View File

@ -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());
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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,