diff --git a/CHANGELOG.md b/CHANGELOG.md index 47fe60b3a..248fa36bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,20 @@ #### Next Version -* Add Croatian transliterator * Amazfit Bip U: Remove alarm snooze option * Amazfit GTR 4 / GTS 4: Add watch Wi-Fi Hotspot and FTP Server * Amazfit GTR 4: Whitelist fw 3.18.1.1 diff from 3.17.0.2 * Amazfit GTS 2 Mini: Add missing alexa menu item * Bangle.js: Fix updating timezone in settings.json if the timezone is zero -* Fix restoring app notification/pebble blacklist preferences on import * Huami: Implement repeated activity fetching * Sony WH-1000XM4: Add speak-to-chat -* Zepp OS: Add config to keep screen on during workout +* Zepp OS: Add support for morning updates +* Zepp OS: Add preference to keep screen on during workout * Zepp OS: Add preference for camera remote * Zepp OS: Fix activate display upon lift wrist smart mode * Zepp OS: Fix setting of unknown configuration values +* Add Croatian transliterator +* Fix restoring app notification/pebble blacklist preferences on import #### 0.73.0 * Initial support for Amazfit T-Rex 2 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 64a626070..11f546269 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 @@ -41,6 +41,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_SCREEN_OFFLINE_VOICE = "pref_screen_offline_voice"; public static final String PREF_SCREEN_WIFI_HOTSPOT = "pref_screen_wifi_hotspot"; public static final String PREF_SCREEN_FTP_SERVER = "pref_screen_ftp_server"; + public static final String PREF_SCREEN_MORNING_UPDATES = "pref_morning_updates"; public static final String PREF_LANGUAGE = "language"; public static final String PREF_LANGUAGE_AUTO = "auto"; @@ -240,6 +241,9 @@ public class DeviceSettingsPreferenceConst { public static final String WIFI_HOTSPOT_STOP = "wifi_hotspot_stop"; public static final String WIFI_HOTSPOT_STATUS = "wifi_hotspot_status"; + public static final String MORNING_UPDATES_ENABLED = "morning_updates_enabled"; + public static final String MORNING_UPDATES_CATEGORIES_SORTABLE = "morning_updates_categories"; + 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 2a4aebe23..26758c370 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 @@ -494,6 +494,9 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_LEFT_SWITCH); addPreferenceHandlerFor(PREF_GALAXY_BUDS_TOUCH_RIGHT_SWITCH); + addPreferenceHandlerFor(MORNING_UPDATES_ENABLED); + addPreferenceHandlerFor(MORNING_UPDATES_CATEGORIES_SORTABLE); + 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 0636e112b..729ccd7bd 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 @@ -26,6 +26,7 @@ import org.apache.commons.lang3.ArrayUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; @@ -44,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public abstract class Huami2021Coordinator extends HuamiCoordinator { @Override @@ -253,6 +255,7 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { settings.add(R.xml.devicesettings_buttonactions_lower_short); settings.add(R.xml.devicesettings_weardirection); settings.add(R.xml.devicesettings_camera_remote); + settings.add(R.xml.devicesettings_morning_updates); // // Connection @@ -362,4 +365,22 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { private boolean supportsConfig(final GBDevice device, final ZeppOsConfigService.ConfigArg config) { return ZeppOsConfigService.deviceHasConfig(getPrefs(device), config); } + + /** + * Returns the preference key where to save the list of possible value for a preference, comma-separated. + */ + public static String getPrefPossibleValuesKey(final String key) { + return String.format(Locale.ROOT, "%s_huami_2021_possible_values", key); + } + + /** + * Returns the preference key where to that a config was reported as supported (boolean). + */ + public static String getPrefKnownConfig(final String key) { + return String.format(Locale.ROOT, "huami_2021_known_config_%s", key); + } + + public static boolean deviceHasConfig(final Prefs devicePrefs, final ZeppOsConfigService.ConfigArg config) { + return devicePrefs.getBoolean(Huami2021Coordinator.getPrefKnownConfig(config.name()), false); + } } 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 60e070d80..b127d648d 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 @@ -59,6 +59,7 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { removeUnsupportedElementsFromListPreference(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, handler, prefs); removeUnsupportedElementsFromListPreference(HuamiConst.PREF_SHORTCUTS_SORTABLE, handler, prefs); removeUnsupportedElementsFromListPreference(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, handler, prefs); + removeUnsupportedElementsFromListPreference(DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE, handler, prefs); for (final ZeppOsConfigService.ConfigArg config : ZeppOsConfigService.ConfigArg.values()) { if (config.getPrefKey() == null) { @@ -78,102 +79,106 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { case TIMESTAMP_MILLIS: default: // For other preferences, just hide them if they were not reported as supported by the device - hidePrefIfNoConfigSupported(handler, prefs, config.getPrefKey(), config); + hidePrefIfNoConfigSupported(handler, prefs, config.getPrefKey(), config.name()); break; } } // Hide all config groups that may not be mapped directly to a preference - final Map> configScreens = new HashMap>() {{ + final Map> configScreens = new HashMap>() {{ put(DeviceSettingsPreferenceConst.PREF_SCREEN_NIGHT_MODE, Arrays.asList( - ZeppOsConfigService.ConfigArg.NIGHT_MODE_MODE, - ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_START, - ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_END + ZeppOsConfigService.ConfigArg.NIGHT_MODE_MODE.name(), + ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_START.name(), + ZeppOsConfigService.ConfigArg.NIGHT_MODE_SCHEDULED_END.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_SLEEP_MODE, Arrays.asList( - ZeppOsConfigService.ConfigArg.SLEEP_MODE_SLEEP_SCREEN, - ZeppOsConfigService.ConfigArg.SLEEP_MODE_SMART_ENABLE + ZeppOsConfigService.ConfigArg.SLEEP_MODE_SLEEP_SCREEN.name(), + ZeppOsConfigService.ConfigArg.SLEEP_MODE_SMART_ENABLE.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST, Arrays.asList( - ZeppOsConfigService.ConfigArg.LIFT_WRIST_MODE, - ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_START, - ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_END, - ZeppOsConfigService.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY + ZeppOsConfigService.ConfigArg.LIFT_WRIST_MODE.name(), + ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_START.name(), + ZeppOsConfigService.ConfigArg.LIFT_WRIST_SCHEDULED_END.name(), + ZeppOsConfigService.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD, Arrays.asList( - ZeppOsConfigService.ConfigArg.PASSWORD_ENABLED, - ZeppOsConfigService.ConfigArg.PASSWORD_TEXT + ZeppOsConfigService.ConfigArg.PASSWORD_ENABLED.name(), + ZeppOsConfigService.ConfigArg.PASSWORD_TEXT.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_ALWAYS_ON_DISPLAY, Arrays.asList( - ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_MODE, - ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START, - ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END, - ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE, - ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_STYLE + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_MODE.name(), + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START.name(), + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END.name(), + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE.name(), + ZeppOsConfigService.ConfigArg.ALWAYS_ON_DISPLAY_STYLE.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS, Arrays.asList( - ZeppOsConfigService.ConfigArg.SCREEN_AUTO_BRIGHTNESS + ZeppOsConfigService.ConfigArg.SCREEN_AUTO_BRIGHTNESS.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_HEARTRATE_MONITORING, Arrays.asList( - ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING, - ZeppOsConfigService.ConfigArg.HEART_RATE_HIGH_ALERTS, - ZeppOsConfigService.ConfigArg.HEART_RATE_LOW_ALERTS, - ZeppOsConfigService.ConfigArg.HEART_RATE_ACTIVITY_MONITORING, - ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING, - ZeppOsConfigService.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING, - ZeppOsConfigService.ConfigArg.STRESS_MONITORING, - ZeppOsConfigService.ConfigArg.STRESS_RELAXATION_REMINDER, - ZeppOsConfigService.ConfigArg.SPO2_ALL_DAY_MONITORING, - ZeppOsConfigService.ConfigArg.SPO2_LOW_ALERT + ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING.name(), + ZeppOsConfigService.ConfigArg.HEART_RATE_HIGH_ALERTS.name(), + ZeppOsConfigService.ConfigArg.HEART_RATE_LOW_ALERTS.name(), + ZeppOsConfigService.ConfigArg.HEART_RATE_ACTIVITY_MONITORING.name(), + ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING.name(), + ZeppOsConfigService.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING.name(), + ZeppOsConfigService.ConfigArg.STRESS_MONITORING.name(), + ZeppOsConfigService.ConfigArg.STRESS_RELAXATION_REMINDER.name(), + ZeppOsConfigService.ConfigArg.SPO2_ALL_DAY_MONITORING.name(), + ZeppOsConfigService.ConfigArg.SPO2_LOW_ALERT.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_INACTIVITY_EXTENDED, Arrays.asList( - ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_ENABLED, - ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START, - ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END, - ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED, - ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START, - ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_ENABLED.name(), + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START.name(), + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END.name(), + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED.name(), + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START.name(), + ZeppOsConfigService.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END.name() )); put(DeviceSettingsPreferenceConst.PREF_HEADER_GPS, Arrays.asList( - ZeppOsConfigService.ConfigArg.WORKOUT_GPS_PRESET, - ZeppOsConfigService.ConfigArg.WORKOUT_GPS_BAND, - ZeppOsConfigService.ConfigArg.WORKOUT_GPS_COMBINATION, - ZeppOsConfigService.ConfigArg.WORKOUT_GPS_SATELLITE_SEARCH, - ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED, - ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_TIME + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_PRESET.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_BAND.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_COMBINATION.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_GPS_SATELLITE_SEARCH.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_TIME.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_SOUND_AND_VIBRATION, Arrays.asList( - ZeppOsConfigService.ConfigArg.VOLUME, - ZeppOsConfigService.ConfigArg.CROWN_VIBRATION, - ZeppOsConfigService.ConfigArg.ALERT_TONE, - ZeppOsConfigService.ConfigArg.COVER_TO_MUTE, - ZeppOsConfigService.ConfigArg.VIBRATE_FOR_ALERT, - ZeppOsConfigService.ConfigArg.TEXT_TO_SPEECH + ZeppOsConfigService.ConfigArg.VOLUME.name(), + ZeppOsConfigService.ConfigArg.CROWN_VIBRATION.name(), + ZeppOsConfigService.ConfigArg.ALERT_TONE.name(), + ZeppOsConfigService.ConfigArg.COVER_TO_MUTE.name(), + ZeppOsConfigService.ConfigArg.VIBRATE_FOR_ALERT.name(), + ZeppOsConfigService.ConfigArg.TEXT_TO_SPEECH.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_DO_NOT_DISTURB, Arrays.asList( - ZeppOsConfigService.ConfigArg.DND_MODE, - ZeppOsConfigService.ConfigArg.DND_SCHEDULED_START, - ZeppOsConfigService.ConfigArg.DND_SCHEDULED_END + ZeppOsConfigService.ConfigArg.DND_MODE.name(), + ZeppOsConfigService.ConfigArg.DND_SCHEDULED_START.name(), + ZeppOsConfigService.ConfigArg.DND_SCHEDULED_END.name() )); put(DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT_DETECTION, Arrays.asList( - ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_CATEGORY, - ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_ALERT, - ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_SENSITIVITY + ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_CATEGORY.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_ALERT.name(), + ZeppOsConfigService.ConfigArg.WORKOUT_DETECTION_SENSITIVITY.name() )); put(DeviceSettingsPreferenceConst.PREF_SCREEN_OFFLINE_VOICE, Arrays.asList( - ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_TURN_WRIST, - ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_SCREEN_ON, - ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING, - ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_LANGUAGE + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_TURN_WRIST.name(), + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPOND_SCREEN_ON.name(), + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING.name(), + ZeppOsConfigService.ConfigArg.OFFLINE_VOICE_LANGUAGE.name() + )); + put(DeviceSettingsPreferenceConst.PREF_SCREEN_MORNING_UPDATES, Arrays.asList( + DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED, + DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE )); }}; - for (final Map.Entry> configScreen : configScreens.entrySet()) { + for (final Map.Entry> configScreen : configScreens.entrySet()) { hidePrefIfNoConfigSupported( handler, prefs, configScreen.getKey(), - configScreen.getValue().toArray(new ZeppOsConfigService.ConfigArg[0]) + configScreen.getValue().toArray(new String[0]) ); } @@ -368,7 +373,7 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { } // Get the list of possible values for this preference, as reported by the band - final List possibleValues = prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(prefKey), null); + final List possibleValues = prefs.getList(Huami2021Coordinator.getPrefPossibleValuesKey(prefKey), null); if (possibleValues == null || possibleValues.isEmpty()) { // The band hasn't reported this setting, so we don't know the possible values. // Hide it @@ -425,14 +430,15 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { private void hidePrefIfNoConfigSupported(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String prefToHide, - final ZeppOsConfigService.ConfigArg... configs) { + final String... supportedPref) { final Preference pref = handler.findPreference(prefToHide); if (pref == null) { return; } - for (final ZeppOsConfigService.ConfigArg config : configs) { - if (ZeppOsConfigService.deviceHasConfig(prefs, config)) { + for (final String prefKey : supportedPref) { + final boolean deviceHasConfig = prefs.getBoolean(Huami2021Coordinator.getPrefKnownConfig(prefKey), false); + if (deviceHasConfig) { // This preference is supported, don't hide return; } 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 3d67413fe..c3a767972 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 @@ -123,6 +123,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service 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.ZeppOsMorningUpdatesService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; @@ -153,6 +154,8 @@ public abstract class Huami2021Support extends HuamiSupport { private final ZeppOsWifiService wifiService = new ZeppOsWifiService(this); private final ZeppOsFtpServerService ftpServerService = new ZeppOsFtpServerService(this); private final ZeppOsContactsService contactsService = new ZeppOsContactsService(this); + private final ZeppOsMorningUpdatesService morningUpdatesService = new ZeppOsMorningUpdatesService(this); + private final Map mServiceMap = new HashMap() {{ put(fileUploadService.getEndpoint(), fileUploadService); put(configService.getEndpoint(), configService); @@ -160,6 +163,7 @@ public abstract class Huami2021Support extends HuamiSupport { put(wifiService.getEndpoint(), wifiService); put(ftpServerService.getEndpoint(), ftpServerService); put(contactsService.getEndpoint(), contactsService); + put(morningUpdatesService.getEndpoint(), morningUpdatesService); }}; public Huami2021Support() { @@ -231,6 +235,21 @@ public abstract class Huami2021Support extends HuamiSupport { return; } + // morning updates preferences, they do not use the configService + switch (config) { + case DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED: + final boolean morningUpdatesEnabled = prefs.getBoolean(config, false); + LOG.info("Setting morning updates enabled = {}", morningUpdatesEnabled); + morningUpdatesService.setEnabled(morningUpdatesEnabled); + return; + case DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE: + final List categories = new ArrayList<>(prefs.getList(config, Collections.emptyList())); + final List allCategories = new ArrayList<>(prefs.getList(Huami2021Coordinator.getPrefPossibleValuesKey(config), Collections.emptyList())); + LOG.info("Setting morning updates categories = {}", categories); + morningUpdatesService.setCategories(categories, allCategories); + return; + } + try { if (configService.setConfig(prefs, config, configSetter)) { // If the ConfigSetter was able to set the config, just write it and return @@ -1134,7 +1153,7 @@ public abstract class Huami2021Support extends HuamiSupport { setDisplayItems2021( builder, DISPLAY_ITEMS_MENU, - new ArrayList<>(prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList())), + new ArrayList<>(prefs.getList(Huami2021Coordinator.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList())), new ArrayList<>(prefs.getList(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, Collections.emptyList())) ); return this; @@ -1147,7 +1166,7 @@ public abstract class Huami2021Support extends HuamiSupport { setDisplayItems2021( builder, DISPLAY_ITEMS_SHORTCUTS, - new ArrayList<>(prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(HuamiConst.PREF_SHORTCUTS_SORTABLE), Collections.emptyList())), + new ArrayList<>(prefs.getList(Huami2021Coordinator.getPrefPossibleValuesKey(HuamiConst.PREF_SHORTCUTS_SORTABLE), Collections.emptyList())), new ArrayList<>(prefs.getList(HuamiConst.PREF_SHORTCUTS_SORTABLE, Collections.emptyList())) ); return this; @@ -1159,7 +1178,7 @@ public abstract class Huami2021Support extends HuamiSupport { setDisplayItems2021( builder, DISPLAY_ITEMS_CONTROL_CENTER, - new ArrayList<>(prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(HuamiConst.PREF_CONTROL_CENTER_SORTABLE), Collections.emptyList())), + new ArrayList<>(prefs.getList(Huami2021Coordinator.getPrefPossibleValuesKey(HuamiConst.PREF_CONTROL_CENTER_SORTABLE), Collections.emptyList())), new ArrayList<>(prefs.getList(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, Collections.emptyList())) ); return this; @@ -1465,6 +1484,8 @@ public abstract class Huami2021Support extends HuamiSupport { requestAlarms(builder); //requestReminders(builder); //contactsService.requestCapabilities(builder); + morningUpdatesService.getEnabled(builder); + morningUpdatesService.getCategories(builder); } @Override @@ -1813,7 +1834,7 @@ public abstract class Huami2021Support extends HuamiSupport { LOG.error("Unknown display items type {}", String.format("0x%x", payload[1])); return; } - final String allScreensPrefKey = ZeppOsConfigService.getPrefPossibleValuesKey(prefKey); + final String allScreensPrefKey = Huami2021Coordinator.getPrefPossibleValuesKey(prefKey); final boolean menuHasMoreSection; 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 6228de9e5..32e183e6f 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 @@ -51,4 +51,20 @@ public abstract class AbstractZeppOsService { protected void write(final TransactionBuilder builder, final byte[] data) { this.mSupport.writeToChunked2021(builder, getEndpoint(), data, isEncrypted()); } + + protected static Boolean booleanFromByte(final byte b) { + switch (b) { + case 0x00: + return false; + case 0x01: + return true; + default: + } + + return null; + } + + protected byte bool(final boolean bool) { + return (byte) (bool ? 0x01 : 0x00); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java index e7d571672..a7f3e625e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsConfigService.java @@ -64,6 +64,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift; import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLiftSensitivity; import nodomain.freeyourgadget.gadgetbridge.devices.huami.AlwaysOnDisplay; +import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; @@ -616,22 +617,8 @@ public class ZeppOsConfigService extends AbstractZeppOsService { return String.format(Locale.ROOT, "%s_huami_2021_max", key); } - /** - * Returns the preference key where to save the list of possible value for a preference, comma-separated. - */ - public static String getPrefPossibleValuesKey(final String key) { - return String.format(Locale.ROOT, "%s_huami_2021_possible_values", key); - } - - /** - * Returns the preference key where to that a config was reported as supported (boolean). - */ - public static String getPrefKnownConfig(final ConfigArg pref) { - return String.format(Locale.ROOT, "huami_2021_known_config_%s", pref.name()); - } - public static boolean deviceHasConfig(final Prefs devicePrefs, final ZeppOsConfigService.ConfigArg config) { - return devicePrefs.getBoolean(getPrefKnownConfig(config), false); + return devicePrefs.getBoolean(Huami2021Coordinator.getPrefKnownConfig(config.name()), false); } public ConfigSetter newSetter() { @@ -941,7 +928,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService { } if (configArg != null && argPrefs != null && configType == configArg.getConfigType(mGroupVersions)) { - prefs.put(getPrefKnownConfig(configArg), true); + prefs.put(Huami2021Coordinator.getPrefKnownConfig(configArg.name()), true); // Special cases for "follow phone" preferences. We need to ensure that "auto" // always has precedence @@ -1030,7 +1017,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService { prefs = singletonMap(configArg.getPrefKey(), decoder.decode(str.getValue())); if (includesConstraints) { prefs.put( - getPrefPossibleValuesKey(configArg.getPrefKey()), + Huami2021Coordinator.getPrefPossibleValuesKey(configArg.getPrefKey()), decodeStringValues(possibleValues, decoder) ); } @@ -1109,7 +1096,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService { prefs = singletonMap(configArg.getPrefKey(), new HashSet<>(valuesList)); if (includesConstraints) { prefs.put( - getPrefPossibleValuesKey(configArg.getPrefKey()), + Huami2021Coordinator.getPrefPossibleValuesKey(configArg.getPrefKey()), String.join(",", decodeByteValues(possibleValues, decoder)) ); } @@ -1145,7 +1132,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService { possibleLanguages.add(languageByteToLocale(possibleValue)); } possibleLanguages.removeAll(Collections.singleton(null)); - prefs.put(getPrefPossibleValuesKey(configArg.getPrefKey()), String.join(",", possibleLanguages)); + prefs.put(Huami2021Coordinator.getPrefPossibleValuesKey(configArg.getPrefKey()), String.join(",", possibleLanguages)); } } decoder = null; @@ -1201,7 +1188,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService { prefs = singletonMap(configArg.getPrefKey(), decoder.decode(value.getValue())); if (includesConstraints) { prefs.put( - getPrefPossibleValuesKey(configArg.getPrefKey()), + Huami2021Coordinator.getPrefPossibleValuesKey(configArg.getPrefKey()), String.join(",", decodeByteValues(possibleValues, decoder)) ); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java new file mode 100644 index 000000000..a33248e25 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsMorningUpdatesService.java @@ -0,0 +1,208 @@ +/* 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.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; +import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; +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.MapUtils; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class ZeppOsMorningUpdatesService extends AbstractZeppOsService { + private static final Logger LOG = LoggerFactory.getLogger(ZeppOsMorningUpdatesService.class); + + private static final short ENDPOINT = 0x003f; + + private static final byte CMD_ENABLED_GET = 0x03; + private static final byte CMD_ENABLED_RET = 0x04; + private static final byte CMD_ENABLED_SET = 0x07; + private static final byte CMD_ENABLED_SET_ACK = 0x08; + private static final byte CMD_CATEGORIES_REQUEST = 0x05; + private static final byte CMD_CATEGORIES_RESPONSE = 0x06; + private static final byte CMD_CATEGORIES_SET = 0x09; + private static final byte CMD_CATEGORIES_SET_ACK = 0x0a; + + private static final Map MORNING_UPDATES_MAP = new HashMap() {{ + put((byte) 0x02, "weather"); + put((byte) 0x03, "battery"); + put((byte) 0x04, "sleep"); + put((byte) 0x06, "event"); + put((byte) 0x07, "pai"); + put((byte) 0x08, "yesterdays_activity"); + put((byte) 0x0a, "cycle_tracking"); + }}; + + public ZeppOsMorningUpdatesService(Huami2021Support support) { + super(support); + } + + @Override + public short getEndpoint() { + return ENDPOINT; + } + + @Override + public boolean isEncrypted() { + return false; + } + + @Override + public void handlePayload(byte[] payload) { + switch (payload[0]) { + case CMD_ENABLED_RET: + if (payload[1] != 0x01) { + LOG.warn("Unexpected morning updates enabled byte {}", payload[1]); + return; + } + final Boolean enabled = booleanFromByte(payload[2]); + if (enabled == null) { + LOG.error("Unexpected morning updates enabled byte"); + return; + } + final GBDeviceEventUpdatePreferences gbDeviceEventUpdatePreferences = new GBDeviceEventUpdatePreferences() + .withPreference(Huami2021Coordinator.getPrefKnownConfig(DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED), true) + .withPreference(DeviceSettingsPreferenceConst.MORNING_UPDATES_ENABLED, enabled); + getSupport().evaluateGBDeviceEvent(gbDeviceEventUpdatePreferences); + LOG.info("Morning updates enabled = {}", enabled); + return; + case CMD_ENABLED_SET_ACK: + LOG.info("Morning updates enabled set ack ACK, status = {}", payload[1]); + return; + case CMD_CATEGORIES_RESPONSE: + LOG.info("Got morning update items from watch"); + decodeAndUpdateCategories(payload); + return; + case CMD_CATEGORIES_SET_ACK: + LOG.info("Morning updates categories set ack ACK, status = {}", payload[1]); + return; + default: + LOG.warn("Unexpected morning updates byte {}", String.format("0x%02x", payload[0])); + } + } + + public void getEnabled(final TransactionBuilder builder) { + write(builder, CMD_ENABLED_GET); + } + + public void setEnabled(final boolean enabled) { + write("set morning updates enabled", new byte[] {CMD_ENABLED_SET, bool(enabled)}); + } + + public void getCategories(final TransactionBuilder builder) { + write(builder, CMD_CATEGORIES_REQUEST); + } + + public void setCategories(final List categories, final List allCategories) { + // Build a sorted list, with the enabled at the start + final List categoriesSorted = new ArrayList<>(categories); + for (String category : allCategories) { + if (!categories.contains(category)) { + categoriesSorted.add(category); + } + } + + final Map idMap = MapUtils.reverse(MORNING_UPDATES_MAP); + + final ByteBuffer buf = ByteBuffer.allocate(2 + categoriesSorted.size() * 3) + .order(ByteOrder.LITTLE_ENDIAN); + + buf.put(CMD_CATEGORIES_SET); + buf.put((byte) categoriesSorted.size()); + + for (final String category : categoriesSorted) { + final byte id; + if (idMap.containsKey(category)) { + id = idMap.get(category); + } else { + // name doesn't match a known value, attempt to parse it as hex + final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]{1,2})$").matcher(category); + if (matcher.find()) { + id = (byte) Integer.parseInt(matcher.group(1), 16); + } else { + LOG.warn("Unknown category {}, and failed to parse as hex, not setting", category); + return; + } + } + + buf.put(id); + buf.put((byte) 0x00); + buf.put(bool(categories.contains(category))); + } + + write("set morning updates categories", buf.array()); + } + + private void decodeAndUpdateCategories(final byte[] payload) { + if (payload[1] != 0x01) { + LOG.warn("Unexpected morning update items byte {}", payload[1]); + return; + } + + final int numCategories = payload[2]; + final int expectedLength = 3 + numCategories * 3; + if (payload.length != expectedLength) { + LOG.warn("Unexpected morning updates categories payload size {}, expected {}", payload.length, expectedLength); + return; + } + + final List enabledCategories = new ArrayList<>(numCategories); + final List allCategories = new ArrayList<>(numCategories); + + for (int i = 3; i < expectedLength; i += 3) { + final String itemName = MORNING_UPDATES_MAP.containsKey(payload[i]) + ? MORNING_UPDATES_MAP.get(payload[i]) + : String.format("0x%x", payload[i]); + + allCategories.add(itemName); + + // i + 1 is always 0 + final Boolean itemEnabled = booleanFromByte(payload[i + 2]); + if (itemEnabled == null) { + LOG.warn("Unexpected enabled byte {} for item at i={}", payload[i + 2], i); + } else if(itemEnabled) { + enabledCategories.add(itemName); + } + } + + final String prefKey = DeviceSettingsPreferenceConst.MORNING_UPDATES_CATEGORIES_SORTABLE; + final String allCategoriesPrefKey = Huami2021Coordinator.getPrefPossibleValuesKey(prefKey); + + final String allCategoriesPrefValue = StringUtils.join(",", allCategories.toArray(new String[0])).toString(); + final String prefValue = StringUtils.join(",", enabledCategories.toArray(new String[0])).toString(); + final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences() + .withPreference(Huami2021Coordinator.getPrefKnownConfig(prefKey), true) + .withPreference(allCategoriesPrefKey, allCategoriesPrefValue) + .withPreference(prefKey, prefValue); + + getSupport().evaluateGBDeviceEvent(eventUpdatePreferences); + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 8b23f3045..7c3bc15ce 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -676,6 +676,25 @@ rowing_machine + + @string/menuitem_weather + @string/battery + @string/menuitem_sleep + @string/menuitem_events + @string/menuitem_pai + @string/yesterdays_activity + @string/menuitem_cycles + + + weather + battery + sleep + event + pai + yesterdays_activity + cycle_tracking + + @string/accuracy @string/balanced diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a1c5e8ab..2b978ebd0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -467,6 +467,10 @@ AGPS Expire Time Camera Remote Allows the watch to trigger the phone\'s camera + Morning Updates + Display updates every morning + Morning Updates Categories + List of categories to display every morning Fitness app tracking Start/stop fitness app tracking on phone when a GPS workout is started on the band Send GPS during workout @@ -1333,6 +1337,7 @@ Eject Water Unknown (%s) [UNSUPPORTED] %s + Yesterday\'s Activity Minutes: Hours: Seconds: diff --git a/app/src/main/res/xml/devicesettings_morning_updates.xml b/app/src/main/res/xml/devicesettings_morning_updates.xml new file mode 100644 index 000000000..77bf4d84b --- /dev/null +++ b/app/src/main/res/xml/devicesettings_morning_updates.xml @@ -0,0 +1,28 @@ + + + + + + + + +