diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java new file mode 100644 index 000000000..2c5361958 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Contact.java @@ -0,0 +1,32 @@ +/* Copyright (C) 2023 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.model; + +import java.io.Serializable; + +public interface Contact extends Serializable { + /** + * The {@link android.os.Bundle} name for transferring parceled contacts. + */ + String EXTRA_CONTACT = "contact"; + + String getContactId(); + + String getName(); + + String getNumber(); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java index ddd47ccfa..3d67413fe 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java @@ -120,6 +120,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.Abstrac import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileUploadService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFtpServerService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService; @@ -151,12 +152,14 @@ public abstract class Huami2021Support extends HuamiSupport { private final ZeppOsAgpsService agpsService = new ZeppOsAgpsService(this); private final ZeppOsWifiService wifiService = new ZeppOsWifiService(this); private final ZeppOsFtpServerService ftpServerService = new ZeppOsFtpServerService(this); + private final ZeppOsContactsService contactsService = new ZeppOsContactsService(this); private final Map mServiceMap = new HashMap() {{ put(fileUploadService.getEndpoint(), fileUploadService); put(configService.getEndpoint(), configService); put(agpsService.getEndpoint(), agpsService); put(wifiService.getEndpoint(), wifiService); put(ftpServerService.getEndpoint(), ftpServerService); + put(contactsService.getEndpoint(), contactsService); }}; public Huami2021Support() { @@ -1461,6 +1464,7 @@ public abstract class Huami2021Support extends HuamiSupport { } requestAlarms(builder); //requestReminders(builder); + //contactsService.requestCapabilities(builder); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java index 8700ddb38..6228de9e5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/AbstractZeppOsService.java @@ -44,6 +44,10 @@ public abstract class AbstractZeppOsService { this.mSupport.writeToChunked2021(taskName, getEndpoint(), data, isEncrypted()); } + protected void write(final TransactionBuilder builder, final byte b) { + this.write(builder, new byte[]{b}); + } + protected void write(final TransactionBuilder builder, final byte[] data) { this.mSupport.writeToChunked2021(builder, getEndpoint(), data, isEncrypted()); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java new file mode 100644 index 000000000..0c31f11ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsContactsService.java @@ -0,0 +1,122 @@ +/* Copyright (C) 2023 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.model.Contact; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class ZeppOsContactsService extends AbstractZeppOsService { + private static final Logger LOG = LoggerFactory.getLogger(ZeppOsContactsService.class); + + private static final short ENDPOINT = 0x0014; + + public static final byte CMD_CAPABILITIES_REQUEST = 0x01; + public static final byte CMD_CAPABILITIES_RESPONSE = 0x02; + public static final byte CMD_SET_LIST = 0x07; + public static final byte CMD_SET_LIST_ACK = 0x08; + + private int version = 0; + private int maxContacts = 0; + + public ZeppOsContactsService(final Huami2021Support support) { + super(support); + } + + @Override + public short getEndpoint() { + return ENDPOINT; + } + + @Override + public boolean isEncrypted() { + return true; + } + + @Override + public void handlePayload(final byte[] payload) { + switch (payload[0]) { + case CMD_CAPABILITIES_RESPONSE: + version = payload[1]; + if (version != 1) { + LOG.warn("Unsupported contacts service version {}", version); + return; + } + maxContacts = BLETypeConversions.toUint16(payload, 2); + LOG.info("Contacts version={}, maxContacts={}", version, maxContacts); + break; + case CMD_SET_LIST_ACK: + LOG.info("Got contacts set list ack, status = {}", payload[1]); + break; + default: + LOG.warn("Unexpected contacts byte {}", String.format("0x%02x", payload[0])); + } + } + + public int maxContacts() { + return maxContacts; + } + + public boolean isSupported() { + return version == 1 && maxContacts != 0; + } + + public void requestCapabilities(final TransactionBuilder builder) { + write(builder, CMD_CAPABILITIES_REQUEST); + } + + public void setContacts(final List contacts) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + if (contacts.size() > maxContacts) { + LOG.warn("Number of contacts {} larger than max contacts {}, list will be truncated", contacts.size(), maxContacts); + } + + int numContacts = Math.min(contacts.size(), maxContacts); + + try { + baos.write(CMD_SET_LIST); + baos.write(BLETypeConversions.fromUint16(numContacts)); + for (int i = 0; i < numContacts; i++) { + final Contact contact = contacts.get(i); + if (!StringUtils.isNullOrEmpty(contact.getName())) { + baos.write(contact.getName().getBytes(StandardCharsets.UTF_8)); + } + baos.write(0); + if (!StringUtils.isNullOrEmpty(contact.getNumber())) { + baos.write(contact.getNumber().getBytes(StandardCharsets.UTF_8)); + } + baos.write(0); + } + } catch (final Exception e) { + LOG.error("Failed to create command", e); + return; + } + + write("set contacts", baos.toByteArray()); + } +}