diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0b4e61f..d617a6e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * Zepp OS: Fix Cards and MI AI display item and shortcuts * Zepp OS: Fix setting of control center * Zepp OS: Fix setting of unknown configuration values +* Zepp OS: Set watchface from phone * Add Croatian transliterator * Fix restoring app notification/pebble blacklist preferences on import * Cache notifications while devices are out of range (opt-in) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index bf96f607f..c3ca80002 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -251,6 +251,8 @@ public class DeviceSettingsPreferenceConst { public static final String SHORTCUT_CARDS_SORTABLE = "shortcut_cards_sortable"; + public static final String PREF_WATCHFACE = "watchface"; + public static final String FTP_SERVER_ROOT_DIR = "ftp_server_root_dir"; public static final String FTP_SERVER_ADDRESS = "ftp_server_address"; public static final String FTP_SERVER_USERNAME = "ftp_server_username"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index 41ebc7ec9..41c0a5f79 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -503,6 +503,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp addPreferenceHandlerFor(SHORTCUT_CARDS_SORTABLE); + addPreferenceHandlerFor(PREF_WATCHFACE); + addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL); addPreferenceHandlerFor(PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE); addPreferenceHandlerFor(PREF_SONY_FOCUS_VOICE); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java index 4673fc7ae..3eeb54a96 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java @@ -202,6 +202,7 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { settings.add(R.xml.devicesettings_sleep_mode); settings.add(R.xml.devicesettings_liftwrist_display_sensitivity_with_smart); settings.add(R.xml.devicesettings_password); + settings.add(R.xml.devicesettings_huami2021_watchface); settings.add(R.xml.devicesettings_always_on_display); settings.add(R.xml.devicesettings_screen_timeout); if (supportsAutoBrightness(device)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java index a6463d82b..a10094802 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java @@ -64,6 +64,7 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { removeUnsupportedElementsFromListPreference(HuamiConst.PREF_SHORTCUTS_SORTABLE, handler, prefs); removeUnsupportedElementsFromListPreference(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, handler, prefs); removeUnsupportedElementsFromListPreference(DeviceSettingsPreferenceConst.SHORTCUT_CARDS_SORTABLE, handler, prefs); + removeUnsupportedElementsFromListPreference(DeviceSettingsPreferenceConst.PREF_WATCHFACE, handler, prefs); removeUnsupportedElementsFromListPreference(DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE, handler, prefs); for (final ZeppOsConfigService.ConfigArg config : ZeppOsConfigService.ConfigArg.values()) { 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 28f0be98e..f8cd09715 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 @@ -127,6 +127,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFtpServerService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsMorningUpdatesService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWatchfaceService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; @@ -160,6 +161,7 @@ public abstract class Huami2021Support extends HuamiSupport { private final ZeppOsMorningUpdatesService morningUpdatesService = new ZeppOsMorningUpdatesService(this); private final ZeppOsPhoneService phoneService = new ZeppOsPhoneService(this); private final ZeppOsShortcutCardsService shortcutCardsService = new ZeppOsShortcutCardsService(this); + private final ZeppOsWatchfaceService watchfaceService = new ZeppOsWatchfaceService(this); private final Map mServiceMap = new HashMap() {{ put(fileUploadService.getEndpoint(), fileUploadService); @@ -171,6 +173,7 @@ public abstract class Huami2021Support extends HuamiSupport { put(morningUpdatesService.getEndpoint(), morningUpdatesService); put(phoneService.getEndpoint(), phoneService); put(shortcutCardsService.getEndpoint(), shortcutCardsService); + put(watchfaceService.getEndpoint(), watchfaceService); }}; public Huami2021Support() { @@ -283,6 +286,15 @@ public abstract class Huami2021Support extends HuamiSupport { return; } + // watchfaceService preferences, they do not use the configService + switch (config) { + case DeviceSettingsPreferenceConst.PREF_WATCHFACE: + final String watchface = prefs.getString(DeviceSettingsPreferenceConst.PREF_WATCHFACE, null); + LOG.info("Setting watchface to {}", watchface); + watchfaceService.setWatchface(watchface); + return; + } + // Other preferences switch (config) { case HuamiConst.PREF_CONTROL_CENTER_SORTABLE: @@ -1545,6 +1557,8 @@ public abstract class Huami2021Support extends HuamiSupport { morningUpdatesService.getCategories(builder); shortcutCardsService.requestCapabilities(builder); shortcutCardsService.requestShortcutCards(builder); + watchfaceService.requestWatchfaces(builder); + watchfaceService.requestCurrentWatchface(builder); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java index 70e24190b..508dd0f0f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsShortcutCardsService.java @@ -136,7 +136,7 @@ public class ZeppOsShortcutCardsService extends AbstractZeppOsService { return; } maxCards = payload[2] & 0xFF; - LOG.info("Contacts version={}, maxCards={}", version, maxCards); + LOG.info("Shortcut cards version={}, maxCards={}", version, maxCards); break; case CMD_LIST_RET: LOG.info("Got shortcut cards list"); @@ -146,7 +146,7 @@ public class ZeppOsShortcutCardsService extends AbstractZeppOsService { LOG.info("Got enabled shortcut cards ack, status = {}", payload[1]); break; default: - LOG.warn("Unexpected contacts byte {}", String.format("0x%02x", payload[0])); + LOG.warn("Unexpected shortcut cards byte {}", String.format("0x%02x", payload[0])); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java new file mode 100644 index 000000000..fcab0dff4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWatchfaceService.java @@ -0,0 +1,135 @@ +/* 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 static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WATCHFACE; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; +import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; +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; + +public class ZeppOsWatchfaceService extends AbstractZeppOsService { + private static final Logger LOG = LoggerFactory.getLogger(ZeppOsWatchfaceService.class); + + private static final short ENDPOINT = 0x0023; + + public static final byte CMD_LIST_GET = 0x05; + public static final byte CMD_LIST_RET = 0x06; + public static final byte CMD_SET = 0x07; + public static final byte CMD_SET_ACK = 0x08; + public static final byte CMD_CURRENT_GET = 0x09; + public static final byte CMD_CURRENT_RET = 0x0a; + + public ZeppOsWatchfaceService(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_LIST_RET: + LOG.info("Got watchface list, status = {}", payload[1]); + if (payload[1] != 1) { + LOG.warn("Unexpected status byte {}", payload[1]); + return; + } + parseWatchfaceList(payload); + break; + case CMD_SET_ACK: + LOG.info("Got watchface set ack, status = {}", payload[1]); + break; + case CMD_CURRENT_RET: + final int watchface = BLETypeConversions.toUint32(payload, 1); + final String watchfaceHex = String.format(Locale.ROOT, "0x%08X", watchface); + LOG.info("Got current watchface = {}", watchfaceHex); + getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_WATCHFACE, watchfaceHex)); + break; + default: + LOG.warn("Unexpected watchface byte {}", String.format("0x%02x", payload[0])); + } + } + + public void requestWatchfaces(final TransactionBuilder builder) { + write(builder, CMD_LIST_GET); + } + + public void requestCurrentWatchface(final TransactionBuilder builder) { + write(builder, CMD_CURRENT_GET); + } + + public void parseWatchfaceList(final byte[] payload) { + final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN); + buf.get(); // discard the command byte + buf.get(); // discard the status byte + final int numWatchfaces = buf.get() & 0xFF; + + final List watchfaces = new ArrayList<>(); + + for (int i = 0; i < numWatchfaces; i++) { + final int watchface = buf.getInt(); + watchfaces.add(String.format(Locale.ROOT, "0x%08X", watchface)); + } + + final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences() + .withPreference(Huami2021Coordinator.getPrefPossibleValuesKey(PREF_WATCHFACE), String.join(",", watchfaces)); + getSupport().evaluateGBDeviceEvent(evt); + } + + public void setWatchface(final String watchface) { + if (watchface == null) { + LOG.warn("watchface is null"); + return; + } + + final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]+)$").matcher(watchface); + if (!matcher.find()) { + LOG.warn("Failed to parse watchface '{}' as hex", watchface); + return; + } + final int watchfaceInt = Integer.parseInt(matcher.group(1), 16); + + final ByteBuffer buf = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN); + buf.put(CMD_SET); + buf.putInt(watchfaceInt); + + write("set watchface", buf.array()); + } +} diff --git a/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml b/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml index 36e3df9fe..86b792f68 100644 --- a/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml +++ b/app/src/main/res/xml/devicesettings_huami2021_shortcut_cards.xml @@ -2,7 +2,7 @@ + + +