Zepp OS: Add support for morning updates

This commit is contained in:
José Rebelo 2023-04-01 20:58:21 +01:00
parent 4713c6511e
commit f1dd4019bf
12 changed files with 409 additions and 90 deletions

View File

@ -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

View File

@ -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";

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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<String, List<ZeppOsConfigService.ConfigArg>> configScreens = new HashMap<String, List<ZeppOsConfigService.ConfigArg>>() {{
final Map<String, List<String>> configScreens = new HashMap<String, List<String>>() {{
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<String, List<ZeppOsConfigService.ConfigArg>> configScreen : configScreens.entrySet()) {
for (final Map.Entry<String, List<String>> 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<String> possibleValues = prefs.getList(ZeppOsConfigService.getPrefPossibleValuesKey(prefKey), null);
final List<String> 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;
}

View File

@ -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<Short, AbstractZeppOsService> mServiceMap = new HashMap<Short, AbstractZeppOsService>() {{
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<String> categories = new ArrayList<>(prefs.getList(config, Collections.emptyList()));
final List<String> 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;

View File

@ -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);
}
}

View File

@ -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))
);
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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<Byte, String> MORNING_UPDATES_MAP = new HashMap<Byte, String>() {{
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<String> categories, final List<String> allCategories) {
// Build a sorted list, with the enabled at the start
final List<String> categoriesSorted = new ArrayList<>(categories);
for (String category : allCategories) {
if (!categories.contains(category)) {
categoriesSorted.add(category);
}
}
final Map<String, Byte> 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<String> enabledCategories = new ArrayList<>(numCategories);
final List<String> 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);
}
}

View File

@ -676,6 +676,25 @@
<item>rowing_machine</item>
</string-array>
<string-array name="pref_morning_updates_categories">
<item>@string/menuitem_weather</item>
<item>@string/battery</item>
<item>@string/menuitem_sleep</item>
<item>@string/menuitem_events</item>
<item>@string/menuitem_pai</item>
<item>@string/yesterdays_activity</item>
<item>@string/menuitem_cycles</item>
</string-array>
<string-array name="pref_morning_updates_categories_values">
<item>weather</item>
<item>battery</item>
<item>sleep</item>
<item>event</item>
<item>pai</item>
<item>yesterdays_activity</item>
<item>cycle_tracking</item>
</string-array>
<string-array name="pref_gps_mode_preset">
<item>@string/accuracy</item>
<item>@string/balanced</item>

View File

@ -467,6 +467,10 @@
<string name="pref_agps_expire_time">AGPS Expire Time</string>
<string name="pref_camera_remote_title">Camera Remote</string>
<string name="pref_camera_remote_summary">Allows the watch to trigger the phone\'s camera</string>
<string name="pref_morning_updates_title">Morning Updates</string>
<string name="pref_morning_updates_summary">Display updates every morning</string>
<string name="pref_morning_updates_categories_title">Morning Updates Categories</string>
<string name="pref_morning_updates_categories_summary">List of categories to display every morning</string>
<string name="pref_workout_start_on_phone_title">Fitness app tracking</string>
<string name="pref_workout_start_on_phone_summary">Start/stop fitness app tracking on phone when a GPS workout is started on the band</string>
<string name="pref_workout_send_gps_title">Send GPS during workout</string>
@ -1333,6 +1337,7 @@
<string name='menuitem_eject_water'>Eject Water</string>
<string name="menuitem_unknown_app">Unknown (%s)</string>
<string name="menuitem_unsupported">[UNSUPPORTED] %s</string>
<string name="yesterdays_activity">Yesterday\'s Activity</string>
<string name="watch9_time_minutes">Minutes:</string>
<string name="watch9_time_hours">Hours:</string>
<string name="watch9_time_seconds">Seconds:</string>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:icon="@drawable/ic_wb_sunny"
android:key="pref_morning_updates"
android:persistent="false"
android:summary="@string/pref_morning_updates_summary"
android:title="@string/pref_morning_updates_title">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_wb_sunny"
android:key="morning_updates_enabled"
android:summary="@string/pref_morning_updates_summary"
android:title="@string/pref_morning_updates_title" />
<com.mobeta.android.dslv.DragSortListPreference
android:defaultValue="@array/pref_huami2021_empty_array"
android:dialogTitle="@string/pref_morning_updates_categories_title"
android:entries="@array/pref_morning_updates_categories"
android:entryValues="@array/pref_morning_updates_categories_values"
android:icon="@drawable/ic_list_numbered"
android:key="morning_updates_categories"
android:persistent="true"
android:summary="@string/pref_morning_updates_categories_summary"
android:title="@string/pref_morning_updates_categories_title" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>