From fad7fa395bc7f3b8ff0ae96e70e557d5f142d2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Thu, 15 Jun 2023 22:04:01 +0100 Subject: [PATCH] Zepp OS: Show watchfaces in app management --- .../devices/huami/Huami2021Support.java | 55 ++++++++++++-- .../UpdateFirmwareOperation2021.java | 6 +- .../zeppos/services/ZeppOsAppsService.java | 30 +++++--- .../services/ZeppOsWatchfaceService.java | 73 ++++++++++++++----- 4 files changed, 130 insertions(+), 34 deletions(-) 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 f6565ad98..9c82bfade 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 @@ -74,6 +74,7 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; @@ -89,6 +90,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsGpxRouteI import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; @@ -96,6 +98,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.Contact; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -686,20 +689,56 @@ public abstract class Huami2021Support extends HuamiSupport implements ZeppOsFil @Override public void onAppInfoReq() { - appsService.requestApps(); - // TODO watchfaceService.requestWatchfaces(); + // Merge the data from apps and watchfaces + // This is required because the apps service only knows the versions, not the app type, + // and the watchface service only knows the app IDs, and not the versions + + final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo(); + final List appsFull = new ArrayList<>(); + + final Map watchfacesById = new HashMap<>(); + final List watchfaces = watchfaceService.getWatchfaces(); + for (final GBDeviceApp watchface : watchfaces) { + watchfacesById.put(watchface.getUUID(), watchface); + } + + final List apps = appsService.getApps(); + for (final GBDeviceApp app : apps) { + final GBDeviceApp watchface = watchfacesById.get(app.getUUID()); + if (watchface != null) { + appsFull.add(new GBDeviceApp( + watchface.getUUID(), + watchface.getName(), + watchface.getCreator(), + app.getVersion(), + GBDeviceApp.Type.WATCHFACE + )); + } else { + appsFull.add(new GBDeviceApp( + app.getUUID(), + app.getName(), + app.getCreator(), + app.getVersion(), + GBDeviceApp.Type.APP_GENERIC + )); + } + } + + appInfoCmd.apps = appsFull.toArray(new GBDeviceApp[0]); + evaluateGBDeviceEvent(appInfoCmd); } @Override public void onAppStart(final UUID uuid, final boolean start) { - // TODO check if watchface - //LOG.warn("TODO: onAppStart {} {}", uuid, start); - //watchfaceService.setWatchface("0x" + uuid.toString().split("-")[0]); + if (start) { + // This actually also starts apps... + watchfaceService.setWatchface(uuid); + } } @Override public void onAppDelete(final UUID uuid) { - appsService.deleteApp(Integer.parseInt(uuid.toString().split("-")[0], 16)); + appsService.deleteApp(uuid); } @Override @@ -1101,6 +1140,10 @@ public abstract class Huami2021Support extends HuamiSupport implements ZeppOsFil return this; } + public void requestApps(final TransactionBuilder builder) { + appsService.requestApps(builder); + } + public void requestWatchfaces(final TransactionBuilder builder) { watchfaceService.requestWatchfaces(builder); watchfaceService.requestCurrentWatchface(builder); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/UpdateFirmwareOperation2021.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/UpdateFirmwareOperation2021.java index 944a38aad..4cca1af29 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/UpdateFirmwareOperation2021.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/UpdateFirmwareOperation2021.java @@ -74,8 +74,9 @@ public class UpdateFirmwareOperation2021 extends UpdateFirmwareOperation2020 { if (getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.APP) { // After an app is installed, request the display items from the band (new app will be at the end) try { - TransactionBuilder builder = performInitialized("request display items"); + TransactionBuilder builder = performInitialized("request display items and apps"); getSupport().requestDisplayItems(builder); + getSupport().requestApps(builder); builder.queue(getQueue()); } catch (final IOException e) { LOG.error("Failed to request display items after app install", e); @@ -83,8 +84,9 @@ public class UpdateFirmwareOperation2021 extends UpdateFirmwareOperation2020 { } else if (getFirmwareInfo().getFirmwareType() == HuamiFirmwareType.WATCHFACE) { // After a watchface is installed, request the watchfaces from the band (new watchface will be at the end) try { - TransactionBuilder builder = performInitialized("request watchfaces"); + TransactionBuilder builder = performInitialized("request watchfaces and apps"); getSupport().requestWatchfaces(builder); + getSupport().requestApps(builder); builder.queue(getQueue()); } catch (final IOException e) { LOG.error("Failed to request watchfaces after watchface install", e); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java index 33fd511c2..1cc16a511 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsAppsService.java @@ -30,6 +30,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; 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; @@ -51,6 +52,8 @@ public class ZeppOsAppsService extends AbstractZeppOsService { private static final byte CMD_APPS_API_LEVEL = 0x05; private static final byte CMD_SCREENSHOT_REQUEST = 0x01; + private final List apps = new ArrayList<>(); + public ZeppOsAppsService(final Huami2021Support support) { super(support); } @@ -82,6 +85,14 @@ public class ZeppOsAppsService extends AbstractZeppOsService { } } + public void initialize(final TransactionBuilder builder) { + requestApps(builder); + } + + public List getApps() { + return apps; + } + private void handleJsPayload(final byte[] payload) { LOG.warn("Handling js payloads not implemented"); } @@ -127,7 +138,7 @@ public class ZeppOsAppsService extends AbstractZeppOsService { } private void parseAppList(final byte[] payload) { - final List apps = new ArrayList<>(); + apps.clear(); final byte[] appListStringBytes = ArrayUtils.subarray(payload, 16, payload.length); final String appListString = new String(appListStringBytes); @@ -150,20 +161,17 @@ public class ZeppOsAppsService extends AbstractZeppOsService { apps.add(new GBDeviceApp( UUID.fromString(String.format("%08x-0000-0000-0000-000000000000", appId)), - "", //String.format("0x%08x", appId), + "", "", appVersion, - GBDeviceApp.Type.APP_GENERIC // it might actually be a watchface + GBDeviceApp.Type.UNKNOWN // it might be an app or watchface )); } - final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo(); - appInfoCmd.apps = apps.toArray(new GBDeviceApp[0]); - - getSupport().evaluateGBDeviceEvent(appInfoCmd); + // TODO broadcast something to update app manager } - public void requestApps() { + public void requestApps(final TransactionBuilder builder) { LOG.info("Request apps"); final ByteBuffer buf = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN); @@ -173,7 +181,7 @@ public class ZeppOsAppsService extends AbstractZeppOsService { buf.put(CMD_APPS_LIST); buf.put((byte) 0x00); - write("request apps", buf.array()); + write(builder, buf.array()); } public void requestApilevel() { @@ -188,6 +196,10 @@ public class ZeppOsAppsService extends AbstractZeppOsService { write("request api level", buf.array()); } + public void deleteApp(final UUID uuid) { + deleteApp(Integer.parseInt(uuid.toString().split("-")[0], 16)); + } + public void deleteApp(final int appId) { LOG.info("Delete app {}", String.format("0x%08x", appId)); 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 index 8e936aae4..bd7e5ac5a 100644 --- 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 @@ -18,6 +18,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.servic import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WATCHFACE; +import android.content.Context; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,12 +28,15 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support; @@ -52,28 +57,34 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService { public enum Watchface { // Codes are from GTR 4, not sure if they match on other watches - RED_FANTASY(0x00002D38), - MULTIPLE_DATA(0x00002D10), - RUSH(0x00002D37), - MINIMALIST(0x00002D0E), - SIMPLICITY_DATA(0x00002D08), - VIBRANT(0x00002D09), - BUSINESS_STYLE(0x00002D0D), - EMERALD_MOONLIGHT(0x00002D0A), - ROTATING_EARTH(0x00002D0F), - SUPERPOSITION(0x00002D0C), + RED_FANTASY(0x00002D38, R.string.zepp_os_watchface_red_fantasy), + MULTIPLE_DATA(0x00002D10, R.string.zepp_os_watchface_multiple_data), + RUSH(0x00002D37, R.string.zepp_os_watchface_rush), + MINIMALIST(0x00002D0E, R.string.zepp_os_watchface_minimalist), + SIMPLICITY_DATA(0x00002D08, R.string.zepp_os_watchface_simplicity_data), + VIBRANT(0x00002D09, R.string.zepp_os_watchface_vibrant), + BUSINESS_STYLE(0x00002D0D, R.string.zepp_os_watchface_business_style), + EMERALD_MOONLIGHT(0x00002D0A, R.string.zepp_os_watchface_emerald_moonlight), + ROTATING_EARTH(0x00002D0F, R.string.zepp_os_watchface_rotating_earth), + SUPERPOSITION(0x00002D0C, R.string.zepp_os_watchface_superposition), ; private final int code; + private final int nameResId; - Watchface(final int code) { + Watchface(final int code, final int nameResId) { this.code = code; + this.nameResId = nameResId; } public int getCode() { return code; } + public String getName(final Context context) { + return context.getString(nameResId); + } + public static Watchface fromCode(final int code) { for (final Watchface watchface : values()) { if (watchface.getCode() == code) { @@ -85,6 +96,8 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService { } } + final List watchfaces = new ArrayList<>(); + public ZeppOsWatchfaceService(final Huami2021Support support) { super(support); } @@ -143,6 +156,10 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService { requestCurrentWatchface(builder); } + public List getWatchfaces() { + return watchfaces; + } + public void requestWatchfaces(final TransactionBuilder builder) { write(builder, CMD_LIST_GET); } @@ -151,31 +168,49 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService { write(builder, CMD_CURRENT_GET); } - public void parseWatchfaceList(final byte[] payload) { + private void parseWatchfaceList(final byte[] payload) { + watchfaces.clear(); + 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<>(); + final List watchfacePrefValues = new ArrayList<>(); for (int i = 0; i < numWatchfaces; i++) { final int watchfaceCode = buf.getInt(); final Watchface watchface = Watchface.fromCode(watchfaceCode); + final String watchfaceName; if (watchface != null) { - watchfaces.add(watchface.name().toLowerCase(Locale.ROOT)); + watchfaceName = watchface.getName(getContext()); + watchfacePrefValues.add(watchface.name().toLowerCase(Locale.ROOT)); } else { + watchfaceName = ""; final String watchfaceHex = String.format(Locale.ROOT, "0x%08X", watchfaceCode); + watchfacePrefValues.add(watchfaceHex); LOG.warn("Unknown watchface code {}", watchfaceHex); - watchfaces.add(watchfaceHex); } + + watchfaces.add(new GBDeviceApp( + UUID.fromString(String.format("%08x-0000-0000-0000-000000000000", watchfaceCode)), + watchfaceName, + "", + "", + GBDeviceApp.Type.WATCHFACE + )); } + // TODO broadcast something to update app manager final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences() - .withPreference(Huami2021Coordinator.getPrefPossibleValuesKey(PREF_WATCHFACE), String.join(",", watchfaces)); + .withPreference(Huami2021Coordinator.getPrefPossibleValuesKey(PREF_WATCHFACE), String.join(",", watchfacePrefValues)); getSupport().evaluateGBDeviceEvent(evt); } + public void setWatchface(final UUID uuid) { + setWatchface(Integer.parseInt(uuid.toString().split("-")[0], 16)); + } + public void setWatchface(final String watchfacePrefValue) { if (watchfacePrefValue == null) { LOG.warn("watchface is null"); @@ -197,9 +232,13 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService { watchfaceInt = Integer.parseInt(matcher.group(1), 16); } + setWatchface(watchfaceInt); + } + + public void setWatchface(final int watchfaceId) { final ByteBuffer buf = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN); buf.put(CMD_SET); - buf.putInt(watchfaceInt); + buf.putInt(watchfaceId); write("set watchface", buf.array()); }