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