diff --git a/README.md b/README.md
index 333ed2cc2..5a53aff5e 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ vendor's servers.
- [Bip](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
- [Bip Lite](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip-Lite), [Bip S](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip-S), [Bip U](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip-U) [**\[!\]**](#special-pairing-procedures)
- [Cor](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor), [Cor 2](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor-2)
- - [GTR](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR), [GTR 2/2e](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR), [GTR 3](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR-3) [**\[!\]**](#special-pairing-procedures)
+ - [GTR](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR), [GTR 2/2e](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR), [GTR 3](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR-3), [GTR 4](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR-4) [**\[!\]**](#special-pairing-procedures)
- [GTS](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTS), [GTS 2/2e](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTS), [GTS 3](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTS-3) [**\[!\]**](#special-pairing-procedures)
- [Neo](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Neo) [**\[!\]**](#special-pairing-procedures)
- T-Rex [**\[!\]**](#special-pairing-procedures)
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
index 134b66efa..987f71bfd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
@@ -28,14 +28,12 @@ import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
-import ch.qos.logback.core.encoder.Encoder;
-import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
-import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.StatusPrinter;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
public abstract class Logging {
// Only used for tests
@@ -54,7 +52,7 @@ public abstract class Logging {
} else {
stopFileLogger();
}
- getLogger().info("Gadgetbridge version: {}", BuildConfig.VERSION_NAME);
+ getLogger().info("Gadgetbridge version: {}-{}", BuildConfig.VERSION_NAME, BuildConfig.GIT_HASH_SHORT);
} catch (Exception ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
stopFileLogger();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java
index b945753e3..efb7fbf1e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AboutUserPreferencesActivity.java
@@ -17,17 +17,13 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.activities;
-import android.os.Bundle;
-
-import androidx.preference.Preference;
-
-import nodomain.freeyourgadget.gadgetbridge.GBApplication;
-import nodomain.freeyourgadget.gadgetbridge.R;
-
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_ACTIVETIME_MINUTES;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_CALORIES_BURNT;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DISTANCE_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS;
+import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GOAL_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION;
@@ -36,6 +32,10 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH;
+import android.os.Bundle;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
public class AboutUserPreferencesActivity extends AbstractSettingsActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -47,12 +47,18 @@ public class AboutUserPreferencesActivity extends AbstractSettingsActivity {
addPreferenceHandlerFor(PREF_USER_WEIGHT_KG);
addPreferenceHandlerFor(PREF_USER_GENDER);
addPreferenceHandlerFor(PREF_USER_STEPS_GOAL);
+ addPreferenceHandlerFor(PREF_USER_GOAL_WEIGHT_KG);
+ addPreferenceHandlerFor(PREF_USER_GOAL_STANDING_TIME_HOURS);
+ addPreferenceHandlerFor(PREF_USER_GOAL_FAT_BURN_TIME_MINUTES);
addIntentNotificationListener(PREF_USER_STEPS_GOAL);
addIntentNotificationListener(PREF_USER_HEIGHT_CM);
addIntentNotificationListener(PREF_USER_SLEEP_DURATION);
addIntentNotificationListener(PREF_USER_STEP_LENGTH_CM);
addIntentNotificationListener(PREF_USER_DISTANCE_METERS);
+ addIntentNotificationListener(PREF_USER_GOAL_WEIGHT_KG);
+ addIntentNotificationListener(PREF_USER_GOAL_STANDING_TIME_HOURS);
+ addIntentNotificationListener(PREF_USER_GOAL_FAT_BURN_TIME_MINUTES);
}
@Override
@@ -66,6 +72,10 @@ public class AboutUserPreferencesActivity extends AbstractSettingsActivity {
PREF_USER_STEP_LENGTH_CM,
PREF_USER_ACTIVETIME_MINUTES,
PREF_USER_CALORIES_BURNT,
- PREF_USER_DISTANCE_METERS,};
+ PREF_USER_DISTANCE_METERS,
+ PREF_USER_GOAL_WEIGHT_KG,
+ PREF_USER_GOAL_STANDING_TIME_HOURS,
+ PREF_USER_GOAL_FAT_BURN_TIME_MINUTES
+ };
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java
index 7abeffff6..f736568a1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureReminders.java
@@ -86,7 +86,7 @@ public class ConfigureReminders extends AbstractGBActivity {
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
int reservedSlots = prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR, coordinator.supportsCalendarEvents() ? 0 : 9);
- int deviceSlots = coordinator.getReminderSlotCount() - reservedSlots;
+ int deviceSlots = coordinator.getReminderSlotCount(gbDevice) - reservedSlots;
if (mGBReminderListAdapter.getItemCount() >= deviceSlots) {
// No more free slots
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java
index 78406abd4..6d469dede 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FindPhoneActivity.java
@@ -18,7 +18,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -34,13 +33,11 @@ import android.os.Vibrator;
import android.view.View;
import android.widget.Button;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -50,18 +47,29 @@ import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
public class FindPhoneActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(FindPhoneActivity.class);
- public static final String ACTION_FOUND
- = "nodomain.freeyourgadget.gadgetbridge.findphone.action.reply";
+ public static final String ACTION_FOUND = "nodomain.freeyourgadget.gadgetbridge.findphone.action.reply";
+ public static final String ACTION_VIBRATE = "nodomain.freeyourgadget.gadgetbridge.findphone.action.vibrate";
+ public static final String ACTION_RING = "nodomain.freeyourgadget.gadgetbridge.findphone.action.ring";
+
+ public static final String EXTRA_RING = "extra_ring";
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action != null) {
+ LOG.info("Got action: {}", action);
+
switch (action) {
- case ACTION_FOUND: {
+ case ACTION_FOUND:
finish();
break;
- }
+ case ACTION_VIBRATE:
+ stopSound();
+ break;
+ case ACTION_RING:
+ playRingtone();
+ break;
}
}
}
@@ -77,8 +85,12 @@ public class FindPhoneActivity extends AbstractGBActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find_phone);
+ final boolean ring = getIntent().getBooleanExtra(EXTRA_RING, true);
+
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_FOUND);
+ filter.addAction(ACTION_VIBRATE);
+ filter.addAction(ACTION_RING);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter); // for ACTION_FOUND
@@ -93,7 +105,10 @@ public class FindPhoneActivity extends AbstractGBActivity {
GB.removeNotification(GB.NOTIFICATION_ID_PHONE_FIND, this);
vibrate();
- playRingtone();
+ if (ring) {
+ playRingtone();
+ }
+ GBApplication.deviceService().onFindPhone(true);
}
private void vibrate(){
@@ -115,7 +130,12 @@ public class FindPhoneActivity extends AbstractGBActivity {
if (mAudioManager != null) {
userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
}
- mp = new MediaPlayer();
+ if (mp != null && mp.isPlaying()) {
+ LOG.warn("Already playing");
+ return;
+ } else if (mp == null) {
+ mp = new MediaPlayer();
+ }
if (!playConfiguredRingtone()) {
playFallbackRingtone();
@@ -172,19 +192,24 @@ public class FindPhoneActivity extends AbstractGBActivity {
}
private void stopSound() {
- mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND);
- mp.stop();
- mp.reset();
- mp.release();
+ if (mAudioManager != null) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND);
+ }
+ if (mp != null) {
+ mp.stop();
+ mp.reset();
+ mp.release();
+ mp = null;
+ }
}
+
@Override
protected void onDestroy() {
super.onDestroy();
stopVibration();
stopSound();
-
- GBApplication.deviceService().onPhoneFound();
+ GBApplication.deviceService().onFindPhone(false);
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver);
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 ac0970391..72e25d9df 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
@@ -17,6 +17,25 @@
package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
public class DeviceSettingsPreferenceConst {
+ public static final String PREF_HEADER_TIME = "pref_header_time";
+ public static final String PREF_HEADER_DISPLAY = "pref_header_display";
+ public static final String PREF_HEADER_HEALTH = "pref_header_health";
+ public static final String PREF_HEADER_WORKOUT = "pref_header_workout";
+ public static final String PREF_HEADER_WORKOUT_DETECTION = "pref_header_workout_detection";
+ public static final String PREF_HEADER_GPS = "pref_header_gps";
+ public static final String PREF_HEADER_AGPS = "pref_header_agps";
+
+ public static final String PREF_SCREEN_NIGHT_MODE = "pref_screen_night_mode";
+ public static final String PREF_SCREEN_SLEEP_MODE = "pref_screen_sleep_mode";
+ public static final String PREF_SCREEN_LIFT_WRIST = "pref_screen_lift_wrist";
+ public static final String PREF_SCREEN_PASSWORD = "pref_screen_password";
+ public static final String PREF_SCREEN_ALWAYS_ON_DISPLAY = "pref_screen_always_on_display";
+ public static final String PREF_SCREEN_HEARTRATE_MONITORING = "pref_screen_heartrate_monitoring";
+ public static final String PREF_SCREEN_INACTIVITY_EXTENDED = "pref_screen_inactivity_extended";
+ public static final String PREF_SCREEN_SOUND_AND_VIBRATION = "pref_screen_sound_and_vibration";
+ public static final String PREF_SCREEN_DO_NOT_DISTURB = "pref_screen_do_not_disturb";
+ public static final String PREF_SCREEN_OFFLINE_VOICE = "pref_screen_offline_voice";
+
public static final String PREF_LANGUAGE = "language";
public static final String PREF_LANGUAGE_AUTO = "auto";
public static final String PREF_DATEFORMAT = "dateformat";
@@ -25,9 +44,11 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_TIMEFORMAT_12H = "am/pm";
public static final String PREF_TIMEFORMAT_AUTO = "auto";
public static final String PREF_WEARLOCATION = "wearlocation";
+ public static final String PREF_WEARDIRECTION = "weardirection";
public static final String PREF_VIBRATION_ENABLE = "vibration_enable";
public static final String PREF_NOTIFICATION_ENABLE = "notification_enable";
public static final String PREF_SCREEN_BRIGHTNESS = "screen_brightness";
+ public static final String PREF_SCREEN_AUTO_BRIGHTNESS = "screen_auto_brightness";
public static final String PREF_SCREEN_ORIENTATION = "screen_orientation";
public static final String PREF_SCREEN_TIMEOUT = "screen_timeout";
public static final String PREF_RESERVER_ALARMS_CALENDAR = "reserve_alarms_calendar";
@@ -44,6 +65,8 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_BUTTON_1_FUNCTION_DOUBLE = "button_1_function_double";
public static final String PREF_BUTTON_2_FUNCTION_DOUBLE = "button_2_function_double";
public static final String PREF_BUTTON_3_FUNCTION_DOUBLE = "button_3_function_double";
+ public static final String PREF_UPPER_BUTTON_LONG_PRESS = "pref_button_action_upper_long";
+ public static final String PREF_LOWER_BUTTON_SHORT_PRESS = "pref_button_action_lower_short";
public static final String PREF_VIBRATION_STRENGH_PERCENTAGE = "vibration_strength";
public static final String PREF_RELAX_FIRMWARE_CHECKS = "relax_firmware_checks";
@@ -80,10 +103,26 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_ALWAYS_ON_DISPLAY_AUTOMATIC = "automatic";
public static final String PREF_ALWAYS_ON_DISPLAY_ALWAYS = "always";
public static final String PREF_ALWAYS_ON_DISPLAY_SCHEDULED = "scheduled";
+ public static final String PREF_ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE = "always_on_display_follow_watchface";
+ public static final String PREF_ALWAYS_ON_DISPLAY_STYLE = "always_on_display_style";
+
+ public static final String PREF_VOLUME = "volume";
+ public static final String PREF_CROWN_VIBRATION = "crown_vibration";
+ public static final String PREF_ALERT_TONE = "alert_tone";
+ public static final String PREF_COVER_TO_MUTE = "cover_to_mute";
+ public static final String PREF_VIBRATE_FOR_ALERT = "vibrate_for_alert";
+ public static final String PREF_TEXT_TO_SPEECH = "text_to_speech";
+
+ public static final String PREF_OFFLINE_VOICE_RESPOND_TURN_WRIST = "offline_voice_respond_turn_wrist";
+ public static final String PREF_OFFLINE_VOICE_RESPOND_SCREEN_ON = "offline_voice_respond_screen_on";
+ public static final String PREF_OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING = "offline_voice_response_during_screen_lighting";
+ public static final String PREF_OFFLINE_VOICE_LANGUAGE = "offline_voice_language";
public static final String PREF_SLEEP_TIME = "prefs_enable_sleep_time";
public static final String PREF_SLEEP_TIME_START = "prefs_sleep_time_start";
public static final String PREF_SLEEP_TIME_END = "prefs_sleep_time_end";
+ public static final String PREF_SLEEP_MODE_SLEEP_SCREEN = "pref_sleep_mode_sleep_screen";
+ public static final String PREF_SLEEP_MODE_SMART_ENABLE = "pref_sleep_mode_smart_enable";
public static final String PREF_LIFTWRIST_NOSHED = "activate_display_on_lift_wrist_noshed";
public static final String PREF_DISCONNECTNOTIF_NOSHED = "disconnect_notification_noshed";
@@ -140,6 +179,16 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_WORKOUT_START_ON_PHONE = "workout_start_on_phone";
public static final String PREF_WORKOUT_SEND_GPS_TO_BAND = "workout_send_gps_to_band";
+ public static final String PREF_WORKOUT_DETECTION_CATEGORIES = "workout_detection_categories";
+ public static final String PREF_WORKOUT_DETECTION_ALERT = "workout_detection_alert";
+ public static final String PREF_WORKOUT_DETECTION_SENSITIVITY = "workout_detection_sensitivity";
+
+ public static final String PREF_GPS_MODE_PRESET = "pref_gps_mode_preset";
+ public static final String PREF_GPS_BAND = "pref_gps_band";
+ public static final String PREF_GPS_COMBINATION = "pref_gps_combination";
+ public static final String PREF_GPS_SATELLITE_SEARCH = "pref_gps_satellite_search";
+ public static final String PREF_AGPS_EXPIRY_REMINDER_ENABLED = "pref_agps_expiry_reminder_enabled";
+ public static final String PREF_AGPS_EXPIRY_REMINDER_TIME = "pref_agps_expiry_reminder_time";
public static final String PREF_FIND_PHONE = "prefs_find_phone";
public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration";
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 7ad46a05b..e3c8e1c84 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
@@ -66,6 +66,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_CONTROL_CENTER_SORTABLE;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_SELECTION_BROADCAST;
@@ -360,6 +361,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
addPreferenceHandlerFor(PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE);
addPreferenceHandlerFor(PREF_SHORTCUTS);
addPreferenceHandlerFor(PREF_SHORTCUTS_SORTABLE);
+ addPreferenceHandlerFor(PREF_CONTROL_CENTER_SORTABLE);
addPreferenceHandlerFor(PREF_LANGUAGE);
addPreferenceHandlerFor(PREF_EXPOSE_HR_THIRDPARTY);
addPreferenceHandlerFor(PREF_BT_CONNECTED_ADVERTISEMENT);
@@ -367,9 +369,12 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
addPreferenceHandlerFor(PREF_VIBRATION_ENABLE);
addPreferenceHandlerFor(PREF_NOTIFICATION_ENABLE);
addPreferenceHandlerFor(PREF_SCREEN_BRIGHTNESS);
+ addPreferenceHandlerFor(PREF_SCREEN_AUTO_BRIGHTNESS);
addPreferenceHandlerFor(PREF_SCREEN_ORIENTATION);
addPreferenceHandlerFor(PREF_SCREEN_TIMEOUT);
addPreferenceHandlerFor(PREF_TIMEFORMAT);
+ addPreferenceHandlerFor(PREF_UPPER_BUTTON_LONG_PRESS);
+ addPreferenceHandlerFor(PREF_LOWER_BUTTON_SHORT_PRESS);
addPreferenceHandlerFor(PREF_BUTTON_1_FUNCTION_SHORT);
addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION_SHORT);
addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION_SHORT);
@@ -431,6 +436,9 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
addPreferenceHandlerFor(PREF_AMPM_ENABLED);
addPreferenceHandlerFor(PREF_SOUNDS);
+ addPreferenceHandlerFor(PREF_SLEEP_MODE_SLEEP_SCREEN);
+ addPreferenceHandlerFor(PREF_SLEEP_MODE_SMART_ENABLE);
+
addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES);
addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR);
addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES);
@@ -511,6 +519,32 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
addPreferenceHandlerFor(PREF_HOURLY_CHIME_START);
addPreferenceHandlerFor(PREF_HOURLY_CHIME_END);
+ addPreferenceHandlerFor(PREF_WORKOUT_DETECTION_CATEGORIES);
+ addPreferenceHandlerFor(PREF_WORKOUT_DETECTION_ALERT);
+ addPreferenceHandlerFor(PREF_WORKOUT_DETECTION_SENSITIVITY);
+
+ addPreferenceHandlerFor(PREF_GPS_MODE_PRESET);
+ addPreferenceHandlerFor(PREF_GPS_BAND);
+ addPreferenceHandlerFor(PREF_GPS_COMBINATION);
+ addPreferenceHandlerFor(PREF_GPS_SATELLITE_SEARCH);
+ addPreferenceHandlerFor(PREF_AGPS_EXPIRY_REMINDER_ENABLED);
+ addPreferenceHandlerFor(PREF_AGPS_EXPIRY_REMINDER_TIME);
+ addPreferenceHandlerFor(PREF_ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE);
+ addPreferenceHandlerFor(PREF_ALWAYS_ON_DISPLAY_STYLE);
+ addPreferenceHandlerFor(PREF_WEARDIRECTION);
+
+ addPreferenceHandlerFor(PREF_VOLUME);
+ addPreferenceHandlerFor(PREF_CROWN_VIBRATION);
+ addPreferenceHandlerFor(PREF_ALERT_TONE);
+ addPreferenceHandlerFor(PREF_COVER_TO_MUTE);
+ addPreferenceHandlerFor(PREF_VIBRATE_FOR_ALERT);
+ addPreferenceHandlerFor(PREF_TEXT_TO_SPEECH);
+
+ addPreferenceHandlerFor(PREF_OFFLINE_VOICE_RESPOND_TURN_WRIST);
+ addPreferenceHandlerFor(PREF_OFFLINE_VOICE_RESPOND_SCREEN_ON);
+ addPreferenceHandlerFor(PREF_OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING);
+ addPreferenceHandlerFor(PREF_OFFLINE_VOICE_LANGUAGE);
+
String sleepTimeState = prefs.getString(PREF_SLEEP_TIME, PREF_DO_NOT_DISTURB_OFF);
boolean sleepTimeScheduled = sleepTimeState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
index f92bf0eb4..83274480f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
@@ -471,7 +471,7 @@ public class GBDeviceAdapterv2 extends ListAdapter 0 ? View.VISIBLE : View.GONE);
+ holder.setRemindersView.setVisibility(coordinator.getReminderSlotCount(device) > 0 ? View.VISIBLE : View.GONE);
holder.setRemindersView.setOnClickListener(new View.OnClickListener()
{
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java
new file mode 100644
index 000000000..e0d40b0d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/GpsCapability.java
@@ -0,0 +1,45 @@
+/* Copyright (C) 2022 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.capabilities;
+
+public class GpsCapability {
+ public enum Preset {
+ ACCURACY,
+ BALANCED,
+ POWER_SAVING,
+ CUSTOM
+ }
+
+ public enum Band {
+ SINGLE_BAND,
+ DUAL_BAND
+ }
+
+ public enum Combination {
+ LOW_POWER_GPS,
+ GPS,
+ GPS_BDS,
+ GPS_GNOLASS,
+ GPS_GALILEO,
+ ALL_SATELLITES
+ }
+
+ public enum SatelliteSearch {
+ SPEED_FIRST,
+ ACCURACY_FIRST
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java
new file mode 100644
index 000000000..0193a6d40
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/WorkoutDetectionCapability.java
@@ -0,0 +1,36 @@
+/* Copyright (C) 2022 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.capabilities;
+
+public class WorkoutDetectionCapability {
+ public enum Category {
+ WALKING,
+ INDOOR_WALKING,
+ OUTDOOR_RUNNING,
+ TREADMILL,
+ OUTDOOR_CYCLING,
+ POOL_SWIMMING,
+ ELLIPTICAL,
+ ROWING_MACHINE
+ }
+
+ public enum Sensitivity {
+ HIGH,
+ STANDARD,
+ LOW
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
index d6625e146..13992c094 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
@@ -624,7 +624,7 @@ public class DBHelper {
int reservedSlots = prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR, coordinator.supportsCalendarEvents() ? 0 : 9);
- final int reminderSlots = coordinator.getReminderSlotCount();
+ final int reminderSlots = coordinator.getReminderSlotCount(gbDevice);
try (DBHandler db = GBApplication.acquireDB()) {
final DaoSession daoSession = db.getDaoSession();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java
index 16948401e..066a58b43 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventFindPhone.java
@@ -23,6 +23,9 @@ public class GBDeviceEventFindPhone extends GBDeviceEvent {
public enum Event {
UNKNOWN,
START,
- STOP
+ START_VIBRATE,
+ STOP,
+ VIBRATE,
+ RING,
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java
index fac8f9d02..0bdccdcc5 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java
@@ -70,6 +70,8 @@ public class GBDeviceEventUpdatePreferences extends GBDeviceEvent {
if (value == null) {
editor.remove(key);
+ } else if (value instanceof Short) {
+ editor.putInt(key, (Short) value);
} else if (value instanceof Integer) {
editor.putInt(key, (Integer) value);
} else if (value instanceof Boolean) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index 748f6c841..ff2e85a35 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -250,7 +250,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 0;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index 6eee5375d..2efa012ee 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -385,7 +385,7 @@ public interface DeviceCoordinator {
/**
* Indicates the maximum number of reminder slots available in the device.
*/
- int getReminderSlotCount();
+ int getReminderSlotCount(GBDevice device);
/**
* Indicates the maximum number of slots available for world clocks in the device.
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
index f60edb325..db2d1af49 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
@@ -92,7 +92,7 @@ public interface EventHandler {
void onFindDevice(boolean start);
- void onPhoneFound();
+ void onFindPhone(boolean start);
void onSetConstantVibration(int integer);
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 ef7954f07..e9a15a018 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
@@ -18,6 +18,9 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huami;
import androidx.annotation.NonNull;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -34,6 +37,9 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
public abstract class Huami2021Coordinator extends HuamiCoordinator {
@Override
@@ -119,23 +125,16 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator {
}
@Override
- public int getReminderSlotCount() {
- return 50;
+ public int getReminderSlotCount(final GBDevice device) {
+ return getPrefs(device).getInt(Huami2021Service.REMINDERS_PREF_CAPABILITY, 0);
}
@Override
public String[] getSupportedLanguageSettings(final GBDevice device) {
- return new String[]{
- "auto",
- "de_DE",
- "en_US",
- "es_ES",
- "fr_FR",
- "it_IT",
- "nl_NL",
- "pt_PT",
- "tr_TR",
- };
+ // Return all known languages by default. Unsupported languages will be removed by Huami2021SettingsCustomizer
+ final List allLanguages = new ArrayList<>(HuamiLanguageType.idLookup.keySet());
+ allLanguages.add(0, "auto");
+ return allLanguages.toArray(new String[0]);
}
@Override
@@ -145,64 +144,111 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator {
@Override
public List getHeartRateMeasurementIntervals() {
- return Arrays.asList(
- HeartRateCapability.MeasurementInterval.OFF,
- HeartRateCapability.MeasurementInterval.SMART,
- HeartRateCapability.MeasurementInterval.MINUTES_1,
- HeartRateCapability.MeasurementInterval.MINUTES_10,
- HeartRateCapability.MeasurementInterval.MINUTES_30
- );
+ // Return all known by default. Unsupported will be removed by Huami2021SettingsCustomizer
+ return Arrays.asList(HeartRateCapability.MeasurementInterval.values());
}
+ /**
+ * Returns a superset of all settings supported by Zepp OS Devices. Unsupported settings are removed
+ * by {@link Huami2021SettingsCustomizer}.
+ */
@Override
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
- return new int[]{
- R.xml.devicesettings_header_time,
- //R.xml.devicesettings_timeformat,
- R.xml.devicesettings_dateformat_2,
- // TODO R.xml.devicesettings_world_clocks,
+ final List settings = new ArrayList<>();
- R.xml.devicesettings_header_display,
- R.xml.devicesettings_huami2021_displayitems,
- R.xml.devicesettings_huami2021_shortcuts,
- R.xml.devicesettings_nightmode,
- R.xml.devicesettings_liftwrist_display_sensitivity,
- R.xml.devicesettings_password,
- R.xml.devicesettings_always_on_display,
- R.xml.devicesettings_screen_timeout_5_to_15,
- R.xml.devicesettings_screen_brightness,
+ //
+ // Time
+ //
+ settings.add(R.xml.devicesettings_header_time);
+ //settings.add(R.xml.devicesettings_timeformat);
+ settings.add(R.xml.devicesettings_dateformat_2);
+ // TODO settings.add(R.xml.devicesettings_world_clocks);
- R.xml.devicesettings_header_health,
- R.xml.devicesettings_heartrate_sleep_alert_activity_stress_spo2,
- R.xml.devicesettings_inactivity_dnd_no_threshold,
- R.xml.devicesettings_goal_notification,
+ //
+ // Display
+ //
+ settings.add(R.xml.devicesettings_header_display);
+ settings.add(R.xml.devicesettings_huami2021_displayitems);
+ settings.add(R.xml.devicesettings_huami2021_shortcuts);
+ if (supportsControlCenter()) {
+ settings.add(R.xml.devicesettings_huami2021_control_center);
+ }
+ settings.add(R.xml.devicesettings_nightmode);
+ settings.add(R.xml.devicesettings_sleep_mode);
+ settings.add(R.xml.devicesettings_liftwrist_display_sensitivity);
+ settings.add(R.xml.devicesettings_password);
+ settings.add(R.xml.devicesettings_always_on_display);
+ settings.add(R.xml.devicesettings_screen_timeout);
+ if (supportsAutoBrightness(device)) {
+ settings.add(R.xml.devicesettings_screen_brightness_withauto);
+ } else {
+ settings.add(R.xml.devicesettings_screen_brightness);
+ }
- R.xml.devicesettings_header_workout,
- R.xml.devicesettings_workout_start_on_phone,
- R.xml.devicesettings_workout_send_gps_to_band,
+ //
+ // Health
+ //
+ settings.add(R.xml.devicesettings_header_health);
+ settings.add(R.xml.devicesettings_heartrate_sleep_alert_activity_stress_spo2);
+ settings.add(R.xml.devicesettings_inactivity_dnd_no_threshold);
+ settings.add(R.xml.devicesettings_goal_notification);
- R.xml.devicesettings_header_notifications,
- R.xml.devicesettings_vibrationpatterns,
- R.xml.devicesettings_donotdisturb_withauto_and_always,
- R.xml.devicesettings_screen_on_on_notifications,
- R.xml.devicesettings_autoremove_notifications,
- R.xml.devicesettings_canned_reply_16,
- R.xml.devicesettings_transliteration,
+ //
+ // Workout
+ //
+ settings.add(R.xml.devicesettings_header_workout);
+ if (hasGps(device)) {
+ settings.add(R.xml.devicesettings_gps_agps);
+ } else {
+ // If the device has GPS, it doesn't report workout start/end to the phone
+ settings.add(R.xml.devicesettings_workout_start_on_phone);
+ settings.add(R.xml.devicesettings_workout_send_gps_to_band);
+ }
+ settings.add(R.xml.devicesettings_workout_detection);
- R.xml.devicesettings_header_calendar,
- R.xml.devicesettings_sync_calendar,
+ //
+ // Notifications
+ //
+ settings.add(R.xml.devicesettings_header_notifications);
+ settings.add(R.xml.devicesettings_sound_and_vibration);
+ settings.add(R.xml.devicesettings_vibrationpatterns);
+ settings.add(R.xml.devicesettings_donotdisturb_withauto_and_always);
+ settings.add(R.xml.devicesettings_screen_on_on_notifications);
+ settings.add(R.xml.devicesettings_autoremove_notifications);
+ settings.add(R.xml.devicesettings_canned_reply_16);
+ settings.add(R.xml.devicesettings_transliteration);
- R.xml.devicesettings_header_other,
- R.xml.devicesettings_device_actions_without_not_wear,
+ //
+ // Calendar
+ //
+ settings.add(R.xml.devicesettings_header_calendar);
+ settings.add(R.xml.devicesettings_sync_calendar);
- R.xml.devicesettings_header_connection,
- R.xml.devicesettings_expose_hr_thirdparty,
- R.xml.devicesettings_bt_connected_advertisement,
- R.xml.devicesettings_high_mtu,
+ //
+ // Other
+ //
+ settings.add(R.xml.devicesettings_header_other);
+ settings.add(R.xml.devicesettings_offline_voice);
+ settings.add(R.xml.devicesettings_device_actions_without_not_wear);
+ settings.add(R.xml.devicesettings_buttonactions_upper_long);
+ settings.add(R.xml.devicesettings_buttonactions_lower_short);
+ settings.add(R.xml.devicesettings_weardirection);
- R.xml.devicesettings_header_developer,
- R.xml.devicesettings_keep_activity_data_on_device,
- };
+ //
+ // Connection
+ //
+ settings.add(R.xml.devicesettings_header_connection);
+ settings.add(R.xml.devicesettings_expose_hr_thirdparty);
+ settings.add(R.xml.devicesettings_bt_connected_advertisement);
+ settings.add(R.xml.devicesettings_high_mtu);
+
+ //
+ // Developer
+ //
+ settings.add(R.xml.devicesettings_header_developer);
+ settings.add(R.xml.devicesettings_keep_activity_data_on_device);
+
+ return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
}
@Override
@@ -212,13 +258,73 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator {
};
}
+ @Override
+ public List getVibrationPatternNotificationTypes(final GBDevice device) {
+ final List notificationTypes = new ArrayList<>(Arrays.asList(
+ HuamiVibrationPatternNotificationType.APP_ALERTS,
+ HuamiVibrationPatternNotificationType.INCOMING_CALL,
+ HuamiVibrationPatternNotificationType.INCOMING_SMS,
+ HuamiVibrationPatternNotificationType.GOAL_NOTIFICATION,
+ HuamiVibrationPatternNotificationType.ALARM,
+ HuamiVibrationPatternNotificationType.IDLE_ALERTS
+ ));
+
+ if (getReminderSlotCount(device) > 0) {
+ notificationTypes.add(HuamiVibrationPatternNotificationType.EVENT_REMINDER);
+ }
+
+ if (!supportsContinuousFindDevice()) {
+ notificationTypes.add(HuamiVibrationPatternNotificationType.FIND_BAND);
+ }
+
+ if (supportsToDoList()) {
+ notificationTypes.add(HuamiVibrationPatternNotificationType.SCHEDULE);
+ notificationTypes.add(HuamiVibrationPatternNotificationType.TODO_LIST);
+ }
+
+ return notificationTypes;
+ }
+
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
- return new Huami2021SettingsCustomizer(device);
+ return new Huami2021SettingsCustomizer(device, getVibrationPatternNotificationTypes(device));
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_REQUIRE_KEY;
}
+
+ public boolean supportsContinuousFindDevice() {
+ // TODO: Auto-detect continuous find device?
+ return false;
+ }
+
+ public boolean supportsControlCenter() {
+ // TODO: Auto-detect control center?
+ return false;
+ }
+
+ public boolean supportsToDoList() {
+ // TODO: Not yet implemented
+ // TODO: When implemented, query the capability like reminders
+ return false;
+ }
+
+ public boolean mainMenuHasMoreSection() {
+ // Devices that have a control center don't seem to have a "more" section in the main menu
+ return !supportsControlCenter();
+ }
+
+ public boolean hasGps(final GBDevice device) {
+ return supportsConfig(device, Huami2021Config.ConfigArg.WORKOUT_GPS_PRESET);
+ }
+
+ public boolean supportsAutoBrightness(final GBDevice device) {
+ return supportsConfig(device, Huami2021Config.ConfigArg.SCREEN_AUTO_BRIGHTNESS);
+ }
+
+ private boolean supportsConfig(final GBDevice device, final Huami2021Config.ConfigArg config) {
+ return Huami2021Config.deviceHasConfig(getPrefs(device), config);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java
index ac3a8ce8b..3a21c3311 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Service.java
@@ -27,6 +27,7 @@ public class Huami2021Service {
public static final short CHUNKED2021_ENDPOINT_WEATHER = 0x000e;
public static final short CHUNKED2021_ENDPOINT_ALARMS = 0x000f;
public static final short CHUNKED2021_ENDPOINT_CANNED_MESSAGES = 0x0013;
+ public static final short CHUNKED2021_ENDPOINT_CONNECTION = 0x0015;
public static final short CHUNKED2021_ENDPOINT_USER_INFO = 0x0017;
public static final short CHUNKED2021_ENDPOINT_STEPS = 0x0016;
public static final short CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS = 0x0018;
@@ -84,6 +85,7 @@ public class Huami2021Service {
public static final byte DISPLAY_ITEMS_CMD_CREATE_ACK = 0x06;
public static final byte DISPLAY_ITEMS_MENU = 0x01;
public static final byte DISPLAY_ITEMS_SHORTCUTS = 0x02;
+ public static final byte DISPLAY_ITEMS_CONTROL_CENTER = 0x03;
public static final byte DISPLAY_ITEMS_SECTION_MAIN = 0x01;
public static final byte DISPLAY_ITEMS_SECTION_MORE = 0x02;
public static final byte DISPLAY_ITEMS_SECTION_DISABLED = 0x03;
@@ -91,12 +93,15 @@ public class Huami2021Service {
/**
* Find Device, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_FIND_DEVICE}.
*/
- public static final byte FIND_BAND_ONESHOT = 0x03;
+ public static final byte FIND_BAND_START = 0x03;
public static final byte FIND_BAND_ACK = 0x04;
+ public static final byte FIND_BAND_STOP_FROM_PHONE = 0x06;
+ public static final byte FIND_BAND_STOP_FROM_BAND = 0x07;
public static final byte FIND_PHONE_START = 0x11;
public static final byte FIND_PHONE_ACK = 0x12;
public static final byte FIND_PHONE_STOP_FROM_BAND = 0x13;
public static final byte FIND_PHONE_STOP_FROM_PHONE = 0x14;
+ public static final byte FIND_PHONE_MODE = 0x15;
/**
* Steps, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_STEPS}.
@@ -151,6 +156,14 @@ public class Huami2021Service {
public static final byte CANNED_MESSAGES_CMD_REPLY_SMS_CHECK = 0x0d;
public static final byte CANNED_MESSAGES_CMD_REPLY_SMS_ALLOW = 0x0e;
+ /**
+ * Connection, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_CONNECTION}.
+ */
+ public static final byte CONNECTION_CMD_MTU_REQUEST = 0x01;
+ public static final byte CONNECTION_CMD_MTU_RESPONSE = 0x02;
+ public static final byte CONNECTION_CMD_UNKNOWN_3 = 0x03;
+ public static final byte CONNECTION_CMD_UNKNOWN_4 = 0x04;
+
/**
* Notifications, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_HEARTRATE}.
*/
@@ -218,8 +231,6 @@ public class Huami2021Service {
public static final byte CONFIG_CMD_RESPONSE = 0x04;
public static final byte CONFIG_CMD_SET = 0x05;
public static final byte CONFIG_CMD_ACK = 0x06;
- public static final byte CONFIG_REQUEST_TYPE_SPECIFIC = 0x00;
- public static final byte CONFIG_REQUEST_TYPE_ALL = 0x01; // Don't know how to parse them properly
/**
* Config, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_ICONS}.
@@ -234,6 +245,8 @@ public class Huami2021Service {
/**
* Reminders, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_REMINDERS}.
*/
+ public static final byte REMINDERS_CMD_CAPABILITIES_REQUEST = 0x01;
+ public static final byte REMINDERS_CMD_CAPABILITIES_RESPONSE = 0x02;
public static final byte REMINDERS_CMD_REQUEST = 0x03;
public static final byte REMINDERS_CMD_RESPONSE = 0x04;
public static final byte REMINDERS_CMD_CREATE = 0x05;
@@ -246,6 +259,7 @@ public class Huami2021Service {
public static final int REMINDER_FLAG_TEXT = 0x0008;
public static final int REMINDER_FLAG_REPEAT_MONTH = 0x1000;
public static final int REMINDER_FLAG_REPEAT_YEAR = 0x2000;
+ public static final String REMINDERS_PREF_CAPABILITY = "huami_2021_capability_reminders";
/**
* Calendar, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_CALENDAR}.
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 2111ca068..047793526 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
@@ -17,87 +17,405 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huami;
import android.os.Parcel;
-import android.text.InputType;
import androidx.preference.ListPreference;
+import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
-import com.mobeta.android.dslv.DragSortListPreference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
-import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
-import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021MenuType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
-import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer {
- public Huami2021SettingsCustomizer(final GBDevice device) {
- super(device);
+ private static final Logger LOG = LoggerFactory.getLogger(Huami2021SettingsCustomizer.class);
+
+ public Huami2021SettingsCustomizer(final GBDevice device, final List vibrationPatternNotificationTypes) {
+ super(device, vibrationPatternNotificationTypes);
}
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
super.customizeSettings(handler, prefs);
- setupDisplayItemsPref(
- HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE,
- HuamiConst.PREF_ALL_DISPLAY_ITEMS,
- Huami2021MenuType.displayItemNameLookup,
- handler,
- prefs
- );
+ // These are not reported by the normal configs
+ removeUnsupportedElementsFromListPreference(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, handler, prefs);
+ removeUnsupportedElementsFromListPreference(HuamiConst.PREF_SHORTCUTS_SORTABLE, handler, prefs);
+ removeUnsupportedElementsFromListPreference(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, handler, prefs);
- setupDisplayItemsPref(
+ for (final Huami2021Config.ConfigArg config : Huami2021Config.ConfigArg.values()) {
+ if (config.getPrefKey() == null) {
+ continue;
+ }
+ switch (config.getConfigType()) {
+ case BYTE:
+ case BYTE_LIST:
+ case STRING_LIST:
+ // For list preferences, remove the unsupported items
+ removeUnsupportedElementsFromListPreference(config.getPrefKey(), handler, prefs);
+ break;
+ case BOOL:
+ case SHORT:
+ case INT:
+ case DATETIME_HH_MM:
+ // For other preferences, just hide them if they were not reported as supported by the device
+ hidePrefIfNoConfigSupported(handler, prefs, config.getPrefKey(), config);
+ break;
+ }
+ }
+
+ // Hide all config groups that may not be mapped directly to a preference
+ final Map> configScreens = new HashMap>() {{
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_NIGHT_MODE, Arrays.asList(
+ Huami2021Config.ConfigArg.NIGHT_MODE_MODE,
+ Huami2021Config.ConfigArg.NIGHT_MODE_SCHEDULED_START,
+ Huami2021Config.ConfigArg.NIGHT_MODE_SCHEDULED_END
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_SLEEP_MODE, Arrays.asList(
+ Huami2021Config.ConfigArg.SLEEP_MODE_SLEEP_SCREEN,
+ Huami2021Config.ConfigArg.SLEEP_MODE_SMART_ENABLE
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST, Arrays.asList(
+ Huami2021Config.ConfigArg.LIFT_WRIST_MODE,
+ Huami2021Config.ConfigArg.LIFT_WRIST_SCHEDULED_START,
+ Huami2021Config.ConfigArg.LIFT_WRIST_SCHEDULED_END,
+ Huami2021Config.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD, Arrays.asList(
+ Huami2021Config.ConfigArg.PASSWORD_ENABLED,
+ Huami2021Config.ConfigArg.PASSWORD_TEXT
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_ALWAYS_ON_DISPLAY, Arrays.asList(
+ Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_MODE,
+ Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START,
+ Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END,
+ Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE,
+ Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_STYLE
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS, Arrays.asList(
+ Huami2021Config.ConfigArg.SCREEN_AUTO_BRIGHTNESS
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_HEARTRATE_MONITORING, Arrays.asList(
+ Huami2021Config.ConfigArg.HEART_RATE_ALL_DAY_MONITORING,
+ Huami2021Config.ConfigArg.HEART_RATE_HIGH_ALERTS,
+ Huami2021Config.ConfigArg.HEART_RATE_LOW_ALERTS,
+ Huami2021Config.ConfigArg.HEART_RATE_ACTIVITY_MONITORING,
+ Huami2021Config.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING,
+ Huami2021Config.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING,
+ Huami2021Config.ConfigArg.STRESS_MONITORING,
+ Huami2021Config.ConfigArg.STRESS_RELAXATION_REMINDER,
+ Huami2021Config.ConfigArg.SPO2_ALL_DAY_MONITORING,
+ Huami2021Config.ConfigArg.SPO2_LOW_ALERT
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_INACTIVITY_EXTENDED, Arrays.asList(
+ Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_ENABLED,
+ Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START,
+ Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END,
+ Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED,
+ Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START,
+ Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_HEADER_GPS, Arrays.asList(
+ Huami2021Config.ConfigArg.WORKOUT_GPS_PRESET,
+ Huami2021Config.ConfigArg.WORKOUT_GPS_BAND,
+ Huami2021Config.ConfigArg.WORKOUT_GPS_COMBINATION,
+ Huami2021Config.ConfigArg.WORKOUT_GPS_SATELLITE_SEARCH,
+ Huami2021Config.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED,
+ Huami2021Config.ConfigArg.WORKOUT_AGPS_EXPIRY_REMINDER_TIME
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_SOUND_AND_VIBRATION, Arrays.asList(
+ Huami2021Config.ConfigArg.VOLUME,
+ Huami2021Config.ConfigArg.CROWN_VIBRATION,
+ Huami2021Config.ConfigArg.ALERT_TONE,
+ Huami2021Config.ConfigArg.COVER_TO_MUTE,
+ Huami2021Config.ConfigArg.VIBRATE_FOR_ALERT,
+ Huami2021Config.ConfigArg.TEXT_TO_SPEECH
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_DO_NOT_DISTURB, Arrays.asList(
+ Huami2021Config.ConfigArg.DND_MODE,
+ Huami2021Config.ConfigArg.DND_SCHEDULED_START,
+ Huami2021Config.ConfigArg.DND_SCHEDULED_END
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT_DETECTION, Arrays.asList(
+ Huami2021Config.ConfigArg.WORKOUT_DETECTION_CATEGORY,
+ Huami2021Config.ConfigArg.WORKOUT_DETECTION_ALERT,
+ Huami2021Config.ConfigArg.WORKOUT_DETECTION_SENSITIVITY
+ ));
+ put(DeviceSettingsPreferenceConst.PREF_SCREEN_OFFLINE_VOICE, Arrays.asList(
+ Huami2021Config.ConfigArg.OFFLINE_VOICE_RESPOND_TURN_WRIST,
+ Huami2021Config.ConfigArg.OFFLINE_VOICE_RESPOND_SCREEN_ON,
+ Huami2021Config.ConfigArg.OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING,
+ Huami2021Config.ConfigArg.OFFLINE_VOICE_LANGUAGE
+ ));
+ }};
+
+ for (final Map.Entry> configScreen : configScreens.entrySet()) {
+ hidePrefIfNoConfigSupported(
+ handler,
+ prefs,
+ configScreen.getKey(),
+ configScreen.getValue().toArray(new Huami2021Config.ConfigArg[0])
+ );
+ }
+
+ // Hides the headers if none of the preferences under them are available
+ hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_TIME, Arrays.asList(
+ DeviceSettingsPreferenceConst.PREF_TIMEFORMAT,
+ DeviceSettingsPreferenceConst.PREF_DATEFORMAT,
+ DeviceSettingsPreferenceConst.PREF_WORLD_CLOCKS
+ ));
+ hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_DISPLAY, Arrays.asList(
+ HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE,
HuamiConst.PREF_SHORTCUTS_SORTABLE,
- HuamiConst.PREF_ALL_SHORTCUTS,
- Huami2021MenuType.shortcutsNameLookup,
- handler,
- prefs
- );
+ HuamiConst.PREF_CONTROL_CENTER_SORTABLE,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_NIGHT_MODE,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_SLEEP_MODE,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_PASSWORD,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_ALWAYS_ON_DISPLAY,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS
+ ));
+ hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_HEALTH, Arrays.asList(
+ DeviceSettingsPreferenceConst.PREF_SCREEN_HEARTRATE_MONITORING,
+ DeviceSettingsPreferenceConst.PREF_SCREEN_INACTIVITY_EXTENDED,
+ DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION
+ ));
+ hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT, Arrays.asList(
+ DeviceSettingsPreferenceConst.PREF_HEADER_GPS,
+ DeviceSettingsPreferenceConst.PREF_WORKOUT_START_ON_PHONE,
+ DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND,
+ DeviceSettingsPreferenceConst.PREF_HEADER_WORKOUT_DETECTION
+ ));
+ hidePrefIfNoneVisible(handler, DeviceSettingsPreferenceConst.PREF_HEADER_AGPS, Arrays.asList(
+ DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRY_REMINDER_ENABLED,
+ DeviceSettingsPreferenceConst.PREF_AGPS_EXPIRY_REMINDER_TIME
+ ));
+
+ setupGpsPreference(handler);
}
- private void setupDisplayItemsPref(final String prefKey,
- final String allItemsPrefKey,
- final Map nameLookup,
- final DeviceSpecificSettingsHandler handler,
- final Prefs prefs) {
- final DragSortListPreference pref = handler.findPreference(prefKey);
+ private void setupGpsPreference(final DeviceSpecificSettingsHandler handler) {
+ final ListPreference prefGpsPreset = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_MODE_PRESET);
+ final ListPreference prefGpsBand = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_BAND);
+ final ListPreference prefGpsCombination = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_COMBINATION);
+ final ListPreference prefGpsSatelliteSearch = handler.findPreference(DeviceSettingsPreferenceConst.PREF_GPS_SATELLITE_SEARCH);
+
+ if (prefGpsPreset != null) {
+ // When the preset preference is changed, update the band, combination and satellite search to the corresponding values
+ final Preference.OnPreferenceChangeListener onGpsPresetUpdated = (preference, newVal) -> {
+ final boolean isCustomPreset = GpsCapability.Preset.CUSTOM.name().toLowerCase(Locale.ROOT).equals(newVal);
+ final GpsCapability.Preset preset = GpsCapability.Preset.valueOf(newVal.toString().toUpperCase(Locale.ROOT));
+ final GpsCapability.Band presetBand;
+ final GpsCapability.Combination presetCombination;
+ final GpsCapability.SatelliteSearch presetSatelliteSearch;
+ switch (preset) {
+ case ACCURACY:
+ presetBand = GpsCapability.Band.DUAL_BAND;
+ presetCombination = GpsCapability.Combination.ALL_SATELLITES;
+ presetSatelliteSearch = GpsCapability.SatelliteSearch.ACCURACY_FIRST;
+ break;
+ case BALANCED:
+ presetBand = GpsCapability.Band.SINGLE_BAND;
+ presetCombination = GpsCapability.Combination.GPS_BDS;
+ presetSatelliteSearch = GpsCapability.SatelliteSearch.ACCURACY_FIRST;
+ break;
+ case POWER_SAVING:
+ presetBand = GpsCapability.Band.SINGLE_BAND;
+ presetCombination = GpsCapability.Combination.LOW_POWER_GPS;
+ presetSatelliteSearch = GpsCapability.SatelliteSearch.SPEED_FIRST;
+ break;
+ default:
+ presetBand = null;
+ presetCombination = null;
+ presetSatelliteSearch = null;
+ break;
+ }
+
+ if (prefGpsBand != null) {
+ prefGpsBand.setEnabled(isCustomPreset);
+ if (!isCustomPreset && presetBand != null) {
+ prefGpsBand.setValue(presetBand.name().toLowerCase(Locale.ROOT));
+ }
+ }
+ if (prefGpsCombination != null) {
+ prefGpsCombination.setEnabled(isCustomPreset);
+ if (!isCustomPreset && presetBand != null) {
+ prefGpsCombination.setValue(presetCombination.name().toLowerCase(Locale.ROOT));
+ }
+ }
+ if (prefGpsSatelliteSearch != null) {
+ prefGpsSatelliteSearch.setEnabled(isCustomPreset);
+ if (!isCustomPreset && presetBand != null) {
+ prefGpsSatelliteSearch.setValue(presetSatelliteSearch.name().toLowerCase(Locale.ROOT));
+ }
+ }
+
+ return true;
+ };
+
+ handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_GPS_MODE_PRESET, onGpsPresetUpdated);
+ onGpsPresetUpdated.onPreferenceChange(prefGpsPreset, prefGpsPreset.getValue());
+ }
+
+ // The gps combination can only be chosen if the gps band is single band
+ if (prefGpsBand != null && prefGpsCombination != null) {
+ final Preference.OnPreferenceChangeListener onGpsBandUpdate = (preference, newVal) -> {
+ final boolean isSingleBand = GpsCapability.Band.SINGLE_BAND.name().toLowerCase(Locale.ROOT).equals(newVal);
+ prefGpsCombination.setEnabled(isSingleBand);
+ return true;
+ };
+
+ handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_GPS_BAND, onGpsBandUpdate);
+ final boolean isCustomPreset = prefGpsPreset != null &&
+ GpsCapability.Preset.CUSTOM.name().toLowerCase(Locale.ROOT).equals(prefGpsPreset.getValue());
+ if (isCustomPreset) {
+ onGpsBandUpdate.onPreferenceChange(prefGpsPreset, prefGpsBand.getValue());
+ }
+ }
+ }
+
+ /**
+ * Removes all unsupported elements from a list preference. If they are not known, the preference
+ * is hidden.
+ */
+ private void removeUnsupportedElementsFromListPreference(final String prefKey,
+ final DeviceSpecificSettingsHandler handler,
+ final Prefs prefs) {
+ final Preference pref = handler.findPreference(prefKey);
if (pref == null) {
return;
}
- final List allDisplayItems = prefs.getList(allItemsPrefKey, null);
- if (allDisplayItems == null || allDisplayItems.isEmpty()) {
+
+ // Get the list of possible values for this preference, as reported by the band
+ final List possibleValues = prefs.getList(Huami2021Config.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
+ pref.setVisible(false);
+
return;
}
- final CharSequence[] entries = new CharSequence[allDisplayItems.size()];
- final CharSequence[] values = new CharSequence[allDisplayItems.size()];
- for (int i = 0; i < allDisplayItems.size(); i++) {
- final String screenId = allDisplayItems.get(i);
- final String screenName;
- if (screenId.equals("more")) {
- screenName = handler.getContext().getString(R.string.menuitem_more);
- } else if (nameLookup.containsKey(screenId)) {
- screenName = handler.getContext().getString(nameLookup.get(screenId));
- } else {
- screenName = handler.getContext().getString(R.string.menuitem_unknown_app, screenId);
- }
+ final CharSequence[] originalEntries;
+ final CharSequence[] originalValues;
- entries[i] = screenName;
- values[i] = screenId;
+ if (pref instanceof ListPreference) {
+ originalEntries = ((ListPreference) pref).getEntries();
+ originalValues = ((ListPreference) pref).getEntryValues();
+ } else if (pref instanceof MultiSelectListPreference) {
+ originalEntries = ((MultiSelectListPreference) pref).getEntries();
+ originalValues = ((MultiSelectListPreference) pref).getEntryValues();
+ } else {
+ LOG.error("Unknown list pref class {}", pref.getClass().getName());
+ return;
}
- pref.setEntries(entries);
- pref.setEntryValues(values);
+ final List prefValues = new ArrayList<>(originalValues.length);
+ for (final CharSequence entryValue : originalValues) {
+ prefValues.add(entryValue.toString());
+ }
+
+ final CharSequence[] entries = new CharSequence[possibleValues.size()];
+ final CharSequence[] values = new CharSequence[possibleValues.size()];
+ for (int i = 0; i < possibleValues.size(); i++) {
+ final String possibleValue = possibleValues.get(i);
+ final int idxPrefValue = prefValues.indexOf(possibleValue);
+
+ if (idxPrefValue >= 0) {
+ entries[i] = originalEntries[idxPrefValue];
+ } else {
+ entries[i] = handler.getContext().getString(R.string.menuitem_unknown_app, possibleValue);
+ }
+ values[i] = possibleValue;
+ }
+
+ if (pref instanceof ListPreference) {
+ ((ListPreference) pref).setEntries(entries);
+ ((ListPreference) pref).setEntryValues(values);
+ } else if (pref instanceof MultiSelectListPreference) {
+ ((MultiSelectListPreference) pref).setEntries(entries);
+ ((MultiSelectListPreference) pref).setEntryValues(values);
+ }
}
+
+ /**
+ * Hides prefToHide if no configuration from the list has been reported by the band.
+ */
+ private void hidePrefIfNoConfigSupported(final DeviceSpecificSettingsHandler handler,
+ final Prefs prefs,
+ final String prefToHide,
+ final Huami2021Config.ConfigArg... configs) {
+ final Preference pref = handler.findPreference(prefToHide);
+ if (pref == null) {
+ return;
+ }
+
+ for (final Huami2021Config.ConfigArg config : configs) {
+ if (Huami2021Config.deviceHasConfig(prefs, config)) {
+ // This preference is supported, don't hide
+ return;
+ }
+ }
+
+ // None of the configs were supported by the device, hide this preference
+ pref.setVisible(false);
+ }
+
+ /**
+ * Hides the the prefToHide preference if none of the preferences in the preferences list are
+ * visible.
+ */
+ private void hidePrefIfNoneVisible(final DeviceSpecificSettingsHandler handler,
+ final String prefToHide,
+ final List subPrefs) {
+ final Preference pref = handler.findPreference(prefToHide);
+ if (pref == null) {
+ return;
+ }
+
+ for (final String subPrefKey : subPrefs) {
+ final Preference subPref = handler.findPreference(subPrefKey);
+ if (subPref == null) {
+ continue;
+ }
+ if (subPref.isVisible()) {
+ // At least one preference is visible
+ return;
+ }
+ }
+
+ // No preference was visible, hide
+ pref.setVisible(false);
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Huami2021SettingsCustomizer createFromParcel(final Parcel in) {
+ final GBDevice device = in.readParcelable(Huami2021SettingsCustomizer.class.getClassLoader());
+ final List vibrationPatternNotificationTypes = new ArrayList<>();
+ in.readList(vibrationPatternNotificationTypes, HuamiVibrationPatternNotificationType.class.getClassLoader());
+ return new Huami2021SettingsCustomizer(device, vibrationPatternNotificationTypes);
+ }
+
+ @Override
+ public Huami2021SettingsCustomizer[] newArray(final int size) {
+ return new Huami2021SettingsCustomizer[size];
+ }
+ };
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java
index 5626d5bb6..3465af2fb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiConst.java
@@ -57,16 +57,16 @@ public class HuamiConst {
public static final String AMAZFIT_X = "Amazfit X";
public static final String AMAZFIT_GTS3_NAME = "Amazfit GTS 3";
public static final String AMAZFIT_GTR3_NAME = "Amazfit GTR 3";
+ public static final String AMAZFIT_GTR4_NAME = "Amazfit GTR 4";
public static final String XIAOMI_SMART_BAND7_NAME = "Xiaomi Smart Band 7";
public static final String PREF_DISPLAY_ITEMS = "display_items";
- public static final String PREF_ALL_DISPLAY_ITEMS = "all_display_items";
public static final String PREF_DISPLAY_ITEMS_SORTABLE = "display_items_sortable";
public static final String PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE = "workout_activity_types_sortable";
public static final String PREF_SHORTCUTS = "shortcuts";
- public static final String PREF_ALL_SHORTCUTS = "all_shortcuts";
public static final String PREF_SHORTCUTS_SORTABLE = "shortcuts_sortable";
+ public static final String PREF_CONTROL_CENTER_SORTABLE = "control_center_sortable";
public static final String PREF_EXPOSE_HR_THIRDPARTY = "expose_hr_thirdparty";
public static final String PREF_USE_CUSTOM_FONT = "use_custom_font";
@@ -94,6 +94,8 @@ public class HuamiConst {
/**
* The suffixes match the enum {@link nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType}.
*/
+ public static final String PREF_HUAMI_VIBRATION_PROFILE_KEY_PREFIX = "vibration_profile_key_";
+ public static final String PREF_HUAMI_DEFAULT_VIBRATION_PROFILE = "default";
// profile
public static final String PREF_HUAMI_VIBRATION_PROFILE_PREFIX = "huami_vibration_profile_";
public static final String PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "app_alerts";
@@ -104,6 +106,8 @@ public class HuamiConst {
public static final String PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "idle_alerts";
public static final String PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "event_reminder";
public static final String PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "find_band";
+ public static final String PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "todo_list";
+ public static final String PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE = PREF_HUAMI_VIBRATION_PROFILE_PREFIX + "schedule";
// count
public static final String PREF_HUAMI_VIBRATION_COUNT_PREFIX = "huami_vibration_count_";
public static final String PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "app_alerts";
@@ -114,6 +118,8 @@ public class HuamiConst {
public static final String PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "idle_alerts";
public static final String PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "event_reminder";
public static final String PREF_HUAMI_VIBRATION_COUNT_FIND_BAND = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "find_band";
+ public static final String PREF_HUAMI_VIBRATION_COUNT_TODO_LIST = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "todo_list";
+ public static final String PREF_HUAMI_VIBRATION_COUNT_SCHEDULE = PREF_HUAMI_VIBRATION_COUNT_PREFIX + "schedule";
// try
public static final String PREF_HUAMI_VIBRATION_TRY_PREFIX = "huami_vibration_try_";
public static final String PREF_HUAMI_VIBRATION_TRY_APP_ALERTS = PREF_HUAMI_VIBRATION_TRY_PREFIX + "app_alerts";
@@ -124,6 +130,8 @@ public class HuamiConst {
public static final String PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS = PREF_HUAMI_VIBRATION_TRY_PREFIX + "idle_alerts";
public static final String PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER = PREF_HUAMI_VIBRATION_TRY_PREFIX + "event_reminder";
public static final String PREF_HUAMI_VIBRATION_TRY_FIND_BAND = PREF_HUAMI_VIBRATION_TRY_PREFIX + "find_band";
+ public static final String PREF_HUAMI_VIBRATION_TRY_TODO_LIST = PREF_HUAMI_VIBRATION_TRY_PREFIX + "todo_list";
+ public static final String PREF_HUAMI_VIBRATION_TRY_SCHEDULE = PREF_HUAMI_VIBRATION_TRY_PREFIX + "schedule";
public static int toActivityKind(int rawType) {
switch (rawType) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
index cd531deef..d8a094c6b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
@@ -17,24 +17,23 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.devices.huami;
-import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.content.SharedPreferences;
-import android.os.Build;
import android.os.ParcelUuid;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.List;
import java.util.Locale;
import de.greenrobot.dao.query.QueryBuilder;
@@ -146,6 +145,10 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
return new HuamiActivitySummaryParser();
}
+ protected static Prefs getPrefs(final GBDevice device) {
+ return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
+ }
+
public static DateTimeDisplay getDateDisplay(Context context, String deviceAddress) throws IllegalArgumentException {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
String dateFormatTime = context.getString(R.string.p_dateformat_time);
@@ -363,7 +366,8 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
return prefs.getBoolean("keep_activity_data_on_device", false);
}
- public static VibrationProfile getVibrationProfile(String deviceAddress, HuamiVibrationPatternNotificationType notificationType) {
+ @Nullable
+ public static VibrationProfile getVibrationProfile(String deviceAddress, HuamiVibrationPatternNotificationType notificationType, boolean nullOnDeviceDefault) {
final String defaultVibrationProfileId;
final int defaultVibrationCount;
@@ -400,6 +404,14 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
defaultVibrationProfileId = VibrationProfile.ID_RING;
defaultVibrationCount = 3;
break;
+ case TODO_LIST:
+ defaultVibrationProfileId = VibrationProfile.ID_SHORT;
+ defaultVibrationCount = 2;
+ break;
+ case SCHEDULE:
+ defaultVibrationProfileId = VibrationProfile.ID_SHORT;
+ defaultVibrationCount = 2;
+ break;
default:
defaultVibrationProfileId = VibrationProfile.ID_MEDIUM;
defaultVibrationCount = 2;
@@ -408,8 +420,18 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
final String vibrationProfileId = prefs.getString(
HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + notificationType.name().toLowerCase(Locale.ROOT),
- defaultVibrationProfileId
+ HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE
);
+
+ if (HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE.equals(vibrationProfileId)) {
+ if (nullOnDeviceDefault) {
+ // Return null, so the device default is used
+ return null;
+ }
+
+ return VibrationProfile.getProfile(defaultVibrationProfileId, (short) defaultVibrationCount);
+ }
+
final int vibrationProfileCount = prefs.getInt(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + notificationType.name().toLowerCase(Locale.ROOT), defaultVibrationCount);
return VibrationProfile.getProfile(vibrationProfileId, (short) vibrationProfileCount);
@@ -423,20 +445,8 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
} else {
prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
}
- String time = prefs.getString(key, defaultValue);
- DateFormat df = new SimpleDateFormat("HH:mm");
- try {
- return df.parse(time);
- } catch (Exception e) {
- LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
- }
-
- return new Date();
- }
-
- protected static Date getTimePreference(String key, String defaultValue) {
- return getTimePreference(key, defaultValue, null);
+ return prefs.getTimePreference(key, defaultValue);
}
public static MiBandConst.DistanceUnit getDistanceUnit() {
@@ -501,13 +511,26 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 22; // At least, Mi Fit still allows more
}
+ public List getVibrationPatternNotificationTypes(final GBDevice device) {
+ return Arrays.asList(
+ HuamiVibrationPatternNotificationType.APP_ALERTS,
+ HuamiVibrationPatternNotificationType.INCOMING_CALL,
+ HuamiVibrationPatternNotificationType.INCOMING_SMS,
+ HuamiVibrationPatternNotificationType.GOAL_NOTIFICATION,
+ HuamiVibrationPatternNotificationType.ALARM,
+ HuamiVibrationPatternNotificationType.IDLE_ALERTS,
+ HuamiVibrationPatternNotificationType.EVENT_REMINDER,
+ HuamiVibrationPatternNotificationType.FIND_BAND
+ );
+ }
+
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
- return new HuamiSettingsCustomizer(device);
+ return new HuamiSettingsCustomizer(device, getVibrationPatternNotificationTypes(device));
}
public static boolean getHourlyChime(String deviceAddress) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java
index b4d13b3c5..5e0f230ec 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiSettingsCustomizer.java
@@ -21,7 +21,9 @@ import android.text.InputType;
import androidx.preference.Preference;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -34,9 +36,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class HuamiSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
final GBDevice device;
+ final List vibrationPatternNotificationTypes;
- public HuamiSettingsCustomizer(final GBDevice device) {
+ public HuamiSettingsCustomizer(final GBDevice device,
+ final List vibrationPatternNotificationTypes) {
this.device = device;
+ this.vibrationPatternNotificationTypes = vibrationPatternNotificationTypes;
}
@Override
@@ -46,24 +51,48 @@ public class HuamiSettingsCustomizer implements DeviceSpecificSettingsCustomizer
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
+ // Setup the vibration patterns for all supported notification types
for (HuamiVibrationPatternNotificationType notificationType : HuamiVibrationPatternNotificationType.values()) {
final String typeKey = notificationType.name().toLowerCase(Locale.ROOT);
+ // Hide unsupported notification types
+ if (!vibrationPatternNotificationTypes.contains(notificationType)) {
+ final String screenKey = HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_KEY_PREFIX + typeKey;
+ final Preference pref = handler.findPreference(screenKey);
+ if (pref != null) {
+ pref.setVisible(false);
+ }
+ continue;
+ }
+
handler.addPreferenceHandlerFor(HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
handler.addPreferenceHandlerFor(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + typeKey);
handler.setInputTypeFor(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + typeKey, InputType.TYPE_CLASS_NUMBER);
+ // Setup the try pref to vibrate the device
final String tryPrefKey = HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX + typeKey;
final Preference tryPref = handler.findPreference(tryPrefKey);
if (tryPref != null) {
- tryPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- GBApplication.deviceService(device).onSendConfiguration(tryPrefKey);
- return true;
- }
+ tryPref.setOnPreferenceClickListener(preference -> {
+ GBApplication.deviceService(device).onSendConfiguration(tryPrefKey);
+ return true;
});
}
+
+ // Setup the default preference - disable count if default preference is selected
+ final String profilePrefKey = HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey;
+ final String countPrefKey = HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX + typeKey;
+ final Preference countPref = handler.findPreference(countPrefKey);
+
+ final Preference.OnPreferenceChangeListener profilePrefListener = (preference, newValue) -> {
+ if (countPref != null) {
+ countPref.setEnabled(!HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE.equals(newValue));
+ }
+ return true;
+ };
+
+ profilePrefListener.onPreferenceChange(null, prefs.getString(profilePrefKey, HuamiConst.PREF_HUAMI_DEFAULT_VIBRATION_PROFILE));
+ handler.addPreferenceHandlerFor(profilePrefKey, profilePrefListener);
}
}
@@ -83,7 +112,9 @@ public class HuamiSettingsCustomizer implements DeviceSpecificSettingsCustomizer
@Override
public HuamiSettingsCustomizer createFromParcel(final Parcel in) {
final GBDevice device = in.readParcelable(HuamiSettingsCustomizer.class.getClassLoader());
- return new HuamiSettingsCustomizer(device);
+ final List vibrationPatternNotificationTypes = new ArrayList<>();
+ in.readList(vibrationPatternNotificationTypes, HuamiVibrationPatternNotificationType.class.getClassLoader());
+ return new HuamiSettingsCustomizer(device, vibrationPatternNotificationTypes);
}
@Override
@@ -100,5 +131,6 @@ public class HuamiSettingsCustomizer implements DeviceSpecificSettingsCustomizer
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeParcelable(device, 0);
+ dest.writeList(vibrationPatternNotificationTypes);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java
index fdcf1c603..dbbafe74e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipu/AmazfitBipUCoordinator.java
@@ -89,7 +89,7 @@ public class AmazfitBipUCoordinator extends HuamiCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 0;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java
index ae60e1b19..e7b1829a6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbipupro/AmazfitBipUProCoordinator.java
@@ -89,7 +89,7 @@ public class AmazfitBipUProCoordinator extends HuamiCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 0;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
new file mode 100644
index 000000000..14c9ba1a8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4Coordinator.java
@@ -0,0 +1,78 @@
+/* Copyright (C) 2022 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.devices.huami.amazfitgtr4;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+public class AmazfitGTR4Coordinator extends Huami2021Coordinator {
+ private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTR4Coordinator.class);
+
+ @NonNull
+ @Override
+ public DeviceType getSupportedType(final GBDeviceCandidate candidate) {
+ try {
+ final BluetoothDevice device = candidate.getDevice();
+ final String name = device.getName();
+ if (name != null && name.startsWith(HuamiConst.AMAZFIT_GTR4_NAME)) {
+ return DeviceType.AMAZFITGTR4;
+ }
+ } catch (final Exception e) {
+ LOG.error("unable to check device support", e);
+ }
+
+ return DeviceType.UNKNOWN;
+ }
+
+ @Override
+ public DeviceType getDeviceType() {
+ return DeviceType.AMAZFITGTR4;
+ }
+
+ @Override
+ public InstallHandler findInstallHandler(final Uri uri, final Context context) {
+ final AmazfitGTR4FWInstallHandler handler = new AmazfitGTR4FWInstallHandler(uri, context);
+ return handler.isValid() ? handler : null;
+ }
+
+ @Override
+ public boolean supportsContinuousFindDevice() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsControlCenter() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsToDoList() {
+ return true;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4FWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4FWHelper.java
new file mode 100644
index 000000000..50a41718f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4FWHelper.java
@@ -0,0 +1,44 @@
+/* Copyright (C) 2022 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.devices.huami.amazfitgtr4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr4.AmazfitGTR4FirmwareInfo;
+
+public class AmazfitGTR4FWHelper extends HuamiFWHelper {
+ public AmazfitGTR4FWHelper(final Uri uri, final Context context) throws IOException {
+ super(uri, context);
+ }
+
+ @Override
+ public long getMaxExpectedFileSize() {
+ return 1024 * 1024 * 128; // 128.0MB
+ }
+
+ @Override
+ protected void determineFirmwareInfo(final byte[] wholeFirmwareBytes) {
+ firmwareInfo = new AmazfitGTR4FirmwareInfo(wholeFirmwareBytes);
+ if (!firmwareInfo.isHeaderValid()) {
+ throw new IllegalArgumentException("Not a Amazfit GTR 4 firmware");
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4FWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4FWInstallHandler.java
new file mode 100644
index 000000000..f98f681a0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitgtr4/AmazfitGTR4FWInstallHandler.java
@@ -0,0 +1,49 @@
+/* Copyright (C) 2022 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.devices.huami.amazfitgtr4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+
+class AmazfitGTR4FWInstallHandler extends AbstractMiBandFWInstallHandler {
+ AmazfitGTR4FWInstallHandler(final Uri uri, final Context context) {
+ super(uri, context);
+ }
+
+ @Override
+ protected String getFwUpgradeNotice() {
+ return mContext.getString(R.string.fw_upgrade_notice_amazfit_gtr4, helper.getHumanFirmwareVersion());
+ }
+
+ @Override
+ protected AbstractMiBandFWHelper createHelper(final Uri uri, final Context context) throws IOException {
+ return new AmazfitGTR4FWHelper(uri, context);
+ }
+
+ @Override
+ protected boolean isSupportedDeviceType(final GBDevice device) {
+ return device.getType() == DeviceType.AMAZFITGTR4;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java
index c64f3934d..8d2c548c1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitneo/AmazfitNeoCoordinator.java
@@ -93,7 +93,7 @@ public class AmazfitNeoCoordinator extends HuamiCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 0; // Neo does not support reminders
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java
index a19b7c92d..99fbd4958 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband2/MiBand2Coordinator.java
@@ -75,7 +75,7 @@ public class MiBand2Coordinator extends HuamiCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 0;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java
index 8d5d90651..9936eac30 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java
@@ -90,7 +90,7 @@ public class MiBand5Coordinator extends HuamiCoordinator {
}
@Override
- public int getReminderSlotCount() {
+ public int getReminderSlotCount(final GBDevice device) {
return 50; // as enforced by Zepp Life
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
index 306fa26b5..5718ebafb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
@@ -355,8 +355,9 @@ public class GBDeviceService implements DeviceService {
}
@Override
- public void onPhoneFound() {
- Intent intent = createIntent().setAction(ACTION_PHONE_FOUND);
+ public void onFindPhone(final boolean start) {
+ Intent intent = createIntent().setAction(ACTION_PHONE_FOUND)
+ .putExtra(EXTRA_FIND_START, start);
invokeService(intent);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java
index 89701c0c9..5d2d3ed5e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityUser.java
@@ -58,6 +58,9 @@ public class ActivityUser {
public static final int defaultUserDistanceGoalMeters = 5000;
public static final int defaultUserActiveTimeGoalMinutes = 60;
public static final int defaultUserStepLengthCm = 0;
+ public static final int defaultUserGoalWeightKg = 70;
+ public static final int defaultUserGoalStandingTimeHours = 12;
+ public static final int defaultUserFatBurnTimeMinutes = 30;
public static final String PREF_USER_NAME = "mi_user_alias";
public static final String PREF_USER_YEAR_OF_BIRTH = "activity_user_year_of_birth";
@@ -70,6 +73,9 @@ public class ActivityUser {
public static final String PREF_USER_DISTANCE_METERS = "activity_user_distance_meters";
public static final String PREF_USER_ACTIVETIME_MINUTES = "activity_user_activetime_minutes";
public static final String PREF_USER_STEP_LENGTH_CM = "activity_user_step_length_cm";
+ public static final String PREF_USER_GOAL_WEIGHT_KG = "activity_user_goal_weight_kg";
+ public static final String PREF_USER_GOAL_STANDING_TIME_HOURS = "activity_user_goal_standing_time_minutes";
+ public static final String PREF_USER_GOAL_FAT_BURN_TIME_MINUTES = "activity_user_goal_fat_burn_time_minutes";
public ActivityUser() {
fetchPreferences();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index ed0e3581f..449373c81 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -68,6 +68,7 @@ public enum DeviceType {
MIBAND7(10041, R.drawable.ic_device_miband6, R.drawable.ic_device_miband6_disabled, R.string.devicetype_miband7),
AMAZFITGTS3(10042, R.drawable.ic_device_amazfit_bip, R.drawable.ic_device_amazfit_bip_disabled, R.string.devicetype_amazfit_gts3),
AMAZFITGTR3(10043, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_amazfit_gtr3),
+ AMAZFITGTR4(10044, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_amazfit_gtr4),
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_hplus),
MAKIBESF68(41, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_f68),
EXRIZUK8(42, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_exrizu_k8),
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
index 6066584fa..1119cc181 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
@@ -186,36 +186,49 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
}
}
- private void handleGBDeviceEvent(GBDeviceEventFindPhone deviceEvent) {
- Context context = getContext();
- LOG.info("Got GBDeviceEventFindPhone");
+ private void handleGBDeviceEvent(final GBDeviceEventFindPhone deviceEvent) {
+ final Context context = getContext();
+ LOG.info("Got GBDeviceEventFindPhone: {}", deviceEvent.event);
switch (deviceEvent.event) {
case START:
- handleGBDeviceEventFindPhoneStart();
+ handleGBDeviceEventFindPhoneStart(true);
+ break;
+ case START_VIBRATE:
+ handleGBDeviceEventFindPhoneStart(false);
+ break;
+ case VIBRATE:
+ final Intent intentVibrate = new Intent(FindPhoneActivity.ACTION_VIBRATE);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intentVibrate);
+ break;
+ case RING:
+ final Intent intentRing = new Intent(FindPhoneActivity.ACTION_RING);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intentRing);
break;
case STOP:
- Intent intent = new Intent(FindPhoneActivity.ACTION_FOUND);
- LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ final Intent intentStop = new Intent(FindPhoneActivity.ACTION_FOUND);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intentStop);
break;
default:
LOG.warn("unknown GBDeviceEventFindPhone");
}
}
- private void handleGBDeviceEventFindPhoneStart() {
+ private void handleGBDeviceEventFindPhoneStart(final boolean ring) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // this could be used if app in foreground // TODO: Below Q?
Intent startIntent = new Intent(getContext(), FindPhoneActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startIntent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
context.startActivity(startIntent);
} else {
- handleGBDeviceEventFindPhoneStartNotification();
+ handleGBDeviceEventFindPhoneStartNotification(ring);
}
}
@RequiresApi(Build.VERSION_CODES.Q)
- private void handleGBDeviceEventFindPhoneStartNotification() {
+ private void handleGBDeviceEventFindPhoneStartNotification(final boolean ring) {
LOG.info("Got handleGBDeviceEventFindPhoneStartNotification");
Intent intent = new Intent(context, FindPhoneActivity.class);
+ intent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -303,6 +316,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
}
savePreferencesEvent.update(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceState updateDeviceState) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
index cae3fc85d..da1877fa9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -45,7 +45,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@@ -54,7 +53,6 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
-import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
@@ -94,7 +92,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.language.LanguageUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_LANGUAGES;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_REORDER;
@@ -782,7 +779,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
break;
}
case ACTION_PHONE_FOUND: {
- deviceSupport.onPhoneFound();
+ final boolean start = intent.getBooleanExtra(EXTRA_FIND_START, false);
+ deviceSupport.onFindPhone(start);
break;
}
case ACTION_SET_CONSTANT_VIBRATION: {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
index d1281e853..4cd547495 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
@@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.Ama
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2.AmazfitGTR2Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr2.AmazfitGTR2eSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr3.AmazfitGTR3Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr4.AmazfitGTR4Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.AmazfitGTSSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2MiniSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts2.AmazfitGTS2Support;
@@ -188,6 +189,8 @@ public class DeviceSupportFactory {
return new ServiceDeviceSupport(new AmazfitGTS3Support());
case AMAZFITGTR3:
return new ServiceDeviceSupport(new AmazfitGTR3Support());
+ case AMAZFITGTR4:
+ return new ServiceDeviceSupport(new AmazfitGTR4Support());
case MIBAND7:
return new ServiceDeviceSupport(new MiBand7Support());
case AMAZFITBIP:
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
index 563063d4c..1630029c1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -303,11 +303,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
@Override
- public void onPhoneFound() {
+ public void onFindPhone(final boolean start) {
if (checkBusy("phone found")) {
return;
}
- delegate.onPhoneFound();
+ delegate.onFindPhone(start);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
index 12f940454..8f718a743 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
@@ -356,7 +356,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
}
@Override
- public void onPhoneFound() {
+ public void onFindPhone(boolean start) {
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java
index 120e8d2e9..5a2743c6b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Config.java
@@ -16,40 +16,7 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
-import static org.apache.commons.lang3.ArrayUtils.subarray;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_END;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_MODE;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALWAYS_ON_DISPLAY_START;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_END;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_SENSITIVITY;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_START;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_END;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_START;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
import static nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl.PREF_PASSWORD;
import static nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl.PREF_PASSWORD_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.CHUNKED2021_ENDPOINT_CONFIG;
@@ -59,11 +26,16 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PR
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_END;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_START;
+import androidx.annotation.Nullable;
+
+import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
@@ -74,13 +46,19 @@ import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability;
+import nodomain.freeyourgadget.gadgetbridge.capabilities.WorkoutDetectionCapability;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLiftSensitivity;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.AlwaysOnDisplay;
@@ -88,35 +66,74 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class Huami2021Config {
private static final Logger LOG = LoggerFactory.getLogger(Huami2021Config.class);
- public enum ConfigType {
+ public enum ConfigGroup {
DISPLAY(0x01, 0x02),
+ // TODO 0x02
+ SOUND_AND_VIBRATION(0x03, 0x02),
LOCKSCREEN(0x04, 0x01),
+ WEARING_DIRECTION(0x05, 0x02),
+ OFFLINE_VOICE(0x06, 0x02),
LANGUAGE(0x07, 0x02),
HEALTH(0x08, 0x02),
+ WORKOUT(0x09, 0x01),
SYSTEM(0x0a, 0x01),
BLUETOOTH(0x0b, 0x01),
;
private final byte value;
- private final byte nextByte; // FIXME what does this byte mean?
+ private final byte version;
- ConfigType(int value, int nextByte) {
+ ConfigGroup(int value, int version) {
this.value = (byte) value;
- this.nextByte = (byte) nextByte;
+ this.version = (byte) version;
}
public byte getValue() {
return value;
}
- public byte getNextByte() {
- return nextByte;
+ public byte getVersion() {
+ return version;
+ }
+
+ public static ConfigGroup fromValue(final byte value) {
+ for (final ConfigGroup configGroup : values()) {
+ if (configGroup.getValue() == value) {
+ return configGroup;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ public enum ConfigType {
+ BOOL(0x0b),
+ STRING(0x20),
+ STRING_LIST(0x21),
+ SHORT(0x01),
+ INT(0x03),
+ BYTE(0x10),
+ BYTE_LIST(0x11),
+ DATETIME_HH_MM(0x30),
+ ;
+
+ private final byte value;
+
+ ConfigType(int value) {
+ this.value = (byte) value;
+ }
+
+ public byte getValue() {
+ return value;
}
public static ConfigType fromValue(final byte value) {
@@ -130,102 +147,124 @@ public class Huami2021Config {
}
}
- public enum ArgType {
- BOOL(0x0b),
- STRING(0x20),
- SHORT(0x01),
- INT(0x03),
- BYTE(0x10),
- DATETIME_HH_MM(0x30),
- ;
-
- private final byte value;
-
- ArgType(int value) {
- this.value = (byte) value;
- }
-
- public byte getValue() {
- return value;
- }
- }
-
public enum ConfigArg {
// Display
- SCREEN_BRIGHTNESS(ConfigType.DISPLAY, ArgType.SHORT, 0x02, PREF_SCREEN_BRIGHTNESS),
- SCREEN_TIMEOUT(ConfigType.DISPLAY, ArgType.BYTE, 0x03, PREF_SCREEN_TIMEOUT),
- ALWAYS_ON_DISPLAY_MODE(ConfigType.DISPLAY, ArgType.BYTE, 0x04, PREF_ALWAYS_ON_DISPLAY_MODE),
- ALWAYS_ON_DISPLAY_SCHEDULED_START(ConfigType.DISPLAY, ArgType.DATETIME_HH_MM, 0x05, PREF_ALWAYS_ON_DISPLAY_START),
- ALWAYS_ON_DISPLAY_SCHEDULED_END(ConfigType.DISPLAY, ArgType.DATETIME_HH_MM, 0x06, PREF_ALWAYS_ON_DISPLAY_END),
- LIFT_WRIST_MODE(ConfigType.DISPLAY, ArgType.BYTE, 0x08, PREF_ACTIVATE_DISPLAY_ON_LIFT),
- LIFT_WRIST_SCHEDULED_START(ConfigType.DISPLAY, ArgType.DATETIME_HH_MM, 0x09, PREF_DISPLAY_ON_LIFT_START),
- LIFT_WRIST_SCHEDULED_END(ConfigType.DISPLAY, ArgType.DATETIME_HH_MM, 0x0a, PREF_DISPLAY_ON_LIFT_END),
- LIFT_WRIST_RESPONSE_SENSITIVITY(ConfigType.DISPLAY, ArgType.BYTE, 0x0b, PREF_DISPLAY_ON_LIFT_SENSITIVITY),
- SCREEN_ON_ON_NOTIFICATIONS(ConfigType.DISPLAY, ArgType.BOOL, 0x0c, PREF_SCREEN_ON_ON_NOTIFICATIONS),
+ SCREEN_AUTO_BRIGHTNESS(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x01, PREF_SCREEN_AUTO_BRIGHTNESS),
+ SCREEN_BRIGHTNESS(ConfigGroup.DISPLAY, ConfigType.SHORT, 0x02, PREF_SCREEN_BRIGHTNESS),
+ SCREEN_TIMEOUT(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x03, PREF_SCREEN_TIMEOUT),
+ ALWAYS_ON_DISPLAY_MODE(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x04, PREF_ALWAYS_ON_DISPLAY_MODE),
+ ALWAYS_ON_DISPLAY_SCHEDULED_START(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x05, PREF_ALWAYS_ON_DISPLAY_START),
+ ALWAYS_ON_DISPLAY_SCHEDULED_END(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x06, PREF_ALWAYS_ON_DISPLAY_END),
+ LIFT_WRIST_MODE(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x08, PREF_ACTIVATE_DISPLAY_ON_LIFT),
+ LIFT_WRIST_SCHEDULED_START(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x09, PREF_DISPLAY_ON_LIFT_START),
+ LIFT_WRIST_SCHEDULED_END(ConfigGroup.DISPLAY, ConfigType.DATETIME_HH_MM, 0x0a, PREF_DISPLAY_ON_LIFT_END),
+ LIFT_WRIST_RESPONSE_SENSITIVITY(ConfigGroup.DISPLAY, ConfigType.BYTE, 0x0b, PREF_DISPLAY_ON_LIFT_SENSITIVITY),
+ SCREEN_ON_ON_NOTIFICATIONS(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x0c, PREF_SCREEN_ON_ON_NOTIFICATIONS),
+ ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE(ConfigGroup.DISPLAY, ConfigType.BOOL, 0x0e, PREF_ALWAYS_ON_DISPLAY_FOLLOW_WATCHFACE),
+ ALWAYS_ON_DISPLAY_STYLE(ConfigGroup.DISPLAY, ConfigType.STRING_LIST, 0x0f, PREF_ALWAYS_ON_DISPLAY_STYLE),
+
+ // Sound and Vibration
+ VOLUME(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.SHORT, 0x02, PREF_VOLUME),
+ CROWN_VIBRATION(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x06, PREF_CROWN_VIBRATION),
+ ALERT_TONE(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x07, PREF_ALERT_TONE),
+ COVER_TO_MUTE(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x08, PREF_COVER_TO_MUTE),
+ VIBRATE_FOR_ALERT(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x09, PREF_VIBRATE_FOR_ALERT),
+ TEXT_TO_SPEECH(ConfigGroup.SOUND_AND_VIBRATION, ConfigType.BOOL, 0x0a, PREF_TEXT_TO_SPEECH),
+
+ // Wearing Direction
+ WEARING_DIRECTION_BUTTONS(ConfigGroup.WEARING_DIRECTION, ConfigType.BYTE, 0x02, PREF_WEARDIRECTION),
+
+ // Offline Voice
+ OFFLINE_VOICE_RESPOND_TURN_WRIST(ConfigGroup.OFFLINE_VOICE, ConfigType.BOOL, 0x01, PREF_OFFLINE_VOICE_RESPOND_TURN_WRIST),
+ OFFLINE_VOICE_RESPOND_SCREEN_ON(ConfigGroup.OFFLINE_VOICE, ConfigType.BOOL, 0x02, PREF_OFFLINE_VOICE_RESPOND_SCREEN_ON),
+ OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING(ConfigGroup.OFFLINE_VOICE, ConfigType.BOOL, 0x03, PREF_OFFLINE_VOICE_RESPONSE_DURING_SCREEN_LIGHTING),
+ OFFLINE_VOICE_LANGUAGE(ConfigGroup.OFFLINE_VOICE, ConfigType.BYTE, 0x04, PREF_OFFLINE_VOICE_LANGUAGE),
// Lock Screen
- PASSWORD_ENABLED(ConfigType.LOCKSCREEN, ArgType.BOOL, 0x01, PREF_PASSWORD_ENABLED),
- PASSWORD_TEXT(ConfigType.LOCKSCREEN, ArgType.STRING, 0x02, PREF_PASSWORD),
+ PASSWORD_ENABLED(ConfigGroup.LOCKSCREEN, ConfigType.BOOL, 0x01, PREF_PASSWORD_ENABLED),
+ PASSWORD_TEXT(ConfigGroup.LOCKSCREEN, ConfigType.STRING, 0x02, PREF_PASSWORD),
// Language
- LANGUAGE(ConfigType.LANGUAGE, ArgType.BYTE, 0x01, PREF_LANGUAGE),
- LANGUAGE_FOLLOW_PHONE(ConfigType.LANGUAGE, ArgType.BOOL, 0x02, null),
+ LANGUAGE(ConfigGroup.LANGUAGE, ConfigType.BYTE, 0x01, PREF_LANGUAGE),
+ LANGUAGE_FOLLOW_PHONE(ConfigGroup.LANGUAGE, ConfigType.BOOL, 0x02, null /* special case, handled below */),
// Health
- HEART_RATE_ALL_DAY_MONITORING(ConfigType.HEALTH, ArgType.BYTE, 0x01, PREF_HEARTRATE_MEASUREMENT_INTERVAL),
- HEART_RATE_HIGH_ALERTS(ConfigType.HEALTH, ArgType.BYTE, 0x02, PREF_HEARTRATE_ALERT_HIGH_THRESHOLD),
- HEART_RATE_LOW_ALERTS(ConfigType.HEALTH, ArgType.BYTE, 0x03, PREF_HEARTRATE_ALERT_LOW_THRESHOLD),
- THIRD_PARTY_HR_SHARING(ConfigType.HEALTH, ArgType.BOOL, 0x05, PREF_EXPOSE_HR_THIRDPARTY),
- SLEEP_HIGH_ACCURACY_MONITORING(ConfigType.HEALTH, ArgType.BOOL, 0x11, PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION),
- SLEEP_BREATHING_QUALITY_MONITORING(ConfigType.HEALTH, ArgType.BOOL, 0x12, PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING),
- STRESS_MONITORING(ConfigType.HEALTH, ArgType.BOOL, 0x13, PREF_HEARTRATE_STRESS_MONITORING),
- STRESS_RELAXATION_REMINDER(ConfigType.HEALTH, ArgType.BOOL, 0x14, PREF_HEARTRATE_STRESS_RELAXATION_REMINDER),
- SPO2_ALL_DAY_MONITORING(ConfigType.HEALTH, ArgType.BOOL, 0x31, PREF_SPO2_ALL_DAY_MONITORING),
- SPO2_LOW_ALERT(ConfigType.HEALTH, ArgType.BYTE, 0x32, PREF_SPO2_LOW_ALERT_THRESHOLD),
- FITNESS_GOAL_NOTIFICATION(ConfigType.HEALTH, ArgType.BOOL, 0x51, PREF_USER_FITNESS_GOAL_NOTIFICATION), // TODO is it?
- FITNESS_GOAL_STEPS(ConfigType.HEALTH, ArgType.INT, 0x52, null), // TODO needs to be handled globally
- INACTIVITY_WARNINGS_ENABLED(ConfigType.HEALTH, ArgType.BOOL, 0x41, PREF_INACTIVITY_ENABLE),
- INACTIVITY_WARNINGS_SCHEDULED_START(ConfigType.HEALTH, ArgType.DATETIME_HH_MM, 0x42, PREF_INACTIVITY_START),
- INACTIVITY_WARNINGS_SCHEDULED_END(ConfigType.HEALTH, ArgType.DATETIME_HH_MM, 0x43, PREF_INACTIVITY_END),
- INACTIVITY_WARNINGS_DND_ENABLED(ConfigType.HEALTH, ArgType.BOOL, 0x44, PREF_INACTIVITY_DND),
- INACTIVITY_WARNINGS_DND_SCHEDULED_START(ConfigType.HEALTH, ArgType.DATETIME_HH_MM, 0x45, PREF_INACTIVITY_DND_START),
- INACTIVITY_WARNINGS_DND_SCHEDULED_END(ConfigType.HEALTH, ArgType.DATETIME_HH_MM, 0x46, PREF_INACTIVITY_DND_END),
+ HEART_RATE_ALL_DAY_MONITORING(ConfigGroup.HEALTH, ConfigType.BYTE, 0x01, PREF_HEARTRATE_MEASUREMENT_INTERVAL),
+ HEART_RATE_HIGH_ALERTS(ConfigGroup.HEALTH, ConfigType.BYTE, 0x02, PREF_HEARTRATE_ALERT_HIGH_THRESHOLD),
+ HEART_RATE_LOW_ALERTS(ConfigGroup.HEALTH, ConfigType.BYTE, 0x03, PREF_HEARTRATE_ALERT_LOW_THRESHOLD),
+ HEART_RATE_ACTIVITY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x04, PREF_HEARTRATE_ACTIVITY_MONITORING),
+ THIRD_PARTY_HR_SHARING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x05, PREF_EXPOSE_HR_THIRDPARTY),
+ SLEEP_HIGH_ACCURACY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x11, PREF_HEARTRATE_USE_FOR_SLEEP_DETECTION),
+ SLEEP_BREATHING_QUALITY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x12, PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING),
+ STRESS_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x13, PREF_HEARTRATE_STRESS_MONITORING),
+ STRESS_RELAXATION_REMINDER(ConfigGroup.HEALTH, ConfigType.BOOL, 0x14, PREF_HEARTRATE_STRESS_RELAXATION_REMINDER),
+ SPO2_ALL_DAY_MONITORING(ConfigGroup.HEALTH, ConfigType.BOOL, 0x31, PREF_SPO2_ALL_DAY_MONITORING),
+ SPO2_LOW_ALERT(ConfigGroup.HEALTH, ConfigType.BYTE, 0x32, PREF_SPO2_LOW_ALERT_THRESHOLD),
+ FITNESS_GOAL_NOTIFICATION(ConfigGroup.HEALTH, ConfigType.BOOL, 0x51, PREF_USER_FITNESS_GOAL_NOTIFICATION),
+ FITNESS_GOAL_STEPS(ConfigGroup.HEALTH, ConfigType.INT, 0x52, null), // TODO needs to be handled globally
+ FITNESS_GOAL_CALORIES(ConfigGroup.HEALTH, ConfigType.SHORT, 0x53, null), // TODO needs to be handled globally
+ FITNESS_GOAL_WEIGHT(ConfigGroup.HEALTH, ConfigType.SHORT, 0x54, null), // TODO needs to be handled globally
+ FITNESS_GOAL_SLEEP(ConfigGroup.HEALTH, ConfigType.SHORT, 0x55, null), // TODO needs to be handled globally
+ FITNESS_GOAL_STANDING_TIME(ConfigGroup.HEALTH, ConfigType.SHORT, 0x56, null), // TODO needs to be handled globally
+ FITNESS_GOAL_FAT_BURN_TIME(ConfigGroup.HEALTH, ConfigType.SHORT, 0x57, null), // TODO needs to be handled globally
+ INACTIVITY_WARNINGS_ENABLED(ConfigGroup.HEALTH, ConfigType.BOOL, 0x41, PREF_INACTIVITY_ENABLE),
+ INACTIVITY_WARNINGS_SCHEDULED_START(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x42, PREF_INACTIVITY_START),
+ INACTIVITY_WARNINGS_SCHEDULED_END(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x43, PREF_INACTIVITY_END),
+ INACTIVITY_WARNINGS_DND_ENABLED(ConfigGroup.HEALTH, ConfigType.BOOL, 0x44, PREF_INACTIVITY_DND),
+ INACTIVITY_WARNINGS_DND_SCHEDULED_START(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x45, PREF_INACTIVITY_DND_START),
+ INACTIVITY_WARNINGS_DND_SCHEDULED_END(ConfigGroup.HEALTH, ConfigType.DATETIME_HH_MM, 0x46, PREF_INACTIVITY_DND_END),
+
+ // Workout
+ WORKOUT_GPS_PRESET(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x20, PREF_GPS_MODE_PRESET),
+ WORKOUT_GPS_BAND(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x21, PREF_GPS_BAND),
+ WORKOUT_GPS_COMBINATION(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x22, PREF_GPS_COMBINATION),
+ WORKOUT_GPS_SATELLITE_SEARCH(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x23, PREF_GPS_SATELLITE_SEARCH),
+ WORKOUT_AGPS_EXPIRY_REMINDER_ENABLED(ConfigGroup.WORKOUT, ConfigType.BOOL, 0x30, PREF_AGPS_EXPIRY_REMINDER_ENABLED),
+ WORKOUT_AGPS_EXPIRY_REMINDER_TIME(ConfigGroup.WORKOUT, ConfigType.DATETIME_HH_MM, 0x31, PREF_AGPS_EXPIRY_REMINDER_TIME),
+ WORKOUT_DETECTION_CATEGORY(ConfigGroup.WORKOUT, ConfigType.BYTE_LIST, 0x40, PREF_WORKOUT_DETECTION_CATEGORIES),
+ WORKOUT_DETECTION_ALERT(ConfigGroup.WORKOUT, ConfigType.BOOL, 0x41, PREF_WORKOUT_DETECTION_ALERT),
+ WORKOUT_DETECTION_SENSITIVITY(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x42, PREF_WORKOUT_DETECTION_SENSITIVITY),
+ WORKOUT_POOL_SWIMMING_SIZE(ConfigGroup.WORKOUT, ConfigType.BYTE, 0x51, null), // TODO ?
// System
- TIME_FORMAT(ConfigType.SYSTEM, ArgType.BYTE, 0x01, PREF_TIMEFORMAT),
- DATE_FORMAT(ConfigType.SYSTEM, ArgType.STRING, 0x02, null),
- DND_MODE(ConfigType.SYSTEM, ArgType.BYTE, 0x0a, PREF_DO_NOT_DISTURB),
- DND_SCHEDULED_START(ConfigType.SYSTEM, ArgType.DATETIME_HH_MM, 0x0b, PREF_DO_NOT_DISTURB_START),
- DND_SCHEDULED_END(ConfigType.SYSTEM, ArgType.DATETIME_HH_MM, 0x0c, PREF_DO_NOT_DISTURB_END),
- TEMPERATURE_UNIT(ConfigType.SYSTEM, ArgType.BYTE, 0x12, null),
- TIME_FORMAT_FOLLOWS_PHONE(ConfigType.SYSTEM, ArgType.BOOL, 0x13, null),
- DISPLAY_CALLER(ConfigType.SYSTEM, ArgType.BOOL, 0x18, null), // TODO Handle
- NIGHT_MODE_MODE(ConfigType.SYSTEM, ArgType.BYTE, 0x1b, PREF_NIGHT_MODE),
- NIGHT_MODE_SCHEDULED_START(ConfigType.SYSTEM, ArgType.DATETIME_HH_MM, 0x1c, PREF_NIGHT_MODE_START),
- NIGHT_MODE_SCHEDULED_END(ConfigType.SYSTEM, ArgType.DATETIME_HH_MM, 0x1d, PREF_NIGHT_MODE_END),
+ TIME_FORMAT(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x01, PREF_TIMEFORMAT),
+ DATE_FORMAT(ConfigGroup.SYSTEM, ConfigType.STRING, 0x02, PREF_DATEFORMAT),
+ DND_MODE(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x0a, PREF_DO_NOT_DISTURB),
+ DND_SCHEDULED_START(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x0b, PREF_DO_NOT_DISTURB_START),
+ DND_SCHEDULED_END(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x0c, PREF_DO_NOT_DISTURB_END),
+ TEMPERATURE_UNIT(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x12, SettingsActivity.PREF_MEASUREMENT_SYSTEM),
+ TIME_FORMAT_FOLLOWS_PHONE(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x13, null /* special case, handled below */),
+ UPPER_BUTTON_LONG_PRESS(ConfigGroup.SYSTEM, ConfigType.STRING_LIST, 0x15, PREF_UPPER_BUTTON_LONG_PRESS),
+ LOWER_BUTTON_PRESS(ConfigGroup.SYSTEM, ConfigType.STRING_LIST, 0x16, PREF_LOWER_BUTTON_SHORT_PRESS),
+ DISPLAY_CALLER(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x18, null), // TODO Handle
+ NIGHT_MODE_MODE(ConfigGroup.SYSTEM, ConfigType.BYTE, 0x1b, PREF_NIGHT_MODE),
+ NIGHT_MODE_SCHEDULED_START(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x1c, PREF_NIGHT_MODE_START),
+ NIGHT_MODE_SCHEDULED_END(ConfigGroup.SYSTEM, ConfigType.DATETIME_HH_MM, 0x1d, PREF_NIGHT_MODE_END),
+ SLEEP_MODE_SLEEP_SCREEN(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x21, PREF_SLEEP_MODE_SLEEP_SCREEN),
+ SLEEP_MODE_SMART_ENABLE(ConfigGroup.SYSTEM, ConfigType.BOOL, 0x22, PREF_SLEEP_MODE_SMART_ENABLE),
// Bluetooth
- BLUETOOTH_CONNECTED_ADVERTISING(ConfigType.BLUETOOTH, ArgType.BOOL, 0x02, PREF_BT_CONNECTED_ADVERTISEMENT),
+ BLUETOOTH_CONNECTED_ADVERTISING(ConfigGroup.BLUETOOTH, ConfigType.BOOL, 0x02, PREF_BT_CONNECTED_ADVERTISEMENT),
;
+ private final ConfigGroup configGroup;
private final ConfigType configType;
- private final ArgType argType;
private final byte code;
private final String prefKey;
- ConfigArg(final ConfigType configType, final ArgType argType, final int code, final String prefKey) {
+ ConfigArg(final ConfigGroup configGroup, final ConfigType configType, final int code, final String prefKey) {
+ this.configGroup = configGroup;
this.configType = configType;
- this.argType = argType;
this.code = (byte) code;
this.prefKey = prefKey;
}
- public ConfigType getConfigType() {
- return configType;
+ public ConfigGroup getConfigGroup() {
+ return configGroup;
}
- public ArgType getArgType() {
- return argType;
+ public ConfigType getConfigType() {
+ return configType;
}
public byte getCode() {
@@ -236,19 +275,19 @@ public class Huami2021Config {
return prefKey;
}
- public static ConfigArg fromCode(final ConfigType configType, final byte code) {
+ public static ConfigArg fromCode(final ConfigGroup configGroup, final byte code) {
for (final Huami2021Config.ConfigArg arg : values()) {
- if (arg.getConfigType().equals(configType) && arg.getCode() == code) {
+ if (arg.getConfigGroup().equals(configGroup) && arg.getCode() == code) {
return arg;
}
}
return null;
}
- public static List getAllArgsForConfigType(final ConfigType configType) {
+ public static List getAllArgsForConfigGroup(final ConfigGroup configGroup) {
final List configArgs = new ArrayList<>();
for (final Huami2021Config.ConfigArg arg : values()) {
- if (arg.getConfigType().equals(configType)) {
+ if (arg.getConfigGroup().equals(configGroup)) {
configArgs.add(arg);
}
}
@@ -256,61 +295,254 @@ public class Huami2021Config {
}
}
- public static class ConfigSetter {
- private final ConfigType configType;
- private final Map arguments = new LinkedHashMap<>();
+ /**
+ * Map of pref key to config.
+ */
+ private static final Map PREF_TO_CONFIG = new HashMap() {{
+ for (final ConfigArg arg : ConfigArg.values()) {
+ if (arg.getPrefKey() != null) {
+ if (containsKey(arg.getPrefKey())) {
+ LOG.error("Duplicate config preference key: {}", arg);
+ continue;
+ }
+ put(arg.getPrefKey(), arg);
+ }
+ }
+ }};
- public ConfigSetter(final ConfigType configType) {
- this.configType = configType;
+ /**
+ * Updates a {@link ConfigSetter} with a preference. Default values don't really matter - if we're
+ * setting this preference, it's because the device reported it, along with the current value, so we
+ * shouldn't need a default unless there's a bug.
+ *
+ * @return true if the {@link ConfigSetter} was updated for this preference key
+ */
+ public static boolean setConfig(final Prefs prefs, final String key, final ConfigSetter setter) {
+ final ConfigArg configArg = PREF_TO_CONFIG.get(key);
+ if (configArg == null) {
+ LOG.error("Unknown pref key {}", key);
+ return false;
+ }
+
+ switch (configArg.getConfigType()) {
+ case BOOL:
+ setter.setBoolean(configArg, prefs.getBoolean(key, false));
+ return true;
+ case STRING:
+ final String encodedString = encodeString(configArg, prefs.getString(key, null));
+ if (encodedString != null) {
+ setter.setString(configArg, encodedString);
+ return true;
+ }
+ break;
+ case STRING_LIST:
+ final String encodedStringList = encodeString(configArg, prefs.getString(key, null));
+ if (encodedStringList != null) {
+ setter.setStringList(configArg, encodedStringList);
+ return true;
+ }
+ break;
+ case SHORT:
+ setter.setShort(configArg, (short) prefs.getInt(key, 0));
+ return true;
+ case INT:
+ setter.setInt(configArg, prefs.getInt(key, 0));
+ return true;
+ case BYTE:
+ final Byte encodedByte = encodeByte(configArg, prefs.getString(key, null));
+ if (encodedByte != null) {
+ setter.setByte(configArg, encodedByte);
+ return true;
+ }
+ break;
+ case BYTE_LIST:
+ final Set byteListString = prefs.getStringSet(key, Collections.emptySet());
+ final byte[] encodedByteList = new byte[byteListString.size()];
+ int i = 0;
+ for (final String s : byteListString) {
+ encodedByteList[i++] = encodeByte(configArg, s);
+ }
+ setter.setByteList(configArg, encodedByteList);
+ return true;
+ case DATETIME_HH_MM:
+ setter.setHourMinute(configArg, prefs.getTimePreference(key, "00:00"));
+ return true;
+ }
+
+ LOG.warn("Failed to set {}", configArg);
+
+ return false;
+ }
+
+ private static String encodeString(final ConfigArg configArg, final String value) {
+ if (value == null) {
+ return null;
+ }
+
+ switch (configArg) {
+ case UPPER_BUTTON_LONG_PRESS:
+ case LOWER_BUTTON_PRESS:
+ return MapUtils.reverse(Huami2021MenuType.displayItemNameLookup).get(value);
+ case DATE_FORMAT:
+ return value.replace("/", ".");
+ }
+
+ return value; // passthrough
+ }
+
+ private static Byte encodeByte(final ConfigArg configArg, final String value) {
+ if (value == null) {
+ return null;
+ }
+
+ switch (configArg) {
+ case ALWAYS_ON_DISPLAY_MODE:
+ return encodeEnum(ALWAYS_ON_DISPLAY_MAP, value);
+ case LIFT_WRIST_MODE:
+ return encodeEnum(LIFT_WRIST_MAP, value);
+ case LIFT_WRIST_RESPONSE_SENSITIVITY:
+ return encodeEnum(LIFT_WRIST_SENSITIVITY_MAP, value);
+ case LANGUAGE:
+ return languageLocaleToByte(value);
+ case HEART_RATE_ALL_DAY_MONITORING:
+ return encodeHeartRateAllDayMonitoring(value);
+ case SCREEN_TIMEOUT:
+ case HEART_RATE_HIGH_ALERTS:
+ case HEART_RATE_LOW_ALERTS:
+ case SPO2_LOW_ALERT:
+ return (byte) Integer.parseInt(value);
+ case TIME_FORMAT:
+ return encodeString(TIME_FORMAT_MAP, value);
+ case DND_MODE:
+ return encodeEnum(DND_MODE_MAP, value);
+ case TEMPERATURE_UNIT:
+ return encodeEnum(TEMPERATURE_UNIT_MAP, value);
+ case NIGHT_MODE_MODE:
+ return encodeString(NIGHT_MODE_MAP, value);
+ case WEARING_DIRECTION_BUTTONS:
+ return encodeString(WEARING_DIRECTION_MAP, value);
+ case OFFLINE_VOICE_LANGUAGE:
+ return encodeString(OFFLINE_VOICE_LANGUAGE_MAP, value);
+ case WORKOUT_GPS_PRESET:
+ return encodeEnum(GPS_PRESET_MAP, value);
+ case WORKOUT_GPS_BAND:
+ return encodeEnum(GPS_BAND_MAP, value);
+ case WORKOUT_GPS_COMBINATION:
+ return encodeEnum(GPS_COMBINATION_MAP, value);
+ case WORKOUT_GPS_SATELLITE_SEARCH:
+ return encodeEnum(GPS_SATELLITE_SEARCH_MAP, value);
+ case WORKOUT_DETECTION_CATEGORY:
+ return encodeEnum(WORKOUT_DETECTION_CATEGORY_MAP, value);
+ case WORKOUT_DETECTION_SENSITIVITY:
+ return encodeEnum(WORKOUT_DETECTION_SENSITIVITY_MAP, value);
+ }
+
+ LOG.error("No encoder for {}", configArg);
+
+ return null;
+ }
+
+ /**
+ * Returns the preference key where to save the minimum possible value for a preference.
+ */
+ public static String getPrefMinKey(final String key) {
+ return String.format(Locale.ROOT, "%s_huami_2021_min", key);
+ }
+
+ /**
+ * Returns the preference key where to save the maximum possible value for a preference.
+ */
+ public static String getPrefMaxKey(final String key) {
+ 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 Huami2021Config.ConfigArg config) {
+ return devicePrefs.getBoolean(getPrefKnownConfig(config), false);
+ }
+
+ public static class ConfigSetter {
+ private final Map> arguments = new LinkedHashMap<>();
+
+ public ConfigSetter() {
}
public ConfigSetter setBoolean(final ConfigArg arg, final boolean value) {
- checkArg(arg, ArgType.BOOL);
+ checkArg(arg, ConfigType.BOOL);
- arguments.put(arg, new byte[]{(byte) (value ? 0x01 : 0x00)});
+ putArgument(arg, new byte[]{(byte) (value ? 0x01 : 0x00)});
return this;
}
public ConfigSetter setString(final ConfigArg arg, final String value) {
- checkArg(arg, ArgType.STRING);
+ checkArg(arg, ConfigType.STRING);
- arguments.put(arg, (value + "\0").getBytes(StandardCharsets.UTF_8));
+ putArgument(arg, (value + "\0").getBytes(StandardCharsets.UTF_8));
+
+ return this;
+ }
+
+ public ConfigSetter setStringList(final ConfigArg arg, final String value) {
+ checkArg(arg, ConfigType.STRING_LIST);
+
+ putArgument(arg, (value + "\0").getBytes(StandardCharsets.UTF_8));
return this;
}
public ConfigSetter setShort(final ConfigArg arg, final short value) {
- checkArg(arg, ArgType.SHORT);
+ checkArg(arg, ConfigType.SHORT);
- arguments.put(arg, BLETypeConversions.fromUint16(value));
+ putArgument(arg, BLETypeConversions.fromUint16(value));
return this;
}
public ConfigSetter setInt(final ConfigArg arg, final int value) {
- checkArg(arg, ArgType.INT);
+ checkArg(arg, ConfigType.INT);
- arguments.put(arg, BLETypeConversions.fromUint32(value));
+ putArgument(arg, BLETypeConversions.fromUint32(value));
return this;
}
public ConfigSetter setByte(final ConfigArg arg, final byte value) {
- checkArg(arg, ArgType.BYTE);
+ checkArg(arg, ConfigType.BYTE);
- arguments.put(arg, new byte[]{value});
+ putArgument(arg, new byte[]{value});
+
+ return this;
+ }
+
+ public ConfigSetter setByteList(final ConfigArg arg, final byte[] values) {
+ checkArg(arg, ConfigType.BYTE_LIST);
+
+ putArgument(arg, ArrayUtils.addAll(new byte[]{(byte) values.length}, values));
return this;
}
public ConfigSetter setHourMinute(final ConfigArg arg, final Date date) {
- checkArg(arg, ArgType.DATETIME_HH_MM);
+ checkArg(arg, ConfigType.DATETIME_HH_MM);
final Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(date);
- arguments.put(arg, new byte[]{
+ putArgument(arg, new byte[]{
(byte) calendar.get(Calendar.HOUR_OF_DAY),
(byte) calendar.get(Calendar.MINUTE)
});
@@ -318,19 +550,33 @@ public class Huami2021Config {
return this;
}
- public byte[] encode() {
+ private void putArgument(final ConfigArg arg, final byte[] encodedValue) {
+ final Map groupMap;
+ if (arguments.containsKey(arg.getConfigGroup())) {
+ groupMap = arguments.get(arg.getConfigGroup());
+ } else {
+ groupMap = new LinkedHashMap<>();
+ arguments.put(arg.getConfigGroup(), groupMap);
+ }
+
+ groupMap.put(arg, encodedValue);
+ }
+
+ public byte[] encode(final ConfigGroup configGroup) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final Map configArgMap = arguments.get(configGroup);
+
try {
baos.write(CONFIG_CMD_SET);
- baos.write(configType.getValue());
- baos.write(configType.getNextByte());
+ baos.write(configGroup.getValue());
+ baos.write(configGroup.getVersion());
baos.write(0x00); // ?
- baos.write(arguments.size());
- for (final Map.Entry arg : arguments.entrySet()) {
- final ArgType argType = arg.getKey().getArgType();
+ baos.write(configArgMap.size());
+ for (final Map.Entry arg : configArgMap.entrySet()) {
+ final ConfigType configType = arg.getKey().getConfigType();
baos.write(arg.getKey().getCode());
- baos.write(argType.getValue());
+ baos.write(configType.getValue());
baos.write(arg.getValue());
}
} catch (final IOException e) {
@@ -341,32 +587,21 @@ public class Huami2021Config {
}
public void write(final Huami2021Support support, final TransactionBuilder builder) {
- support.writeToChunked2021(builder, CHUNKED2021_ENDPOINT_CONFIG, encode(), true);
- }
-
- public void write(final Huami2021Support support) {
- try {
- final TransactionBuilder builder = support.performInitialized("write config");
- support.writeToChunked2021(builder, CHUNKED2021_ENDPOINT_CONFIG, encode(), true);
- builder.queue(support.getQueue());
- } catch (final Exception e) {
- LOG.error("Failed to write config", e);
+ // Write one command per config group
+ for (final ConfigGroup configGroup : arguments.keySet()) {
+ support.writeToChunked2021(builder, CHUNKED2021_ENDPOINT_CONFIG, encode(configGroup), true);
}
}
- private void checkArg(final ConfigArg arg, final ArgType expectedArgType) {
+ private void checkArg(final ConfigArg arg, final ConfigType expectedConfigType) {
try {
- if (!configType.equals(arg.getConfigType())) {
- throw new IllegalArgumentException("Unexpected config type " + arg.getConfigType());
- }
-
- if (!expectedArgType.equals(arg.getArgType())) {
+ if (!expectedConfigType.equals(arg.getConfigType())) {
throw new IllegalArgumentException(
String.format(
"Invalid arg type %s for %s, expected %s",
- expectedArgType,
+ expectedConfigType,
arg,
- arg.getArgType()
+ arg.getConfigType()
)
);
}
@@ -384,101 +619,161 @@ public class Huami2021Config {
public static class ConfigParser {
private static final Logger LOG = LoggerFactory.getLogger(ConfigParser.class);
- private final ConfigType configType;
+ private final ConfigGroup configGroup;
+ private final boolean includesConstraints;
- public ConfigParser(final ConfigType configType) {
- this.configType = configType;
+ public ConfigParser(final ConfigGroup configGroup, final boolean includesConstraints) {
+ this.configGroup = configGroup;
+ this.includesConstraints = includesConstraints;
}
public Map parse(final int expectedNumConfigs, final byte[] bytes) {
+ final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
final Map prefs = new HashMap<>();
int configCount = 0;
- int pos = 0;
- while (pos < bytes.length) {
+ while (buf.position() < buf.limit()) {
if (configCount > expectedNumConfigs) {
LOG.error("Got more configs than {}", expectedNumConfigs);
return null;
}
- final Huami2021Config.ConfigArg configArg = Huami2021Config.ConfigArg.fromCode(configType, bytes[pos]);
+ final byte configArgByte = buf.get();
+ final Huami2021Config.ConfigArg configArg = Huami2021Config.ConfigArg.fromCode(configGroup, configArgByte);
if (configArg == null) {
- LOG.error("Unknown config {} for {} at {}", String.format("0x%02x", bytes[pos]), configType, pos);
- return null;
+ LOG.error("Unknown config {} for {}", String.format("0x%02x", configArgByte), configGroup);
}
- pos++;
-
- final boolean unexpectedType = (bytes[pos] != configArg.getArgType().getValue());
- if (unexpectedType) {
- LOG.warn("Unexpected arg type {} for {}, expected {}", String.format("0x%02x", bytes[pos]), configArg, configArg.getArgType());
+ final byte configTypeByte = buf.get();
+ final ConfigType configType = ConfigType.fromValue(configTypeByte);
+ if (configType == null) {
+ LOG.error("Unknown type {} for {}", String.format("0x%02x", configTypeByte), configArg);
+ // Abort, since we don't know how to parse this type or how many bytes it is
+ // Return whatever we parsed so far, since that's still valid
+ return prefs;
+ }
+ if (configArg != null) {
+ if (configType != configArg.getConfigType()) {
+ LOG.warn("Unexpected arg type {} for {}, expected {}", configType, configArg, configArg.getConfigType());
+ }
}
- pos++;
+ Map argPrefs = null;
- final Map argPrefs;
-
- switch (configArg.getArgType()) {
+ // FIXME this switch has a lot of repeated code that could be generalized...
+ switch (configType) {
case BOOL:
- final boolean valBoolean = bytes[pos] == 1;
- LOG.info("Got {} for {} = {}", configArg.getArgType(), configArg, valBoolean);
- argPrefs = convertBooleanToPrefs(configArg, valBoolean);
- pos += 1;
+ final ConfigBoolean valBoolean = ConfigBoolean.consume(buf);
+ if (valBoolean == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valBoolean);
+ if (configArg != null) {
+ argPrefs = convertBooleanToPrefs(configArg, valBoolean);
+ }
break;
case STRING:
- final String valString = StringUtils.untilNullTerminator(bytes, pos);
- LOG.info("Got {} for {} = {}", configArg.getArgType(), configArg, valString);
- argPrefs = convertStringToPrefs(configArg, valString);
- pos += valString.length() + 1;
+ final ConfigString valString = ConfigString.consume(buf, includesConstraints);
+ if (valString == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valString);
+ if (configArg != null) {
+ argPrefs = convertStringToPrefs(configArg, valString);
+ }
+ break;
+ case STRING_LIST:
+ final ConfigStringList valStringList = ConfigStringList.consume(buf, includesConstraints);
+ if (valStringList == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valStringList);
+ if (configArg != null) {
+ argPrefs = convertStringListToPrefs(configArg, valStringList);
+ }
break;
case SHORT:
- final int valShort = BLETypeConversions.toUint16(subarray(bytes, pos, pos + 2));
- LOG.info("Got {} for {} = {}", configArg.getArgType(), configArg, valShort);
- argPrefs = convertNumberToPrefs(configArg, valShort);
- pos += 2;
+ final ConfigShort valShort = ConfigShort.consume(buf, includesConstraints);
+ if (valShort == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valShort);
+ if (configArg != null) {
+ argPrefs = convertShortToPrefs(configArg, valShort);
+ }
break;
case INT:
- final int valInt = BLETypeConversions.toUint32(subarray(bytes, pos, pos + 4));
- LOG.info("Got {} for {} = {}", configArg.getArgType(), configArg, valInt);
- argPrefs = convertNumberToPrefs(configArg, valInt);
- pos += 4;
+ final ConfigInt valInt = ConfigInt.consume(buf, includesConstraints);
+ if (valInt == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valInt);
+ if (configArg != null) {
+ argPrefs = convertIntToPrefs(configArg, valInt);
+ }
break;
case BYTE:
- final byte valByte = bytes[pos];
- LOG.info("Got {} for {} = {}", configArg.getArgType(), configArg, valByte);
- argPrefs = convertByteToPrefs(configArg, valByte);
- pos += 1;
+ final ConfigByte valByte = ConfigByte.consume(buf, includesConstraints);
+ if (valByte == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valByte);
+ if (configArg != null) {
+ argPrefs = convertByteToPrefs(configArg, valByte);
+ }
+ break;
+ case BYTE_LIST:
+ final ConfigByteList valByteList = ConfigByteList.consume(buf, includesConstraints);
+ if (valByteList == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valByteList);
+ if (configArg != null) {
+ argPrefs = convertByteListToPrefs(configArg, valByteList);
+ }
break;
case DATETIME_HH_MM:
- final DateFormat df = new SimpleDateFormat("HH:mm", Locale.getDefault());
- final String hhmm = String.format(Locale.ROOT, "%02d:%02d", bytes[pos], bytes[pos + 1]);
- try {
- df.parse(hhmm);
- } catch (final ParseException e) {
- LOG.error("Failed to parse HH:mm from {}", hhmm);
- return null;
+ final ConfigDatetimeHhMm valHhMm = ConfigDatetimeHhMm.consume(buf);
+ if (valHhMm == null) {
+ LOG.error("Failed to parse {} for {}", configType, configArg);
+ return prefs;
+ }
+ LOG.info("Got {} ({}) = {}", configArg, String.format("0x%02x", configArgByte), valHhMm);
+ if (configArg != null) {
+ argPrefs = convertDatetimeHhMmToPrefs(configArg, valHhMm);
}
- LOG.info("Got {} for {} = {}", configArg.getArgType(), configArg, hhmm);
- argPrefs = convertDatetimeHhMmToPrefs(configArg, hhmm);
- pos += 2;
break;
default:
- LOG.error("Unknown arg type {}", configArg);
- configCount++;
- continue;
+ LOG.error("No parser for {}", configArg);
+ // Abort, since we don't know how to parse this type or how many bytes it is
+ // Return whatever we parsed so far, since that's still valid
+ return prefs;
}
- if (argPrefs != null && !unexpectedType) {
+ if (argPrefs == null) {
+ LOG.warn("Unhandled {} pref of type {}", configType, configArg);
+ }
+
+ if (configArg != null && argPrefs != null && configType == configArg.getConfigType()) {
+ prefs.put(getPrefKnownConfig(configArg), true);
+
// Special cases for "follow phone" preferences. We need to ensure that "auto"
// always has precedence
if (argPrefs.containsKey(PREF_LANGUAGE) && prefs.containsKey(PREF_LANGUAGE)) {
- if (prefs.get(PREF_LANGUAGE).equals(DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
+ if (Objects.equals(prefs.get(PREF_LANGUAGE), DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
argPrefs.remove(PREF_LANGUAGE);
}
}
if (argPrefs.containsKey(PREF_TIMEFORMAT) && prefs.containsKey(PREF_TIMEFORMAT)) {
- if (prefs.get(PREF_TIMEFORMAT).equals(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO)) {
+ if (Objects.equals(prefs.get(PREF_TIMEFORMAT), DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO)) {
argPrefs.remove(PREF_TIMEFORMAT);
}
}
@@ -492,22 +787,18 @@ public class Huami2021Config {
return prefs;
}
- private static Map convertBooleanToPrefs(final ConfigArg configArg, final boolean value) {
- if (configArg.getPrefKey() != null) {
- // The arg maps to a boolean pref directly
- return singletonMap(configArg.getPrefKey(), value);
- }
-
- switch(configArg) {
+ private static Map convertBooleanToPrefs(final ConfigArg configArg, final ConfigBoolean value) {
+ // Special cases
+ switch (configArg) {
case LANGUAGE_FOLLOW_PHONE:
- if (value) {
+ if (value.getValue()) {
return singletonMap(PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO);
} else {
// If not following phone, we'll receive the actual value in LANGUAGE
return Collections.emptyMap();
}
case TIME_FORMAT_FOLLOWS_PHONE:
- if (value) {
+ if (value.getValue()) {
return singletonMap(PREF_TIMEFORMAT, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO);
} else {
// If not following phone, we'll receive the actual value in TIME_FORMAT
@@ -517,151 +808,766 @@ public class Huami2021Config {
break;
}
- LOG.warn("Unhandled Boolean pref {}", configArg);
+ if (configArg.getPrefKey() != null) {
+ // The arg maps to a boolean pref directly
+ return singletonMap(configArg.getPrefKey(), value.getValue());
+ }
+
return null;
}
- private static Map convertStringToPrefs(final ConfigArg configArg, final String str) {
- if (configArg.getPrefKey() != null) {
- // The arg maps to a string pref directly
- return singletonMap(configArg.getPrefKey(), str);
- }
-
- switch(configArg) {
+ private static Map convertStringToPrefs(final ConfigArg configArg, final ConfigString str) {
+ // Special cases
+ switch (configArg) {
case DATE_FORMAT:
- return singletonMap(PREF_DATEFORMAT, str.replace(".", "/").toUpperCase(Locale.ROOT));
+ return singletonMap(PREF_DATEFORMAT, str.getValue().replace(".", "/").toUpperCase(Locale.ROOT));
default:
break;
}
- LOG.warn("Unhandled String pref {}", configArg);
+ if (configArg.getPrefKey() != null) {
+ // The arg maps to a string pref directly
+ return singletonMap(configArg.getPrefKey(), str.getValue());
+ }
+
return null;
}
- private static Map convertNumberToPrefs(final ConfigArg configArg, final int value) {
+ private static Map convertStringListToPrefs(final ConfigArg configArg, final ConfigStringList str) {
+ final List possibleValues = str.getPossibleValues();
+ final boolean includesConstraints = !possibleValues.isEmpty();
+ Map prefs = null;
+ final ValueDecoder decoder;
+
+ switch (configArg) {
+ case UPPER_BUTTON_LONG_PRESS:
+ case LOWER_BUTTON_PRESS:
+ decoder = Huami2021MenuType.displayItemNameLookup::get;
+ break;
+ default:
+ decoder = a -> a; // passthrough
+ }
+
+ if (configArg.getPrefKey() != null) {
+ prefs = singletonMap(configArg.getPrefKey(), decoder.decode(str.getValue()));
+ if (includesConstraints) {
+ prefs.put(
+ getPrefPossibleValuesKey(configArg.getPrefKey()),
+ decodeStringValues(possibleValues, decoder)
+ );
+ }
+ }
+
+ return prefs;
+ }
+
+ private static Map convertShortToPrefs(final ConfigArg configArg, final ConfigShort value) {
if (configArg.getPrefKey() != null) {
// The arg maps to a number pref directly
- return singletonMap(configArg.getPrefKey(), value);
+ final Map prefs = singletonMap(configArg.getPrefKey(), value.getValue());
+
+ if (value.isMinMaxKnown()) {
+ prefs.put(getPrefMinKey(configArg.getPrefKey()), value.getMin());
+ prefs.put(getPrefMaxKey(configArg.getPrefKey()), value.getMax());
+ }
+
+ return prefs;
}
- LOG.warn("Unhandled number pref {}", configArg);
return null;
}
- private static Map convertDatetimeHhMmToPrefs(final ConfigArg configArg, final String hhmm) {
+ private static Map convertIntToPrefs(final ConfigArg configArg, final ConfigInt value) {
+ if (configArg.getPrefKey() != null) {
+ // The arg maps to a number pref directly
+ final Map prefs = singletonMap(configArg.getPrefKey(), value.getValue());
+
+ if (value.isMinMaxKnown()) {
+ prefs.put(getPrefMinKey(configArg.getPrefKey()), value.getMin());
+ prefs.put(getPrefMaxKey(configArg.getPrefKey()), value.getMax());
+ }
+
+ return prefs;
+ }
+
+ return null;
+ }
+
+ private static Map convertDatetimeHhMmToPrefs(final ConfigArg configArg, final ConfigDatetimeHhMm hhmm) {
if (configArg.getPrefKey() != null) {
// The arg maps to a hhmm pref directly
- return singletonMap(configArg.getPrefKey(), hhmm);
+ return singletonMap(configArg.getPrefKey(), hhmm.getValue());
}
- LOG.warn("Unhandled datetime pref {}", configArg);
return null;
}
- private static Map convertByteToPrefs(final ConfigArg configArg, final byte b) {
- switch(configArg) {
+ private static Map convertByteListToPrefs(final ConfigArg configArg, final ConfigByteList value) {
+ final byte[] possibleValues = value.getPossibleValues();
+ final boolean includesConstraints = possibleValues != null && possibleValues.length > 0;
+ Map prefs = null;
+ final ValueDecoder decoder;
+
+ switch (configArg) {
+ case WORKOUT_DETECTION_CATEGORY:
+ decoder = b -> decodeEnum(WORKOUT_DETECTION_CATEGORY_MAP, b);
+ break;
+ default:
+ LOG.warn("No decoder for {}", configArg);
+ return null;
+ }
+
+ if (configArg.getPrefKey() != null) {
+ final List valuesList = decodeByteValues(value.getValues(), decoder);
+ prefs = singletonMap(configArg.getPrefKey(), new HashSet<>(valuesList));
+ if (includesConstraints) {
+ prefs.put(
+ getPrefPossibleValuesKey(configArg.getPrefKey()),
+ decodeByteValues(possibleValues, decoder)
+ );
+ }
+ }
+
+ return prefs;
+ }
+
+ private static Map convertByteToPrefs(final ConfigArg configArg, final ConfigByte value) {
+ final byte[] possibleValues = value.getPossibleValues();
+ final boolean includesConstraints = value.getPossibleValues().length > 0;
+ Map prefs = null;
+ final ValueDecoder decoder;
+
+ switch (configArg) {
case ALWAYS_ON_DISPLAY_MODE:
- switch(b) {
- case 0x00:
- return singletonMap(configArg.getPrefKey(), AlwaysOnDisplay.OFF.name().toLowerCase(Locale.ROOT));
- case 0x01:
- return singletonMap(configArg.getPrefKey(), AlwaysOnDisplay.AUTOMATIC.name().toLowerCase(Locale.ROOT));
- case 0x02:
- return singletonMap(configArg.getPrefKey(), AlwaysOnDisplay.SCHEDULED.name().toLowerCase(Locale.ROOT));
- case 0x03:
- return singletonMap(configArg.getPrefKey(), AlwaysOnDisplay.ALWAYS.name().toLowerCase(Locale.ROOT));
- }
+ decoder = b -> decodeEnum(ALWAYS_ON_DISPLAY_MAP, b);
break;
case LIFT_WRIST_MODE:
- switch(b) {
- case 0x00:
- return singletonMap(configArg.getPrefKey(), ActivateDisplayOnLift.OFF.name().toLowerCase(Locale.ROOT));
- case 0x01:
- return singletonMap(configArg.getPrefKey(), ActivateDisplayOnLift.SCHEDULED.name().toLowerCase(Locale.ROOT));
- case 0x02:
- return singletonMap(configArg.getPrefKey(), ActivateDisplayOnLift.ON.name().toLowerCase(Locale.ROOT));
- }
+ decoder = b -> decodeEnum(LIFT_WRIST_MAP, b);
break;
case LIFT_WRIST_RESPONSE_SENSITIVITY:
- switch(b) {
- case 0x00:
- return singletonMap(configArg.getPrefKey(), ActivateDisplayOnLiftSensitivity.NORMAL.name().toLowerCase(Locale.ROOT));
- case 0x01:
- return singletonMap(configArg.getPrefKey(), ActivateDisplayOnLiftSensitivity.SENSITIVE.name().toLowerCase(Locale.ROOT));
- }
+ decoder = b -> decodeEnum(LIFT_WRIST_SENSITIVITY_MAP, b);
break;
case LANGUAGE:
- final Map reverseLanguageLookup = MapUtils.reverse(HuamiLanguageType.idLookup);
- final String language = reverseLanguageLookup.get(b & 0xff);
+ final String language = languageByteToLocale(value.getValue());
if (language != null) {
- return singletonMap(configArg.getPrefKey(), language);
+ prefs = singletonMap(configArg.getPrefKey(), language);
+ if (includesConstraints) {
+ final List possibleLanguages = new ArrayList<>();
+ possibleLanguages.add("auto");
+ for (final byte possibleValue : value.getPossibleValues()) {
+ possibleLanguages.add(languageByteToLocale(possibleValue));
+ }
+ possibleLanguages.removeAll(Collections.singleton(null));
+ prefs.put(getPrefPossibleValuesKey(configArg.getPrefKey()), String.join(",", possibleLanguages));
+ }
}
+ decoder = null;
break;
case HEART_RATE_ALL_DAY_MONITORING:
- if (b > 0) {
- return singletonMap(configArg.getPrefKey(), String.format("%d", (b & 0xff) * 60));
- } else {
- return singletonMap(configArg.getPrefKey(), String.format("%d", b));
- }
+ decoder = Huami2021Config::decodeHeartRateAllDayMonitoring;
+ break;
case SCREEN_TIMEOUT:
case HEART_RATE_HIGH_ALERTS:
case HEART_RATE_LOW_ALERTS:
case SPO2_LOW_ALERT:
- return singletonMap(configArg.getPrefKey(), String.format("%d", b & 0xff));
+ decoder = a -> String.format(Locale.ROOT, "%d", a & 0xff);
+ break;
case TIME_FORMAT:
- switch(b) {
- case 0x00:
- return singletonMap(configArg.getPrefKey(), DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H);
- case 0x01:
- return singletonMap(configArg.getPrefKey(), DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H);
- }
+ decoder = b -> decodeString(TIME_FORMAT_MAP, b);
break;
case DND_MODE:
- switch(b) {
- case 0x00:
- return singletonMap(configArg.getPrefKey(), DoNotDisturb.OFF.name().toLowerCase(Locale.ROOT));
- case 0x01:
- return singletonMap(configArg.getPrefKey(), DoNotDisturb.SCHEDULED.name().toLowerCase(Locale.ROOT));
- case 0x02:
- return singletonMap(configArg.getPrefKey(), DoNotDisturb.AUTOMATIC.name().toLowerCase(Locale.ROOT));
- case 0x03:
- return singletonMap(configArg.getPrefKey(), DoNotDisturb.ALWAYS.name().toLowerCase(Locale.ROOT));
- }
+ decoder = b -> decodeEnum(DND_MODE_MAP, b);
break;
case TEMPERATURE_UNIT:
// TODO: This should be per device...
- //switch(b) {
- // case 0x00:
- // return singletonMap(SettingsActivity.PREF_MEASUREMENT_SYSTEM, METRIC);
- // case 0x01:
- // return singletonMap(SettingsActivity.PREF_MEASUREMENT_SYSTEM, IMPERIAL);
- //}
+ decoder = b -> decodeEnum(TEMPERATURE_UNIT_MAP, b);
break;
case NIGHT_MODE_MODE:
- switch(b) {
- case 0x00:
- return singletonMap(configArg.getPrefKey(), MiBandConst.PREF_NIGHT_MODE_OFF);
- case 0x01:
- return singletonMap(configArg.getPrefKey(), MiBandConst.PREF_NIGHT_MODE_SUNSET);
- case 0x02:
- return singletonMap(configArg.getPrefKey(), MiBandConst.PREF_NIGHT_MODE_SCHEDULED);
- }
+ decoder = b -> decodeString(NIGHT_MODE_MAP, b);
+ break;
+ case WEARING_DIRECTION_BUTTONS:
+ decoder = b -> decodeString(WEARING_DIRECTION_MAP, b);
+ break;
+ case OFFLINE_VOICE_LANGUAGE:
+ decoder = b -> decodeString(OFFLINE_VOICE_LANGUAGE_MAP, b);
+ break;
+ case WORKOUT_GPS_PRESET:
+ decoder = b -> decodeEnum(GPS_PRESET_MAP, b);
+ break;
+ case WORKOUT_GPS_BAND:
+ decoder = b -> decodeEnum(GPS_BAND_MAP, b);
+ break;
+ case WORKOUT_GPS_COMBINATION:
+ decoder = b -> decodeEnum(GPS_COMBINATION_MAP, b);
+ break;
+ case WORKOUT_GPS_SATELLITE_SEARCH:
+ decoder = b -> decodeEnum(GPS_SATELLITE_SEARCH_MAP, b);
+ break;
+ case WORKOUT_DETECTION_SENSITIVITY:
+ decoder = b -> decodeEnum(WORKOUT_DETECTION_SENSITIVITY_MAP, b);
break;
default:
- break;
+ decoder = null;
}
- LOG.warn("Unhandled byte pref {}", configArg);
- return null;
+ if (decoder != null) {
+ prefs = singletonMap(configArg.getPrefKey(), decoder.decode(value.getValue()));
+ if (includesConstraints) {
+ prefs.put(
+ getPrefPossibleValuesKey(configArg.getPrefKey()),
+ String.join(",", decodeByteValues(possibleValues, decoder))
+ );
+ }
+ }
+
+ return prefs;
+ }
+
+ private static List decodeByteValues(final byte[] values, final ValueDecoder decoder) {
+ final List decoded = new ArrayList<>(values.length);
+ for (final byte b : values) {
+ final String decodedByte = decoder.decode(b);
+ if (decodedByte != null) {
+ decoded.add(decodedByte);
+ } else {
+ decoded.add(String.format("0x%x", b));
+ }
+ }
+ decoded.removeAll(Collections.singleton(null));
+ return decoded;
+ }
+
+ private static String decodeStringValues(final List values, final ValueDecoder decoder) {
+ final List decoded = new ArrayList<>(values.size());
+ for (final String str : values) {
+ final String decodedStr = decoder.decode(str);
+ if (decodedStr != null) {
+ decoded.add(decodedStr);
+ } else {
+ decoded.add(str);
+ }
+ }
+ if (decoded.isEmpty()) {
+ return null;
+ }
+ return String.join(",", decoded);
}
private static Map singletonMap(final String key, final Object value) {
- if (key == null && BuildConfig.DEBUG) {
- // Crash
- throw new IllegalStateException("Null key in prefs update");
+ if (key == null) {
+ LOG.error("Null key in prefs update");
+ if (BuildConfig.DEBUG) {
+ // Crash
+ throw new IllegalStateException("Null key in prefs update");
+ }
+ return Collections.emptyMap();
}
- return Collections.singletonMap(key, value);
+ return new HashMap() {{
+ put(key, value);
+ }};
}
}
+
+ private static class ConfigBoolean {
+ private final boolean value;
+
+ public ConfigBoolean(final boolean value) {
+ this.value = value;
+ }
+
+ public boolean getValue() {
+ return value;
+ }
+
+ private static ConfigBoolean consume(final ByteBuffer buf) {
+ return new ConfigBoolean(buf.get() == 1);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ConfigBoolean{value=%s}", value);
+ }
+ }
+
+ private static class ConfigString {
+ private final String value;
+ private final int maxLength;
+
+ public ConfigString(final String value, final int maxLength) {
+ this.value = value;
+ this.maxLength = maxLength;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ private static ConfigString consume(final ByteBuffer buf, final boolean includesConstraints) {
+ final String value = StringUtils.untilNullTerminator(buf);
+ if (value == null) {
+ LOG.error("Null terminator not found in buffer");
+ return null;
+ }
+
+ if (!includesConstraints) {
+ return new ConfigString(value, -1);
+ }
+
+ final int maxLength = buf.get() & 0xff;
+
+ return new ConfigString(value, maxLength);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ConfigString{value=%s}", value);
+ }
+ }
+
+ private static class ConfigStringList {
+ private final String value;
+ private final List possibleValues;
+
+ public ConfigStringList(final String value, final List possibleValues) {
+ this.value = value;
+ this.possibleValues = possibleValues;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public List getPossibleValues() {
+ return possibleValues;
+ }
+
+ private static ConfigStringList consume(final ByteBuffer buf, final boolean includesConstraints) {
+ final String value = StringUtils.untilNullTerminator(buf);
+ if (value == null) {
+ LOG.error("Null terminator not found in buffer");
+ return null;
+ }
+
+ final List possibleValues = new ArrayList<>();
+ if (includesConstraints) {
+ final int unknown1 = buf.get() & 0xff; // ?
+ final int numPossibleValues = buf.get() & 0xff;
+
+ for (int i = 0; i < numPossibleValues; i++) {
+ final String possibleValue = StringUtils.untilNullTerminator(buf);
+ possibleValues.add(possibleValue);
+ }
+ }
+
+ return new ConfigStringList(value, possibleValues);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ConfigStringList{value=%s, possibleValues=%s}", value, possibleValues);
+ }
+ }
+
+ private static class ConfigShort {
+ private final short value;
+ private final short min;
+ private final short max;
+ private final boolean minMaxKnown;
+
+ public ConfigShort(final short value) {
+ this.value = value;
+ this.min = this.max = 0;
+ minMaxKnown = false;
+ }
+
+ public ConfigShort(final short value, final short min, final short max) {
+ this.value = value;
+ this.min = min;
+ this.max = max;
+ this.minMaxKnown = true;
+ }
+
+ public short getValue() {
+ return value;
+ }
+
+ public short getMin() {
+ return min;
+ }
+
+ public short getMax() {
+ return max;
+ }
+
+ public boolean isMinMaxKnown() {
+ return minMaxKnown;
+ }
+
+ private static ConfigShort consume(final ByteBuffer buf, final boolean includesConstraints) {
+ final short value = buf.getShort();
+
+ if (!includesConstraints) {
+ return new ConfigShort(value);
+ }
+
+ final short min = buf.getShort();
+ final short max = buf.getShort();
+
+ return new ConfigShort(value, min, max);
+ }
+
+ @Override
+ public String toString() {
+ if (isMinMaxKnown()) {
+ return String.format(Locale.ROOT, "ConfigShort{value=%d, min=%d, max=%d}", value, min, max);
+ } else {
+ return String.format(Locale.ROOT, "ConfigShort{value=%d}", value);
+ }
+ }
+ }
+
+ private static class ConfigInt {
+ private final int value;
+ private final int min;
+ private final int max;
+ private final boolean minMaxKnown;
+
+ public ConfigInt(final int value) {
+ this.value = value;
+ this.min = this.max = 0;
+ minMaxKnown = false;
+ }
+
+ public ConfigInt(final int value, final int min, final int max) {
+ this.value = value;
+ this.min = min;
+ this.max = max;
+ this.minMaxKnown = true;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public int getMin() {
+ return min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+
+ public boolean isMinMaxKnown() {
+ return minMaxKnown;
+ }
+
+ private static ConfigInt consume(final ByteBuffer buf, final boolean includesConstraints) {
+ final int value = buf.getInt();
+
+ if (!includesConstraints) {
+ return new ConfigInt(value);
+ }
+
+ final int min = buf.getInt();
+ final int max = buf.getInt();
+
+ return new ConfigInt(value, min, max);
+ }
+
+ @Override
+ public String toString() {
+ if (isMinMaxKnown()) {
+ return String.format(Locale.ROOT, "ConfigInt{value=%d, min=%d, max=%d}", value, min, max);
+ } else {
+ return String.format(Locale.ROOT, "ConfigInt{value=%d}", value);
+ }
+ }
+ }
+
+ private static class ConfigByte {
+ private final byte value;
+ private final byte[] possibleValues;
+
+ public ConfigByte(final byte value, final byte[] possibleValues) {
+ this.value = value;
+ this.possibleValues = possibleValues;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ public byte[] getPossibleValues() {
+ return possibleValues;
+ }
+
+ private static ConfigByte consume(final ByteBuffer buf, final boolean includesConstraints) {
+ final byte value = buf.get();
+
+ if (includesConstraints) {
+ final int numPossibleValues = buf.get() & 0xff;
+ final byte[] possibleValues = new byte[numPossibleValues];
+
+ for (int i = 0; i < numPossibleValues; i++) {
+ possibleValues[i] = buf.get();
+ }
+
+ return new ConfigByte(value, possibleValues);
+ }
+
+ return new ConfigByte(value, new byte[0]);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ConfigByte{value=0x%02x, possibleValues=%s}", value, GB.hexdump(possibleValues));
+ }
+ }
+
+ private static class ConfigByteList {
+ private final byte[] values;
+ private final byte[] possibleValues;
+
+ public ConfigByteList(final byte[] values, final byte[] possibleValues) {
+ this.values = values;
+ this.possibleValues = possibleValues;
+ }
+
+ public byte[] getValues() {
+ return values;
+ }
+
+ @Nullable
+ public byte[] getPossibleValues() {
+ return possibleValues;
+ }
+
+ private static ConfigByteList consume(final ByteBuffer buf, final boolean includesConstraints) {
+ final int numValues = buf.get() & 0xff;
+ final byte[] values = new byte[numValues];
+ for (int i = 0; i < numValues; i++) {
+ values[i] = buf.get();
+ }
+
+ if (includesConstraints) {
+ final int numPossibleValues = buf.get() & 0xff;
+ final byte[] possibleValues = new byte[numPossibleValues];
+
+ for (int i = 0; i < numPossibleValues; i++) {
+ possibleValues[i] = buf.get();
+ }
+
+ return new ConfigByteList(values, possibleValues);
+ }
+
+ return new ConfigByteList(values, null);
+ }
+
+ @Override
+ public String toString() {
+ if (possibleValues != null) {
+ return String.format("ConfigByteList{values=%s, possibleValues=%s}", GB.hexdump(values), GB.hexdump(possibleValues));
+ } else {
+ return String.format("ConfigByteList{values=%s}", GB.hexdump(values));
+ }
+ }
+ }
+
+ private static class ConfigDatetimeHhMm {
+ final String value;
+
+ public ConfigDatetimeHhMm(final String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ private static ConfigDatetimeHhMm consume(final ByteBuffer buf) {
+ final DateFormat df = new SimpleDateFormat("HH:mm", Locale.getDefault());
+ final String hhmm = String.format(Locale.ROOT, "%02d:%02d", buf.get(), buf.get());
+ try {
+ df.parse(hhmm);
+ } catch (final ParseException e) {
+ LOG.error("Failed to parse HH:mm from {}", hhmm);
+ return null;
+ }
+ return new ConfigDatetimeHhMm(hhmm);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ConfigDatetimeHhMm{value=%s}", value);
+ }
+ }
+
+ private interface ValueDecoder {
+ String decode(T val);
+ }
+
+ public static String languageByteToLocale(final byte code) {
+ final Map localeLookup = MapUtils.reverse(HuamiLanguageType.idLookup);
+ return localeLookup.get((int) code);
+ }
+
+ public static Byte languageLocaleToByte(final String locale) {
+ if (HuamiLanguageType.idLookup.containsKey(locale)) {
+ return (byte) (int) HuamiLanguageType.idLookup.get(locale);
+ }
+
+ return null;
+ }
+
+ public static String decodeHeartRateAllDayMonitoring(final byte b) {
+ if (b > 0) {
+ return String.format(Locale.ROOT, "%d", (b & 0xff) * 60);
+ } else {
+ return String.format(Locale.ROOT, "%d", b);
+ }
+ }
+
+ public static byte encodeHeartRateAllDayMonitoring(final String val) {
+ final int intVal = Integer.parseInt(val);
+ if (intVal < 0) {
+ return (byte) intVal;
+ } else {
+ return (byte) (intVal / 60);
+ }
+ }
+
+ private static final Map> ALWAYS_ON_DISPLAY_MAP = new HashMap>() {{
+ put((byte) 0x00, AlwaysOnDisplay.OFF);
+ put((byte) 0x01, AlwaysOnDisplay.AUTOMATIC);
+ put((byte) 0x02, AlwaysOnDisplay.SCHEDULED);
+ put((byte) 0x03, AlwaysOnDisplay.ALWAYS);
+ }};
+
+ private static final Map NIGHT_MODE_MAP = new HashMap() {{
+ put((byte) 0x00, MiBandConst.PREF_NIGHT_MODE_OFF);
+ put((byte) 0x01, MiBandConst.PREF_NIGHT_MODE_SUNSET);
+ put((byte) 0x02, MiBandConst.PREF_NIGHT_MODE_SCHEDULED);
+ }};
+
+ private static final Map> DND_MODE_MAP = new HashMap>() {{
+ put((byte) 0x00, DoNotDisturb.OFF);
+ put((byte) 0x01, DoNotDisturb.SCHEDULED);
+ put((byte) 0x02, DoNotDisturb.AUTOMATIC);
+ put((byte) 0x03, DoNotDisturb.ALWAYS);
+ }};
+
+ private static final Map> TEMPERATURE_UNIT_MAP = new HashMap>() {{
+ put((byte) 0x00, MiBandConst.DistanceUnit.METRIC);
+ put((byte) 0x01, MiBandConst.DistanceUnit.IMPERIAL);
+ }};
+
+ private static final Map TIME_FORMAT_MAP = new HashMap() {{
+ put((byte) 0x00, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H);
+ put((byte) 0x01, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H);
+ }};
+
+ private static final Map> LIFT_WRIST_MAP = new HashMap>() {{
+ put((byte) 0x00, ActivateDisplayOnLift.OFF);
+ put((byte) 0x01, ActivateDisplayOnLift.SCHEDULED);
+ put((byte) 0x02, ActivateDisplayOnLift.ON);
+ }};
+
+ private static final Map> LIFT_WRIST_SENSITIVITY_MAP = new HashMap>() {{
+ put((byte) 0x00, ActivateDisplayOnLiftSensitivity.NORMAL);
+ put((byte) 0x01, ActivateDisplayOnLiftSensitivity.SENSITIVE);
+ }};
+
+ private static final Map WEARING_DIRECTION_MAP = new HashMap() {{
+ put((byte) 0x00, "buttons_on_left");
+ put((byte) 0x01, "buttons_on_right");
+ }};
+
+ private static final Map OFFLINE_VOICE_LANGUAGE_MAP = new HashMap() {{
+ put((byte) 0x01, "zh_CN"); // TODO confirm
+ put((byte) 0x02, "en_US");
+ put((byte) 0x03, "de_DE");
+ put((byte) 0x04, "es_ES");
+ }};
+
+ private static final Map> GPS_PRESET_MAP = new HashMap>() {{
+ put((byte) 0x00, GpsCapability.Preset.ACCURACY);
+ put((byte) 0x01, GpsCapability.Preset.BALANCED);
+ put((byte) 0x02, GpsCapability.Preset.POWER_SAVING);
+ put((byte) 0x04, GpsCapability.Preset.CUSTOM);
+ }};
+
+ private static final Map> GPS_BAND_MAP = new HashMap>() {{
+ put((byte) 0x00, GpsCapability.Band.SINGLE_BAND);
+ put((byte) 0x01, GpsCapability.Band.DUAL_BAND);
+ }};
+
+ private static final Map> GPS_COMBINATION_MAP = new HashMap>() {{
+ put((byte) 0x00, GpsCapability.Combination.LOW_POWER_GPS);
+ put((byte) 0x01, GpsCapability.Combination.GPS);
+ put((byte) 0x02, GpsCapability.Combination.GPS_BDS);
+ put((byte) 0x03, GpsCapability.Combination.GPS_GNOLASS);
+ put((byte) 0x04, GpsCapability.Combination.GPS_GALILEO);
+ put((byte) 0x05, GpsCapability.Combination.ALL_SATELLITES);
+ }};
+
+ private static final Map> GPS_SATELLITE_SEARCH_MAP = new HashMap>() {{
+ put((byte) 0x00, GpsCapability.SatelliteSearch.SPEED_FIRST);
+ put((byte) 0x01, GpsCapability.SatelliteSearch.ACCURACY_FIRST);
+ }};
+
+ private static final Map> WORKOUT_DETECTION_CATEGORY_MAP = new HashMap>() {{
+ put((byte) 0x03, WorkoutDetectionCapability.Category.WALKING);
+ put((byte) 0x28, WorkoutDetectionCapability.Category.INDOOR_WALKING);
+ put((byte) 0x01, WorkoutDetectionCapability.Category.OUTDOOR_RUNNING);
+ put((byte) 0x02, WorkoutDetectionCapability.Category.TREADMILL);
+ put((byte) 0x04, WorkoutDetectionCapability.Category.OUTDOOR_CYCLING);
+ put((byte) 0x06, WorkoutDetectionCapability.Category.POOL_SWIMMING);
+ put((byte) 0x09, WorkoutDetectionCapability.Category.ELLIPTICAL);
+ put((byte) 0x17, WorkoutDetectionCapability.Category.ROWING_MACHINE);
+ }};
+
+ private static final Map> WORKOUT_DETECTION_SENSITIVITY_MAP = new HashMap>() {{
+ put((byte) 0x00, WorkoutDetectionCapability.Sensitivity.HIGH);
+ put((byte) 0x01, WorkoutDetectionCapability.Sensitivity.STANDARD);
+ put((byte) 0x02, WorkoutDetectionCapability.Sensitivity.LOW);
+ }};
+
+ public static String decodeEnum(final Map> map, final byte b) {
+ if (map.containsKey(b)) {
+ return map.get(b).name().toLowerCase(Locale.ROOT);
+ }
+
+ return null;
+ }
+
+ public static String decodeString(final Map map, final byte b) {
+ return map.get(b);
+ }
+
+ public static Byte encodeEnum(final Map> map, final String val) {
+ final Map, Byte> reverse = MapUtils.reverse(map);
+ for (final Enum> anEnum : reverse.keySet()) {
+ if (anEnum.name().toLowerCase(Locale.ROOT).equals(val)) {
+ return reverse.get(anEnum);
+ }
+ }
+
+ return null;
+ }
+
+ public static Byte encodeString(final Map map, final String val) {
+ final Map reverse = MapUtils.reverse(map);
+ for (final String aString : reverse.keySet()) {
+ if (aString.equals(val)) {
+ return reverse.get(aString);
+ }
+ }
+
+ return null;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021MenuType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021MenuType.java
index 47a60c0b8..8f1dc2908 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021MenuType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021MenuType.java
@@ -19,57 +19,91 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
import java.util.HashMap;
import java.util.Map;
-import nodomain.freeyourgadget.gadgetbridge.R;
-
public class Huami2021MenuType {
/**
* These somewhat match the ones in {@link HuamiMenuType}, but not all. The band sends and
* receives those as 8-digit upper case hex strings.
*/
- public static final Map displayItemNameLookup = new HashMap() {{
- put("00000001", R.string.menuitem_personal_activity_intelligence);
- put("00000002", R.string.menuitem_hr);
- put("00000003", R.string.menuitem_workout);
- put("00000004", R.string.menuitem_weather);
- put("00000009", R.string.menuitem_alarm);
- put("0000001A", R.string.menuitem_worldclock);
- put("0000000B", R.string.menuitem_music);
- put("0000000C", R.string.menuitem_stopwatch);
- put("0000000D", R.string.menuitem_countdown);
- put("0000000E", R.string.menuitem_findphone);
- put("0000000F", R.string.menuitem_mutephone);
- put("00000013", R.string.menuitem_settings);
- put("00000014", R.string.menuitem_workout_history);
- put("00000015", R.string.menuitem_eventreminder);
- put("00000019", R.string.menuitem_pai);
- put("0000000A", R.string.menuitem_takephoto);
- put("0000001C", R.string.menuitem_stress);
- put("0000001D", R.string.menuitem_female_health);
- put("0000001E", R.string.menuitem_workout_status);
- put("00000023", R.string.menuitem_sleep);
- put("00000024", R.string.menuitem_spo2);
- put("00000026", R.string.menuitem_events);
- put("00000033", R.string.menuitem_breathing);
- put("00000038", R.string.menuitem_pomodoro);
- put("00000102", R.string.menuitem_flashlight);
+ public static final Map displayItemNameLookup = new HashMap() {{
+ put("00000001", "personal_activity_intelligence");
+ put("00000002", "hr");
+ put("00000003", "workout");
+ put("00000004", "weather");
+ put("00000009", "alarm");
+ put("0000000A", "takephoto");
+ put("0000000B", "music");
+ put("0000000C", "stopwatch");
+ put("0000000D", "countdown");
+ put("0000000E", "findphone");
+ put("0000000F", "mutephone");
+ put("00000013", "settings");
+ put("00000014", "workout_history");
+ put("00000015", "eventreminder");
+ put("00000016", "compass");
+ put("00000019", "pai");
+ put("0000001A", "worldclock");
+ put("0000001C", "stress");
+ put("0000001D", "female_health");
+ put("0000001E", "workout_status");
+ put("00000020", "calendar");
+ put("00000023", "sleep");
+ put("00000024", "spo2");
+ put("00000025", "phone");
+ put("00000026", "events");
+ put("00000033", "breathing");
+ put("00000038", "pomodoro");
+ put("0000003E", "todo");
+ put("00000041", "barometer");
+ put("00000042", "voice_memos");
+ put("00000044", "sun_moon");
+ put("00000045", "one_tap_measuring");
+ put("00000047", "membership_cards");
+ put("00000100", "alexa");
+ put("00000101", "offline_voice");
+ put("00000102", "flashlight");
}};
- public static final Map shortcutsNameLookup = new HashMap() {{
- put("00000001", R.string.menuitem_hr);
- put("0000000A", R.string.menuitem_workout);
- put("0000000C", R.string.menuitem_workout_status);
- put("00000002", R.string.menuitem_weather);
- put("0000001A", R.string.menuitem_worldclock);
- put("00000016", R.string.menuitem_alarm);
- put("00000004", R.string.menuitem_music);
- put("00000020", R.string.menuitem_activity);
- put("00000021", R.string.menuitem_eventreminder);
- put("00000011", R.string.menuitem_female_health);
- put("00000003", R.string.menuitem_pai);
- put("0000000F", R.string.menuitem_stress);
- put("00000005", R.string.menuitem_sleep);
- put("00000013", R.string.menuitem_spo2);
- put("00000018", R.string.menuitem_events);
- put("00000012", R.string.menuitem_breathing);
+ public static final Map shortcutsNameLookup = new HashMap() {{
+ put("00000001", "hr");
+ put("00000002", "weather");
+ put("00000003", "pai");
+ put("00000004", "music");
+ put("00000005", "sleep");
+ put("0000000A", "workout");
+ put("0000000B", "workout_history");
+ put("0000000C", "workout_status");
+ put("0000000E", "one_tap_measuring");
+ put("0000000F", "stress");
+ put("00000011", "female_health");
+ put("00000012", "breathing");
+ put("00000013", "spo2");
+ put("00000016", "alarm");
+ put("00000017", "calendar");
+ put("00000018", "events");
+ put("00000019", "todo");
+ put("0000001A", "worldclock");
+ put("0000001B", "compass");
+ put("0000001C", "barometer");
+ put("0000001E", "voice_memos");
+ put("00000020", "activity");
+ put("00000021", "eventreminder");
+ }};
+
+ public static final Map controlCenterNameLookup = new HashMap() {{
+ put("00000007", "battery");
+ put("00000003", "dnd");
+ put("00000004", "sleep");
+ put("00000008", "theater_mode");
+ put("0000000D", "calendar");
+ put("00000006", "volume");
+ put("00000009", "screen_always_lit");
+ put("00000001", "brightness");
+ put("00000013", "settings");
+ put("00000000", "flashlight");
+ put("0000000A", "bluetooth");
+ put("0000000B", "wifi");
+ put("00000002", "lockscreen");
+ put("00000005", "findphone");
+ put("00000019", "eject_water");
}};
}
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 6e1578781..df5bcd3b7 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
@@ -27,51 +27,23 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_
import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint16;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint8;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.mapTimeZone;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_MODE;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_END;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.ALWAYS_ON_DISPLAY_SCHEDULED_START;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.BLUETOOTH_CONNECTED_ADVERTISING;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.DATE_FORMAT;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.DISPLAY_CALLER;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.DND_MODE;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.DND_SCHEDULED_END;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.DND_SCHEDULED_START;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_NOTIFICATION;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_CALORIES;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_FAT_BURN_TIME;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_SLEEP;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_STANDING_TIME;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_STEPS;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.FITNESS_GOAL_WEIGHT;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.HEART_RATE_ALL_DAY_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.HEART_RATE_HIGH_ALERTS;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.HEART_RATE_LOW_ALERTS;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_ENABLED;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_END;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_DND_SCHEDULED_START;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_ENABLED;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_END;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.INACTIVITY_WARNINGS_SCHEDULED_START;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LANGUAGE;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LANGUAGE_FOLLOW_PHONE;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LIFT_WRIST_MODE;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LIFT_WRIST_RESPONSE_SENSITIVITY;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LIFT_WRIST_SCHEDULED_END;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.LIFT_WRIST_SCHEDULED_START;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.NIGHT_MODE_MODE;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.NIGHT_MODE_SCHEDULED_END;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.NIGHT_MODE_SCHEDULED_START;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.PASSWORD_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.PASSWORD_TEXT;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SCREEN_BRIGHTNESS;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SCREEN_ON_ON_NOTIFICATIONS;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SCREEN_TIMEOUT;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SLEEP_BREATHING_QUALITY_MONITORING;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SPO2_ALL_DAY_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.SPO2_LOW_ALERT;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.STRESS_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.STRESS_RELAXATION_REMINDER;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.TEMPERATURE_UNIT;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.THIRD_PARTY_HR_SHARING;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigArg.TIME_FORMAT;
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigGroup;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigSetter;
-import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Config.ConfigType;
import android.Manifest;
import android.content.Intent;
@@ -80,6 +52,7 @@ import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.net.Uri;
+import android.os.Handler;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -107,6 +80,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
@@ -116,15 +90,11 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicContr
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
-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.huami.Huami2021Service;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
-import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
-import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
@@ -158,6 +128,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -190,11 +161,33 @@ public abstract class Huami2021Support extends HuamiSupport {
return (byte) 0x80;
}
+ @Override
+ public void onSendConfiguration(final String config) {
+ final ConfigSetter configSetter = new ConfigSetter();
+ final Prefs prefs = getDevicePrefs();
+
+ try {
+ if (Huami2021Config.setConfig(prefs, config, configSetter)) {
+ // If the ConfigSetter was able to set the config, just write it and return
+ final TransactionBuilder builder;
+ builder = performInitialized("Sending configuration for option: " + config);
+ configSetter.write(this, builder);
+ builder.queue(getQueue());
+
+ return;
+ }
+
+ super.onSendConfiguration(config);
+ } catch (final Exception e) {
+ GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
+ }
+ }
+
@Override
public void onTestNewFunction() {
try {
final TransactionBuilder builder = performInitialized("test");
-
+ //requestMTU(247);
builder.queue(getQueue());
} catch (final Exception e) {
LOG.error("Failed to test new function", e);
@@ -210,17 +203,31 @@ public abstract class Huami2021Support extends HuamiSupport {
writeToChunked2021("ack find phone", CHUNKED2021_ENDPOINT_FIND_DEVICE, cmd, true);
}
+ protected void stopFindPhone() {
+ LOG.info("Stopping find phone");
+
+ writeToChunked2021("found phone", CHUNKED2021_ENDPOINT_FIND_DEVICE, FIND_PHONE_STOP_FROM_PHONE, true);
+ }
+
+ @Override
+ public void onFindDevice(final boolean start) {
+ if (getCoordinator().supportsContinuousFindDevice()) {
+ sendFindDeviceCommand(start);
+ } else {
+ // Vibrate band periodically
+ super.onFindDevice(start);
+ }
+ }
+
@Override
protected void sendFindDeviceCommand(boolean start) {
- if (!start) {
- return;
- }
+ final byte findBandCommand = start ? FIND_BAND_START : FIND_BAND_STOP_FROM_PHONE;
- LOG.info("Sending one-shot find band");
+ LOG.info("Sending find band {}", start);
try {
final TransactionBuilder builder = performInitialized("find huami 2021");
- writeToChunked2021(builder, CHUNKED2021_ENDPOINT_FIND_DEVICE, new byte[]{FIND_BAND_ONESHOT}, true);
+ writeToChunked2021(builder, CHUNKED2021_ENDPOINT_FIND_DEVICE, findBandCommand, true);
builder.queue(getQueue());
} catch (IOException e) {
LOG.error("error while sending find Huami 2021 device command", e);
@@ -228,12 +235,14 @@ public abstract class Huami2021Support extends HuamiSupport {
}
@Override
- public void onPhoneFound() {
- LOG.info("Sending phone found");
+ public void onFindPhone(final boolean start) {
+ LOG.info("Find phone: {}", start);
- final byte[] cmd = new byte[]{FIND_PHONE_STOP_FROM_PHONE};
+ findPhoneStarted = start;
- writeToChunked2021("found phone", CHUNKED2021_ENDPOINT_FIND_DEVICE, cmd, true);
+ if (!start) {
+ stopFindPhone();
+ }
}
@Override
@@ -309,14 +318,17 @@ public abstract class Huami2021Support extends HuamiSupport {
buf.putInt(calendarEventSpec.timestamp);
buf.putInt(calendarEventSpec.timestamp + calendarEventSpec.durationInSeconds);
+ // Remind
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
+ // Repeat
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
+ // ?
buf.put((byte) 0xff); // ?
buf.put((byte) 0xff); // ?
buf.put((byte) 0xff); // ?
@@ -328,6 +340,7 @@ public abstract class Huami2021Support extends HuamiSupport {
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
+ // TODO: Description here
writeToChunked2021("delete calendar event", CHUNKED2021_ENDPOINT_CALENDAR, buf.array(), false);
}
@@ -414,18 +427,28 @@ public abstract class Huami2021Support extends HuamiSupport {
protected Huami2021Support requestBatteryInfo(TransactionBuilder builder) {
LOG.debug("Requesting Battery Info");
- writeToChunked2021(builder, CHUNKED2021_ENDPOINT_BATTERY, new byte[]{BATTERY_REQUEST}, false);
+ writeToChunked2021(builder, CHUNKED2021_ENDPOINT_BATTERY, BATTERY_REQUEST, false);
return this;
}
@Override
protected Huami2021Support setFitnessGoal(final TransactionBuilder builder) {
- final int fitnessGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
- LOG.info("Setting Fitness Goal to {}", fitnessGoal);
+ final int goalSteps = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
+ final int goalCalories = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_CALORIES_BURNT, ActivityUser.defaultUserCaloriesBurntGoal);
+ final int goalSleep = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_SLEEP_DURATION, ActivityUser.defaultUserSleepDurationGoal);
+ final int goalWeight = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_WEIGHT_KG, ActivityUser.defaultUserGoalWeightKg);
+ final int goalStandingTime = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS, ActivityUser.defaultUserGoalStandingTimeHours);
+ final int goalFatBurnTime = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES, ActivityUser.defaultUserFatBurnTimeMinutes);
+ LOG.info("Setting Fitness Goals to steps={}, calories={}, sleep={}, weight={}, standingTime={}, fatBurn={}", goalSteps, goalCalories, goalSleep, goalWeight, goalStandingTime, goalFatBurnTime);
- new ConfigSetter(ConfigType.HEALTH)
- .setInt(FITNESS_GOAL_STEPS, fitnessGoal)
+ new ConfigSetter()
+ .setInt(FITNESS_GOAL_STEPS, goalSteps)
+ .setShort(FITNESS_GOAL_CALORIES, (short) goalCalories)
+ .setShort(FITNESS_GOAL_SLEEP, (short) (goalSleep * 60))
+ .setShort(FITNESS_GOAL_WEIGHT, (short) goalWeight)
+ .setShort(FITNESS_GOAL_STANDING_TIME, (short) (goalStandingTime))
+ .setShort(FITNESS_GOAL_FAT_BURN_TIME, (short) goalFatBurnTime)
.write(this, builder);
return this;
@@ -483,13 +506,6 @@ public abstract class Huami2021Support extends HuamiSupport {
return this;
}
- @Override
- protected Huami2021Support setWearLocation(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("Function not implemented");
- return this;
- }
-
@Override
protected Huami2021Support setPassword(final TransactionBuilder builder) {
final boolean passwordEnabled = HuamiCoordinator.getPasswordEnabled(gbDevice.getAddress());
@@ -502,7 +518,7 @@ public abstract class Huami2021Support extends HuamiSupport {
return this;
}
- new ConfigSetter(ConfigType.LOCKSCREEN)
+ new ConfigSetter()
.setBoolean(PASSWORD_ENABLED, passwordEnabled)
.setString(PASSWORD_TEXT, password)
.write(this, builder);
@@ -674,7 +690,7 @@ public abstract class Huami2021Support extends HuamiSupport {
protected Huami2021Support requestReminders(final TransactionBuilder builder) {
LOG.info("Requesting reminders");
- writeToChunked2021(builder, CHUNKED2021_ENDPOINT_REMINDERS, new byte[]{REMINDERS_CMD_REQUEST}, false);
+ writeToChunked2021(builder, CHUNKED2021_ENDPOINT_REMINDERS, REMINDERS_CMD_REQUEST, false);
return this;
}
@@ -682,8 +698,9 @@ public abstract class Huami2021Support extends HuamiSupport {
@Override
protected void sendReminderToDevice(final TransactionBuilder builder, int position, final Reminder reminder) {
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
- if (position + 1 > coordinator.getReminderSlotCount()) {
- LOG.error("Reminder for position {} is over the limit of {} reminders", position, coordinator.getReminderSlotCount());
+ final int reminderSlotCount = coordinator.getReminderSlotCount(getDevice());
+ if (position + 1 > reminderSlotCount) {
+ LOG.error("Reminder for position {} is over the limit of {} reminders", position, reminderSlotCount);
return;
}
@@ -752,7 +769,7 @@ public abstract class Huami2021Support extends HuamiSupport {
@Override
protected void sendWorldClocks(final TransactionBuilder builder,
final List extends WorldClock> clocks) {
- // TODO not yet supported by the official app, but menu option shows up on the band
+ // TODO not yet implemented
}
@Override
@@ -869,7 +886,7 @@ public abstract class Huami2021Support extends HuamiSupport {
protected Huami2021Support setHeartrateSleepSupport(final TransactionBuilder builder) {
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
- new ConfigSetter(ConfigType.HEALTH)
+ new ConfigSetter()
.setBoolean(SLEEP_HIGH_ACCURACY_MONITORING, enableHrSleepSupport)
.write(this, builder);
@@ -926,91 +943,9 @@ public abstract class Huami2021Support extends HuamiSupport {
return this;
}
- @Override
- protected Huami2021Support setHeartrateActivityMonitoring(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("setHeartrateActivityMonitoring not implemented");
- return null;
- }
-
- @Override
- protected Huami2021Support setHeartrateAlert(final TransactionBuilder builder) {
- final int hrAlertThresholdHigh = HuamiCoordinator.getHeartrateAlertHighThreshold(gbDevice.getAddress());
- final int hrAlertThresholdLow = HuamiCoordinator.getHeartrateAlertLowThreshold(gbDevice.getAddress());
-
- LOG.info("Setting heart rate alert thresholds to {}, {}", hrAlertThresholdHigh, hrAlertThresholdLow);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setByte(HEART_RATE_HIGH_ALERTS, (byte) hrAlertThresholdHigh)
- .setByte(HEART_RATE_LOW_ALERTS, (byte) hrAlertThresholdLow)
- .write(this, builder);
-
- return null;
- }
-
- @Override
- protected HuamiSupport setHeartrateSleepBreathingQualityMonitoring(TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getHeartrateSleepBreathingQualityMonitoring(gbDevice.getAddress());
- LOG.info("Setting stress relaxation reminder to {}", enable);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(SLEEP_BREATHING_QUALITY_MONITORING, enable)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected HuamiSupport setSPO2AllDayMonitoring(TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getSPO2AllDayMonitoring(gbDevice.getAddress());
- LOG.info("Setting SPO2 All-day monitoring to {}", enable);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(SPO2_ALL_DAY_MONITORING, enable)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected HuamiSupport setSPO2AlertThreshold(TransactionBuilder builder) {
- final int spo2threshold = HuamiCoordinator.getSPO2AlertThreshold(gbDevice.getAddress());
- LOG.info("Setting SPO2 alert threshold to {}", spo2threshold);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setByte(SPO2_LOW_ALERT, (byte) spo2threshold)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setHeartrateStressMonitoring(final TransactionBuilder builder) {
- final boolean enableHrStressMonitoring = HuamiCoordinator.getHeartrateStressMonitoring(gbDevice.getAddress());
- LOG.info("Setting heart rate stress monitoring to {}", enableHrStressMonitoring);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(STRESS_MONITORING, enableHrStressMonitoring)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected HuamiSupport setHeartrateStressRelaxationReminder(TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getHeartrateStressRelaxationReminder(gbDevice.getAddress());
- LOG.info("Setting stress relaxation reminder to {}", enable);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(STRESS_RELAXATION_REMINDER, enable)
- .write(this, builder);
-
- return this;
- }
-
@Override
protected HuamiSupport setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
- new ConfigSetter(ConfigType.HEALTH)
+ new ConfigSetter()
.setByte(HEART_RATE_ALL_DAY_MONITORING, (byte) minutes)
.write(this, builder);
@@ -1018,10 +953,8 @@ public abstract class Huami2021Support extends HuamiSupport {
}
@Override
- public Huami2021Support sendFactoryReset(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("sendFactoryReset not implemented");
- return null;
+ protected boolean supportsDeviceDefaultVibrationProfiles() {
+ return true;
}
@Override
@@ -1039,7 +972,7 @@ public abstract class Huami2021Support extends HuamiSupport {
buf.put(VIBRATION_PATTERN_SET);
buf.put(notificationType.getCode());
- buf.put((byte) 0x01);
+ buf.put((byte) (profile != null ? 1 : 0)); // 1 for custom, 0 for device default
buf.put((byte) (test ? 1 : 0));
buf.put((byte) (onOff.size() / 2));
@@ -1079,49 +1012,9 @@ public abstract class Huami2021Support extends HuamiSupport {
}
}
- @Override
- protected Huami2021Support setDateDisplay(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("Request GPS version not implemented");
- return this;
- }
-
- @Override
- protected Huami2021Support setDateFormat(final TransactionBuilder builder) {
- final String dateFormat = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("dateformat", "MM/dd/yyyy");
- if (dateFormat == null) {
- return this;
- }
-
- switch (dateFormat) {
- case "YYYY/MM/DD":
- case "yyyy/mm/dd":
- case "YYYY.MM.DD":
- case "yyyy.mm.dd":
- case "MM/DD/YYYY":
- case "MM.DD.YYYY":
- case "mm/dd/yyyy":
- case "mm.dd.yyyy":
- case "DD/MM/YYYY":
- case "DD.MM.YYYY":
- case "dd/mm/yyyy":
- case "dd.mm.yyyy":
- break;
- default:
- LOG.warn("unsupported date format " + dateFormat);
- return this;
- }
-
- new ConfigSetter(ConfigType.SYSTEM)
- .setString(DATE_FORMAT, dateFormat.replace("/", ".").toLowerCase(Locale.ROOT))
- .write(this, builder);
-
- return this;
- }
-
@Override
protected Huami2021Support setTimeFormat(final TransactionBuilder builder) {
- final GBPrefs gbPrefs = new GBPrefs(new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())));
+ final GBPrefs gbPrefs = new GBPrefs(getDevicePrefs());
final String timeFormat = gbPrefs.getTimeFormat();
// FIXME: This "works", but the band does not update when the setting changes, so we don't do anything
@@ -1139,144 +1032,79 @@ public abstract class Huami2021Support extends HuamiSupport {
timeFormatByte = 0x00;
}
- new ConfigSetter(ConfigType.SYSTEM)
+ new ConfigSetter()
.setByte(TIME_FORMAT, timeFormatByte)
.write(this, builder);
return this;
}
- @Override
- protected Huami2021Support setGoalNotification(final TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getGoalNotification(gbDevice.getAddress());
- LOG.info("Setting goal notification to {}", enable);
-
- // TODO confirm this works
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(FITNESS_GOAL_NOTIFICATION, enable)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setAlwaysOnDisplay(final TransactionBuilder builder) {
- final AlwaysOnDisplay alwaysOnDisplay = HuamiCoordinator.getAlwaysOnDisplay(gbDevice.getAddress());
- LOG.info("Setting always on display mode {}", alwaysOnDisplay);
-
- final byte aodByte;
- switch (alwaysOnDisplay) {
- case AUTOMATIC:
- aodByte = 0x01;
- break;
- case SCHEDULED:
- aodByte = 0x02;
- break;
- case ALWAYS:
- aodByte = 0x03;
- break;
- case OFF:
- default:
- aodByte = 0x00;
- break;
- }
-
- final Date start = HuamiCoordinator.getAlwaysOnDisplayStart(gbDevice.getAddress());
- final Date end = HuamiCoordinator.getAlwaysOnDisplayEnd(gbDevice.getAddress());
-
- new ConfigSetter(ConfigType.DISPLAY)
- .setByte(ALWAYS_ON_DISPLAY_MODE, aodByte)
- .setHourMinute(ALWAYS_ON_DISPLAY_SCHEDULED_START, start)
- .setHourMinute(ALWAYS_ON_DISPLAY_SCHEDULED_END, end)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setActivateDisplayOnLiftWrist(final TransactionBuilder builder) {
- final ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext(), gbDevice.getAddress());
- LOG.info("Setting activate display on lift wrist to {}", displayOnLift);
-
- final byte liftWristByte;
- switch (displayOnLift) {
- case SCHEDULED:
- liftWristByte = 0x01;
- break;
- case ON:
- liftWristByte = 0x02;
- break;
- case OFF:
- default:
- liftWristByte = 0x00;
- break;
- }
-
- final Date start = HuamiCoordinator.getDisplayOnLiftStart(gbDevice.getAddress());
- final Date end = HuamiCoordinator.getDisplayOnLiftEnd(gbDevice.getAddress());
-
- new ConfigSetter(ConfigType.DISPLAY)
- .setByte(LIFT_WRIST_MODE, liftWristByte)
- .setHourMinute(LIFT_WRIST_SCHEDULED_START, start)
- .setHourMinute(LIFT_WRIST_SCHEDULED_END, end)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setActivateDisplayOnLiftWristSensitivity(final TransactionBuilder builder) {
- final ActivateDisplayOnLiftSensitivity sensitivity = HuamiCoordinator.getDisplayOnLiftSensitivity(gbDevice.getAddress());
- LOG.info("Setting activate display on lift wrist sensitivity to {}", sensitivity);
-
- final byte sensitivityByte;
- switch (sensitivity) {
- case SENSITIVE:
- sensitivityByte = 0x01;
- break;
- case NORMAL:
- default:
- sensitivityByte = 0x00;
- break;
- }
-
- new ConfigSetter(ConfigType.DISPLAY)
- .setByte(LIFT_WRIST_RESPONSE_SENSITIVITY, sensitivityByte)
- .write(this, builder);
-
- return this;
- }
-
@Override
protected Huami2021Support setDisplayItems(final TransactionBuilder builder) {
- setDisplayItems2021(builder, false);
+ final Prefs prefs = getDevicePrefs();
+
+ setDisplayItems2021(
+ builder,
+ DISPLAY_ITEMS_MENU,
+ new ArrayList<>(prefs.getList(Huami2021Config.getPrefPossibleValuesKey(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE), Collections.emptyList())),
+ new ArrayList<>(prefs.getList(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, Collections.emptyList()))
+ );
return this;
}
@Override
protected Huami2021Support setShortcuts(final TransactionBuilder builder) {
- setDisplayItems2021(builder, true);
+ final Prefs prefs = getDevicePrefs();
+
+ setDisplayItems2021(
+ builder,
+ DISPLAY_ITEMS_SHORTCUTS,
+ new ArrayList<>(prefs.getList(Huami2021Config.getPrefPossibleValuesKey(HuamiConst.PREF_SHORTCUTS_SORTABLE), Collections.emptyList())),
+ new ArrayList<>(prefs.getList(HuamiConst.PREF_SHORTCUTS_SORTABLE, Collections.emptyList()))
+ );
+ return this;
+ }
+
+ protected Huami2021Support setControlCenter(final TransactionBuilder builder) {
+ final Prefs prefs = getDevicePrefs();
+
+ setDisplayItems2021(
+ builder,
+ DISPLAY_ITEMS_CONTROL_CENTER,
+ new ArrayList<>(prefs.getList(Huami2021Config.getPrefPossibleValuesKey(HuamiConst.PREF_CONTROL_CENTER_SORTABLE), Collections.emptyList())),
+ new ArrayList<>(prefs.getList(HuamiConst.PREF_CONTROL_CENTER_SORTABLE, Collections.emptyList()))
+ );
return this;
}
private void setDisplayItems2021(final TransactionBuilder builder,
- final boolean isShortcuts) {
- final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
- final List allSettings;
- List enabledList;
- final byte menuType;
+ final byte menuType,
+ final List allSettings,
+ List enabledList) {
+ final boolean isMainMenu = menuType == DISPLAY_ITEMS_MENU;
+ final boolean isShortcuts = menuType == DISPLAY_ITEMS_SHORTCUTS;
+ final boolean hasMoreSection;
+ final Map idMap;
- if (isShortcuts) {
- menuType = Huami2021Service.DISPLAY_ITEMS_SHORTCUTS;
- allSettings = new ArrayList<>(prefs.getList(HuamiConst.PREF_ALL_SHORTCUTS, Collections.emptyList()));
- enabledList = new ArrayList<>(prefs.getList(HuamiConst.PREF_SHORTCUTS_SORTABLE, Collections.emptyList()));
- LOG.info("Setting shortcuts");
- } else {
- menuType = Huami2021Service.DISPLAY_ITEMS_MENU;
- allSettings = new ArrayList<>(prefs.getList(HuamiConst.PREF_ALL_DISPLAY_ITEMS, Collections.emptyList()));
- enabledList = new ArrayList<>(prefs.getList(HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE, Collections.emptyList()));
- LOG.info("Setting menu items");
+ switch (menuType) {
+ case DISPLAY_ITEMS_MENU:
+ LOG.info("Setting menu items");
+ hasMoreSection = getCoordinator().mainMenuHasMoreSection();
+ idMap = MapUtils.reverse(Huami2021MenuType.displayItemNameLookup);
+ break;
+ case DISPLAY_ITEMS_SHORTCUTS:
+ LOG.info("Setting shortcuts");
+ hasMoreSection = false;
+ idMap = MapUtils.reverse(Huami2021MenuType.shortcutsNameLookup);
+ break;
+ case DISPLAY_ITEMS_CONTROL_CENTER:
+ LOG.info("Setting control center");
+ hasMoreSection = false;
+ idMap = MapUtils.reverse(Huami2021MenuType.controlCenterNameLookup);
+ break;
+ default:
+ LOG.warn("Unknown menu type {}", menuType);
+ return;
}
if (allSettings.isEmpty()) {
@@ -1284,9 +1112,9 @@ public abstract class Huami2021Support extends HuamiSupport {
return;
}
- if (!isShortcuts && !enabledList.contains("00000013")) {
+ if (isMainMenu && !enabledList.contains("settings")) {
// Settings can't be disabled
- enabledList.add("00000013");
+ enabledList.add("settings");
}
if (isShortcuts && enabledList.size() > 10) {
@@ -1298,7 +1126,7 @@ public abstract class Huami2021Support extends HuamiSupport {
LOG.info("Setting display items (shortcuts={}): {}", isShortcuts, enabledList);
int numItems = allSettings.size();
- if (!isShortcuts) {
+ if (hasMoreSection) {
// Exclude the "more" item from the main menu, since it's not a real item
numItems--;
}
@@ -1314,13 +1142,22 @@ public abstract class Huami2021Support extends HuamiSupport {
byte pos = 0;
boolean inMoreSection = false;
- for (final String id : enabledList) {
- if (id.equals("more")) {
+ // IDs are 8-char hex strings, in upper case
+ final Pattern ID_REGEX = Pattern.compile("^[0-9A-F]{8}$");
+
+ for (final String name : enabledList) {
+ if (name.equals("more")) {
inMoreSection = true;
pos = 0;
continue;
}
+ final String id = idMap.containsKey(name) ? idMap.get(name) : name;
+ if (!ID_REGEX.matcher(id).find()) {
+ LOG.error("Screen item id '{}' is not 8-char hex string", id);
+ continue;
+ }
+
final byte sectionKey;
if (inMoreSection) {
// In more section
@@ -1340,8 +1177,14 @@ public abstract class Huami2021Support extends HuamiSupport {
// Set all disabled items
pos = 0;
- for (final String id : allSettings) {
- if (enabledList.contains(id) || id.equals("more")) {
+ for (final String name : allSettings) {
+ if (enabledList.contains(name) || name.equals("more")) {
+ continue;
+ }
+
+ final String id = idMap.containsKey(name) ? idMap.get(name) : name;
+ if (!ID_REGEX.matcher(id).find()) {
+ LOG.error("Screen item id '{}' is not 8-char hex string", id);
continue;
}
@@ -1356,134 +1199,6 @@ public abstract class Huami2021Support extends HuamiSupport {
writeToChunked2021(builder, CHUNKED2021_ENDPOINT_DISPLAY_ITEMS, buf.array(), true);
}
- @Override
- protected Huami2021Support setWorkoutActivityTypes(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("Function not implemented");
- return this;
- }
-
- @Override
- protected Huami2021Support setBeepSounds(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("Function not implemented");
- return this;
- }
-
- @Override
- protected Huami2021Support setRotateWristToSwitchInfo(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("Function not implemented");
- return this;
- }
-
- @Override
- protected Huami2021Support setDisplayCaller(final TransactionBuilder builder) {
- // TODO: Make this configurable
-
- LOG.info("Enabling caller display");
-
- new ConfigSetter(ConfigType.SYSTEM)
- .setBoolean(DISPLAY_CALLER, true)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setDoNotDisturb(final TransactionBuilder builder) {
- final DoNotDisturb doNotDisturb = HuamiCoordinator.getDoNotDisturb(gbDevice.getAddress());
- LOG.info("Setting do not disturb to {}", doNotDisturb);
-
- final byte dndByte;
- switch (doNotDisturb) {
- case SCHEDULED:
- dndByte = 0x01;
- break;
- case AUTOMATIC:
- dndByte = 0x02;
- break;
- case ALWAYS:
- dndByte = 0x03;
- break;
- case OFF:
- default:
- dndByte = 0x00;
- break;
- }
-
- final Date start = HuamiCoordinator.getDoNotDisturbStart(gbDevice.getAddress());
- final Date end = HuamiCoordinator.getDoNotDisturbEnd(gbDevice.getAddress());
-
- new ConfigSetter(ConfigType.SYSTEM)
- .setByte(DND_MODE, dndByte)
- .setHourMinute(DND_SCHEDULED_START, start)
- .setHourMinute(DND_SCHEDULED_END, end)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setNightMode(final TransactionBuilder builder) {
- final String nightMode = MiBand3Coordinator.getNightMode(gbDevice.getAddress());
- LOG.info("Setting night mode to {}", nightMode);
-
- final byte nightModeByte;
- switch (nightMode) {
- case MiBandConst.PREF_NIGHT_MODE_SUNSET:
- nightModeByte = 0x01;
- break;
- case MiBandConst.PREF_NIGHT_MODE_SCHEDULED:
- nightModeByte = 0x02;
- break;
- case MiBandConst.PREF_NIGHT_MODE_OFF:
- default:
- nightModeByte = 0x00;
- }
-
- final Date start = MiBand3Coordinator.getNightModeStart(gbDevice.getAddress());
- final Date end = MiBand3Coordinator.getNightModeEnd(gbDevice.getAddress());
-
- new ConfigSetter(ConfigType.SYSTEM)
- .setByte(NIGHT_MODE_MODE, nightModeByte)
- .setHourMinute(NIGHT_MODE_SCHEDULED_START, start)
- .setHourMinute(NIGHT_MODE_SCHEDULED_END, end)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setInactivityWarnings(final TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getInactivityWarnings(gbDevice.getAddress());
- LOG.info("Setting inactivity warnings to {}", enable);
-
- final Date intervalStart = HuamiCoordinator.getInactivityWarningsStart(gbDevice.getAddress());
- final Date intervalEnd = HuamiCoordinator.getInactivityWarningsEnd(gbDevice.getAddress());
- boolean enableDnd = HuamiCoordinator.getInactivityWarningsDnd(gbDevice.getAddress());
- final Date dndStart = HuamiCoordinator.getInactivityWarningsDndStart(gbDevice.getAddress());
- final Date dndEnd = HuamiCoordinator.getInactivityWarningsDndEnd(gbDevice.getAddress());
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(INACTIVITY_WARNINGS_ENABLED, enable)
- .setHourMinute(INACTIVITY_WARNINGS_SCHEDULED_START, intervalStart)
- .setHourMinute(INACTIVITY_WARNINGS_SCHEDULED_END, intervalEnd)
- .setBoolean(INACTIVITY_WARNINGS_DND_ENABLED, enableDnd)
- .setHourMinute(INACTIVITY_WARNINGS_DND_SCHEDULED_START, dndStart)
- .setHourMinute(INACTIVITY_WARNINGS_DND_SCHEDULED_END, dndEnd)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setDisconnectNotification(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
- LOG.warn("Function not implemented");
- return this;
- }
-
@Override
protected Huami2021Support setDistanceUnit(final TransactionBuilder builder) {
final MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
@@ -1500,56 +1215,13 @@ public abstract class Huami2021Support extends HuamiSupport {
break;
}
- new ConfigSetter(ConfigType.SYSTEM)
+ new ConfigSetter()
.setByte(TEMPERATURE_UNIT, unitByte)
.write(this, builder);
return this;
}
- @Override
- protected Huami2021Support setBandScreenUnlock(final TransactionBuilder builder) {
- // Supported by the Mi Band 7 through the band, but not configurable through the app
- LOG.warn("Function not implemented");
- return this;
- }
-
- @Override
- protected Huami2021Support setScreenOnOnNotification(final TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getScreenOnOnNotification(gbDevice.getAddress());
- LOG.info("Set Screen On on notification = {}", enable);
-
- new ConfigSetter(ConfigType.DISPLAY)
- .setBoolean(SCREEN_ON_ON_NOTIFICATIONS, enable)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setScreenBrightness(final TransactionBuilder builder) {
- final int brightness = HuamiCoordinator.getScreenBrightness(gbDevice.getAddress());
- LOG.info("Setting band screen brightness to {}", brightness);
-
- new ConfigSetter(ConfigType.DISPLAY)
- .setShort(SCREEN_BRIGHTNESS, (byte) brightness)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setScreenTimeout(final TransactionBuilder builder) {
- final int timeout = HuamiCoordinator.getScreenTimeout(gbDevice.getAddress());
- LOG.info("Setting band screen timeout to {}", timeout);
-
- new ConfigSetter(ConfigType.DISPLAY)
- .setByte(SCREEN_TIMEOUT, (byte) timeout)
- .write(this, builder);
-
- return this;
- }
-
@Override
protected Huami2021Support setLanguage(final TransactionBuilder builder) {
final String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())
@@ -1557,7 +1229,7 @@ public abstract class Huami2021Support extends HuamiSupport {
LOG.info("Setting device language to {}", localeString);
- new ConfigSetter(ConfigType.LANGUAGE)
+ new ConfigSetter()
.setByte(LANGUAGE, getLanguageId())
.setBoolean(LANGUAGE_FOLLOW_PHONE, localeString.equals("auto"))
.write(this, builder);
@@ -1565,30 +1237,6 @@ public abstract class Huami2021Support extends HuamiSupport {
return this;
}
- @Override
- protected Huami2021Support setExposeHRThirdParty(final TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getExposeHRThirdParty(gbDevice.getAddress());
- LOG.info("Setting exposure of HR to third party apps to {}", enable);
-
- new ConfigSetter(ConfigType.HEALTH)
- .setBoolean(THIRD_PARTY_HR_SHARING, enable)
- .write(this, builder);
-
- return this;
- }
-
- @Override
- protected Huami2021Support setBtConnectedAdvertising(final TransactionBuilder builder) {
- final boolean enable = HuamiCoordinator.getBtConnectedAdvertising(gbDevice.getAddress());
- LOG.info("Setting connected advertisement to: {}", enable);
-
- new ConfigSetter(ConfigType.BLUETOOTH)
- .setBoolean(BLUETOOTH_CONNECTED_ADVERTISING, enable)
- .write(this, builder);
-
- return this;
- }
-
@Override
protected void writeToChunked(final TransactionBuilder builder,
final int type,
@@ -1608,7 +1256,6 @@ public abstract class Huami2021Support extends HuamiSupport {
@Override
protected Huami2021Support requestGPSVersion(final TransactionBuilder builder) {
- // Not supported by the Mi Band 7 at least
LOG.warn("Request GPS version not implemented");
return this;
}
@@ -1646,7 +1293,6 @@ public abstract class Huami2021Support extends HuamiSupport {
return this;
}
- @Override
protected Huami2021Support requestShortcuts(final TransactionBuilder builder) {
LOG.info("Requesting shortcuts");
@@ -1660,23 +1306,59 @@ public abstract class Huami2021Support extends HuamiSupport {
return this;
}
+ protected Huami2021Support requestControlCenter(final TransactionBuilder builder) {
+ LOG.info("Requesting shortcuts");
+
+ writeToChunked2021(
+ builder,
+ CHUNKED2021_ENDPOINT_DISPLAY_ITEMS,
+ new byte[]{DISPLAY_ITEMS_CMD_REQUEST, DISPLAY_ITEMS_CONTROL_CENTER},
+ true
+ );
+
+ return this;
+ }
+
+ protected void requestMTU(final TransactionBuilder builder) {
+ writeToChunked2021(
+ builder,
+ CHUNKED2021_ENDPOINT_CONNECTION,
+ CONNECTION_CMD_MTU_REQUEST,
+ false
+ );
+ }
+
+ protected void requestCapabilityReminders(final TransactionBuilder builder) {
+ writeToChunked2021(
+ builder,
+ CHUNKED2021_ENDPOINT_REMINDERS,
+ REMINDERS_CMD_CAPABILITIES_REQUEST,
+ false
+ );
+ }
+
@Override
public void phase2Initialize(final TransactionBuilder builder) {
LOG.info("2021 phase2Initialize...");
+ requestMTU(builder);
requestBatteryInfo(builder);
}
@Override
public void phase3Initialize(final TransactionBuilder builder) {
+ final Huami2021Coordinator coordinator = getCoordinator();
+
LOG.info("2021 phase3Initialize...");
setUserInfo(builder);
+ setFitnessGoal(builder);
- for (final ConfigType configType : ConfigType.values()) {
- // FIXME: Request only supported args?
- requestConfig(builder, configType);
+ for (final ConfigGroup configGroup : ConfigGroup.values()) {
+ requestConfig(builder, configGroup);
}
- for (final HuamiVibrationPatternNotificationType type : HuamiVibrationPatternNotificationType.values()) {
+ requestCapabilityReminders(builder);
+
+ for (final HuamiVibrationPatternNotificationType type : coordinator.getVibrationPatternNotificationTypes(gbDevice)) {
// FIXME: Can we read these from the band?
final String typeKey = type.name().toLowerCase(Locale.ROOT);
setVibrationPattern(builder, HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
@@ -1685,6 +1367,9 @@ public abstract class Huami2021Support extends HuamiSupport {
requestCannedMessages(builder);
requestDisplayItems(builder);
requestShortcuts(builder);
+ if (coordinator.supportsControlCenter()) {
+ requestControlCenter(builder);
+ }
requestAlarms(builder);
//requestReminders(builder);
}
@@ -1704,6 +1389,11 @@ public abstract class Huami2021Support extends HuamiSupport {
return true;
}
+ @Override
+ protected Huami2021Coordinator getCoordinator() {
+ return (Huami2021Coordinator) DeviceHelper.getInstance().getCoordinator(gbDevice);
+ }
+
@Override
public void handle2021Payload(final int type, final byte[] payload) {
if (payload == null || payload.length == 0) {
@@ -1759,6 +1449,9 @@ public abstract class Huami2021Support extends HuamiSupport {
case CHUNKED2021_ENDPOINT_CANNED_MESSAGES:
handle2021CannedMessages(payload);
return;
+ case CHUNKED2021_ENDPOINT_CONNECTION:
+ handle2021Connection(payload);
+ return;
case CHUNKED2021_ENDPOINT_USER_INFO:
handle2021UserInfo(payload);
return;
@@ -1947,12 +1640,13 @@ public abstract class Huami2021Support extends HuamiSupport {
}
private void requestConfig(final TransactionBuilder builder,
- final ConfigType config,
+ final ConfigGroup config,
+ final boolean includeConstraints,
final List args) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(CONFIG_CMD_REQUEST);
- baos.write(args.isEmpty() ? CONFIG_REQUEST_TYPE_ALL : CONFIG_REQUEST_TYPE_SPECIFIC);
+ baos.write(bool(includeConstraints));
baos.write(config.getValue());
baos.write(args.size());
for (final Huami2021Config.ConfigArg arg : args) {
@@ -1962,8 +1656,8 @@ public abstract class Huami2021Support extends HuamiSupport {
writeToChunked2021(builder, CHUNKED2021_ENDPOINT_CONFIG, baos.toByteArray(), true);
}
- private void requestConfig(final TransactionBuilder builder, final ConfigType config) {
- requestConfig(builder, config, Huami2021Config.ConfigArg.getAllArgsForConfigType(config));
+ private void requestConfig(final TransactionBuilder builder, final ConfigGroup config) {
+ requestConfig(builder, config, true, Huami2021Config.ConfigArg.getAllArgsForConfigGroup(config));
}
protected void handle2021Config(final byte[] payload) {
@@ -1986,17 +1680,24 @@ public abstract class Huami2021Support extends HuamiSupport {
}
private void handle2021ConfigResponse(final byte[] payload) {
- final ConfigType configType = ConfigType.fromValue(payload[2]);
- if (configType == null) {
+ final ConfigGroup configGroup = ConfigGroup.fromValue(payload[2]);
+ if (configGroup == null) {
LOG.warn("Unknown config type {}", String.format("0x%02x", payload[2]));
return;
}
+ if (configGroup.getVersion() != payload[3]) {
+ LOG.warn("Unexpected next byte {} for {}", String.format("0x%02x", payload[3]), configGroup);
+ return;
+ }
+
+ final boolean includesConstraints = payload[4] == 0x01;
+
int numConfigs = payload[5] & 0xff;
- LOG.info("Got {} configs for {}", numConfigs, configType);
+ LOG.info("Got {} configs for {}", numConfigs, configGroup);
- final Map prefs = new Huami2021Config.ConfigParser(configType)
+ final Map prefs = new Huami2021Config.ConfigParser(configGroup, includesConstraints)
.parse(numConfigs, subarray(payload, 6, payload.length));
if (prefs == null) {
@@ -2094,28 +1795,42 @@ public abstract class Huami2021Support extends HuamiSupport {
return;
}
- final String allScreensPrefKey;
final String prefKey;
+ final Map idMap;
switch (payload[1]) {
case DISPLAY_ITEMS_MENU:
LOG.info("Got {} display items", numberScreens);
- allScreensPrefKey = HuamiConst.PREF_ALL_DISPLAY_ITEMS;
prefKey = HuamiConst.PREF_DISPLAY_ITEMS_SORTABLE;
+ idMap = Huami2021MenuType.displayItemNameLookup;
break;
case DISPLAY_ITEMS_SHORTCUTS:
LOG.info("Got {} shortcuts", numberScreens);
- allScreensPrefKey = HuamiConst.PREF_ALL_SHORTCUTS;
prefKey = HuamiConst.PREF_SHORTCUTS_SORTABLE;
+ idMap = Huami2021MenuType.shortcutsNameLookup;
+ break;
+ case DISPLAY_ITEMS_CONTROL_CENTER:
+ LOG.info("Got {} control center", numberScreens);
+ prefKey = HuamiConst.PREF_CONTROL_CENTER_SORTABLE;
+ idMap = Huami2021MenuType.controlCenterNameLookup;
break;
default:
LOG.error("Unknown display items type {}", String.format("0x%x", payload[1]));
return;
}
+ final String allScreensPrefKey = Huami2021Config.getPrefPossibleValuesKey(prefKey);
+
+ final boolean menuHasMoreSection;
+
+ if (payload[1] == DISPLAY_ITEMS_MENU) {
+ menuHasMoreSection = getCoordinator().mainMenuHasMoreSection();
+ } else {
+ menuHasMoreSection = false;
+ }
final String[] mainScreensArr = new String[numberScreens];
final String[] moreScreensArr = new String[numberScreens];
final List allScreens = new LinkedList<>();
- if (payload[1] == DISPLAY_ITEMS_MENU) {
+ if (menuHasMoreSection) {
// The band doesn't report the "more" screen, so we add it
allScreens.add("more");
}
@@ -2123,8 +1838,8 @@ public abstract class Huami2021Support extends HuamiSupport {
for (int i = 0; i < numberScreens; i++) {
// Screen IDs are sent as literal hex strings
final String screenId = new String(subarray(payload, 4 + i * 12, 4 + i * 12 + 8));
-
- allScreens.add(screenId);
+ final String screenNameOrId = idMap.containsKey(screenId) ? idMap.get(screenId) : screenId;
+ allScreens.add(screenNameOrId);
final int screenSectionVal = payload[4 + i * 12 + 9];
final int screenPosition = payload[4 + i * 12 + 10];
@@ -2140,14 +1855,14 @@ public abstract class Huami2021Support extends HuamiSupport {
LOG.warn("Duplicate position {} for main section", screenPosition);
}
//LOG.debug("mainScreensArr[{}] = {}", screenPosition, screenKey);
- mainScreensArr[screenPosition] = screenId;
+ mainScreensArr[screenPosition] = screenNameOrId;
break;
case DISPLAY_ITEMS_SECTION_MORE:
if (moreScreensArr[screenPosition] != null) {
LOG.warn("Duplicate position {} for more section", screenPosition);
}
//LOG.debug("moreScreensArr[{}] = {}", screenPosition, screenKey);
- moreScreensArr[screenPosition] = screenId;
+ moreScreensArr[screenPosition] = screenNameOrId;
break;
case DISPLAY_ITEMS_SECTION_DISABLED:
// Ignore disabled screens
@@ -2159,21 +1874,27 @@ public abstract class Huami2021Support extends HuamiSupport {
}
final List screens = new ArrayList<>(Arrays.asList(mainScreensArr));
- if (payload[1] == DISPLAY_ITEMS_MENU) {
+ if (menuHasMoreSection) {
screens.add("more");
screens.addAll(Arrays.asList(moreScreensArr));
}
screens.removeAll(Collections.singleton(null));
- final String allScrensPrefValue = StringUtils.join(",", allScreens.toArray(new String[0])).toString();
+ final String allScreensPrefValue = StringUtils.join(",", allScreens.toArray(new String[0])).toString();
final String prefValue = StringUtils.join(",", screens.toArray(new String[0])).toString();
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
- .withPreference(allScreensPrefKey, allScrensPrefValue)
+ .withPreference(allScreensPrefKey, allScreensPrefValue)
.withPreference(prefKey, prefValue);
evaluateGBDeviceEvent(eventUpdatePreferences);
}
+ /**
+ * A handler to schedule the find phone event.
+ */
+ private final Handler findPhoneHandler = new Handler();
+ private boolean findPhoneStarted;
+
protected void handle2021FindDevice(final byte[] payload) {
final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
@@ -2183,15 +1904,36 @@ public abstract class Huami2021Support extends HuamiSupport {
return;
case FIND_PHONE_START:
LOG.info("Find Phone Start");
- acknowledgeFindPhone(); // FIXME: premature
- findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
- evaluateGBDeviceEvent(findPhoneEvent);
+ acknowledgeFindPhone(); // FIXME: Premature, but the band will only send the mode after we ack
+
+ // Delay the find phone start, because we might get the FIND_PHONE_MODE
+ findPhoneHandler.postDelayed(() -> {
+ findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+ evaluateGBDeviceEvent(findPhoneEvent);
+ }, 1500);
+
+ break;
+ case FIND_BAND_STOP_FROM_BAND:
+ LOG.info("Find Band Stop from Band");
break;
case FIND_PHONE_STOP_FROM_BAND:
LOG.info("Find Phone Stop");
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
evaluateGBDeviceEvent(findPhoneEvent);
break;
+ case FIND_PHONE_MODE:
+ findPhoneHandler.removeCallbacksAndMessages(null);
+
+ final int mode = payload[1] & 0xff; // 0 to only vibrate, 1 to ring
+ LOG.info("Find Phone Mode: {}", mode);
+ if (findPhoneStarted) {
+ // Already started, just change the mode
+ findPhoneEvent.event = mode == 1 ? GBDeviceEventFindPhone.Event.RING : GBDeviceEventFindPhone.Event.VIBRATE;
+ } else {
+ findPhoneEvent.event = mode == 1 ? GBDeviceEventFindPhone.Event.START : GBDeviceEventFindPhone.Event.START_VIBRATE;
+ }
+ evaluateGBDeviceEvent(findPhoneEvent);
+ break;
default:
LOG.warn("Unexpected find phone byte {}", String.format("0x%02x", payload[0]));
}
@@ -2246,17 +1988,7 @@ public abstract class Huami2021Support extends HuamiSupport {
if (path.startsWith("/weather/")) {
final Huami2021Weather.Response response = Huami2021Weather.handleHttpRequest(path, query);
-
- if (response != null) {
- replyHttpSuccess(requestId, 200, response.toJson());
- } else {
- final Huami2021Weather.Response notFoundResponse = new Huami2021Weather.ErrorResponse(
- -2001,
- "Not found"
- );
- replyHttpSuccess(requestId, 404, notFoundResponse.toJson());
- }
-
+ replyHttpSuccess(requestId, response.getHttpStatusCode(), response.toJson());
return;
}
@@ -2581,6 +2313,19 @@ public abstract class Huami2021Support extends HuamiSupport {
protected void handle2021Reminders(final byte[] payload) {
switch (payload[0]) {
+ case REMINDERS_CMD_CAPABILITIES_RESPONSE:
+ LOG.info("Reminder capability, status = {}", payload[1]);
+ if (payload[1] != 1) {
+ LOG.warn("Reminder capability unexpected status");
+ return;
+ }
+ final int numReminders = payload[2] & 0xff;
+ final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(
+ REMINDERS_PREF_CAPABILITY,
+ numReminders
+ );
+ evaluateGBDeviceEvent(eventUpdatePreferences);
+ return;
case REMINDERS_CMD_CREATE_ACK:
LOG.info("Reminder create ACK, status = {}", payload[1]);
return;
@@ -2783,6 +2528,23 @@ public abstract class Huami2021Support extends HuamiSupport {
evaluateGBDeviceEvent(gbDeviceEventUpdatePreferences);
}
+ protected void handle2021Connection(final byte[] payload) {
+ switch (payload[0]) {
+ case CONNECTION_CMD_MTU_RESPONSE:
+ final int mtu = BLETypeConversions.toUint16(payload, 1) + 3;
+ LOG.info("Device announced MTU change: {}", mtu);
+ setMtu(mtu);
+ return;
+ case CONNECTION_CMD_UNKNOWN_3:
+ // Some ping? Band sometimes sends 0x03, phone replies with 0x04
+ LOG.info("Got unknown 3, replying with unknown 4");
+ writeToChunked2021("respond connection unknown 4", CHUNKED2021_ENDPOINT_CONNECTION, CONNECTION_CMD_UNKNOWN_4, false);
+ return;
+ }
+
+ LOG.warn("Unexpected connection payload byte {}", String.format("0x%02x", payload[0]));
+ }
+
protected void handle2021UserInfo(final byte[] payload) {
// TODO handle2021UserInfo
LOG.warn("Unexpected user info payload byte {}", String.format("0x%02x", payload[0]));
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Weather.java
index d3c02f617..1751652b2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Weather.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Weather.java
@@ -26,8 +26,6 @@ import net.e175.klaus.solarpositioning.SPA;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.threeten.bp.LocalDate;
-import org.threeten.bp.format.DateTimeFormatter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -82,11 +80,12 @@ public class Huami2021Weather {
return new HourlyResponse();
case "/weather/alerts":
return new AlertsResponse();
- default:
- LOG.error("Unknown weather path {}", path);
+ //case "/weather/tide":
+ // return new TideResponse(weatherSpec);
}
- return null;
+ LOG.error("Unknown weather path {}", path);
+ return new Huami2021Weather.ErrorResponse(404, -2001, "Not found");
}
private static class RawJsonStringResponse extends Response {
@@ -102,16 +101,23 @@ public class Huami2021Weather {
}
public static class ErrorResponse extends Response {
- private final int code;
+ private final int httpStatusCode;
+ private final int errorCode;
private final String message;
- public ErrorResponse(final int code, final String message) {
- this.code = code;
+ public ErrorResponse(final int httpStatusCode, final int errorCode, final String message) {
+ this.httpStatusCode = httpStatusCode;
+ this.errorCode = errorCode;
this.message = message;
}
- public int getCode() {
- return code;
+ @Override
+ public int getHttpStatusCode() {
+ return httpStatusCode;
+ }
+
+ public int getErrorCode() {
+ return errorCode;
}
public String getMessage() {
@@ -120,6 +126,10 @@ public class Huami2021Weather {
}
public static abstract class Response {
+ public int getHttpStatusCode() {
+ return 200;
+ }
+
public String toJson() {
return GSON.toJson(this);
}
@@ -197,8 +207,8 @@ public class Huami2021Weather {
}
private static class MoonRiseSet {
- public List moonPhaseValue = new ArrayList<>();
- public List moonRise = new ArrayList<>();
+ public List moonPhaseValue = new ArrayList<>(); // numbers? 20 21 23...
+ public List moonRise = new ArrayList<>(); // yyyy-MM-dd HH:mm:ss
}
private static class Range {
@@ -249,7 +259,7 @@ public class Huami2021Weather {
// locationKey=00.000,-0.000,xiaomi_accu:000000
public static class CurrentResponse extends Response {
public CurrentWeatherModel currentWeatherModel;
- public Object aqiModel = new Object();
+ public AqiModel aqiModel = new AqiModel();
public CurrentResponse(final WeatherSpec weatherSpec) {
this.currentWeatherModel = new CurrentWeatherModel(weatherSpec);
@@ -273,11 +283,52 @@ public class Huami2021Weather {
temperature = new UnitValue(Unit.TEMPERATURE_C, weatherSpec.currentTemp - 273);
uvIndex = "0";
visibility = new UnitValue(Unit.KM, "");
- weather = String.valueOf(HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode) & 0xff); // is it?
+ weather = String.valueOf(HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode) & 0xff);
wind = new Wind(weatherSpec.windDirection, Math.round(weatherSpec.windSpeed));
}
}
+ private static class AqiModel {
+ public String pm10 = "";
+ public String pm25 = "";
+ }
+
+ // /weather/tide
+ //
+ // locale=en_US
+ // deviceSource=7930113
+ // days=10
+ // isGlobal=true
+ // latitude=00.000
+ // longitude=-00.000
+ private static class TideResponse extends Response {
+ public Date pubTime;
+ public String poiName; // poi tide station name
+ public String poiKey; // lat,lon,POI_ID
+ public List tideData = new ArrayList<>();
+
+ public TideResponse(final WeatherSpec weatherSpec) {
+ pubTime = new Date(weatherSpec.timestamp * 1000L);
+ }
+ }
+
+ private static class TideDataEntry {
+ public String date; // YYYY-MM-DD, but LocalDate would need API 26+
+ public List tideTable = new ArrayList<>();
+ public List tideHourly = new ArrayList<>();
+ }
+
+ private static class TideTableEntry {
+ public Date fxTime; // pubTime format
+ public String height; // float, x.xx
+ public String type; // H / L
+ }
+
+ private static class TideHourlyEntry {
+ public Date fxTime; // pubTime format
+ public String height; // float, x.xx
+ }
+
private enum Unit {
PRESSURE_MB("mb"),
PERCENTAGE("%"),
@@ -332,6 +383,7 @@ public class Huami2021Weather {
// locationKey=00.000,-0.000,xiaomi_accu:000000
public static class HourlyResponse extends Response {
public Date pubTime;
+ // One entry in each list per hour
public List weather;
public List temperature;
public List humidity;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java
index ba258690a..0ede411d5 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiFirmwareType.java
@@ -31,6 +31,8 @@ public enum HuamiFirmwareType {
WATCHFACE((byte) 8),
APP((byte) 8),
FONT_LATIN((byte) 11),
+ ZEPPOS_UNKNOWN_0X13((byte) 0x13),
+ ZEPPOS_APP((byte) 0xa0),
INVALID(Byte.MIN_VALUE);
private final byte value;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
index 42e65a75d..340ce2c75 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
@@ -52,6 +52,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
+import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
@@ -183,9 +184,7 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_RELAXATION_REMINDER;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_ENABLE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_END;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_START;
@@ -203,8 +202,6 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SOUNDS;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPO2_ALL_DAY_MONITORING;
-import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SPO2_LOW_ALERT_THRESHOLD;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION;
@@ -236,6 +233,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_SCHEDULE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_TODO_LIST;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_ALARM;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER;
@@ -245,6 +244,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_INCOMING_CALL;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_INCOMING_SMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_ALARM;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_APP_ALERTS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER;
@@ -254,6 +255,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_SCHEDULE;
+import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_TODO_LIST;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_ALARMS_WITH_TIMES;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_GPS_VERSION;
@@ -753,21 +756,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return this;
}
- protected HuamiSupport setHeartrateSleepBreathingQualityMonitoring(TransactionBuilder builder) {
- LOG.warn("setHeartrateSleepBreathingQualityMonitoring not implemented");
- return this;
- }
-
- protected HuamiSupport setSPO2AllDayMonitoring(TransactionBuilder builder) {
- LOG.warn("setSPO2AllDayMonitoring not implemented");
- return this;
- }
-
- protected HuamiSupport setSPO2AlertThreshold(TransactionBuilder builder) {
- LOG.warn("setSPO2AlertThreshold not implemented");
- return this;
- }
-
protected HuamiSupport setHeartrateStressMonitoring(TransactionBuilder builder) {
final boolean enableHrStressMonitoring = HuamiCoordinator.getHeartrateStressMonitoring(gbDevice.getAddress());
LOG.info("Setting heart rate stress monitoring to {}", enableHrStressMonitoring);
@@ -776,11 +764,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return this;
}
- protected HuamiSupport setHeartrateStressRelaxationReminder(TransactionBuilder builder) {
- LOG.warn("setHeartrateStressRelaxationReminder not implemented");
- return this;
- }
-
protected HuamiSupport setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
if (characteristicHRControlPoint != null) {
builder.notify(characteristicHRControlPoint, true);
@@ -998,7 +981,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
// Delete the remaining slots, skipping the sent reminders and reserved slots
- for (int i = reminders.size() + reservedSlots; i < coordinator.getReminderSlotCount(); i++) {
+ final int reminderSlotCount = coordinator.getReminderSlotCount(getDevice());
+ for (int i = reminders.size() + reservedSlots; i < reminderSlotCount; i++) {
LOG.debug("Deleting reminder at position {}", i);
sendReminderToDevice(builder, i, null);
@@ -1012,9 +996,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
+ final int reminderSlotCount = coordinator.getReminderSlotCount(getDevice());
- if (position + 1 > coordinator.getReminderSlotCount()) {
- LOG.error("Reminder for position {} is over the limit of {} reminders", position, coordinator.getReminderSlotCount());
+ if (position + 1 > reminderSlotCount) {
+ LOG.error("Reminder for position {} is over the limit of {} reminders", position, reminderSlotCount);
return;
}
@@ -1106,7 +1091,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
try {
baos.write(0x03);
-
+
if (clocks.size() != 0) {
baos.write(clocks.size());
int i = 0;
@@ -1582,17 +1567,27 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
protected int getFindDeviceInterval() {
- VibrationProfile findBand = HuamiCoordinator.getVibrationProfile(getDevice().getAddress(), HuamiVibrationPatternNotificationType.FIND_BAND);
+ final VibrationProfile findBand = HuamiCoordinator.getVibrationProfile(
+ getDevice().getAddress(),
+ HuamiVibrationPatternNotificationType.FIND_BAND,
+ supportsDeviceDefaultVibrationProfiles()
+ );
int findDeviceInterval = 0;
- for(int len : findBand.getOnOffSequence())
- findDeviceInterval += len;
+ if (findBand != null) {
+ // It can be null if the device supports continuous find mode
+ // If that's the case, this function shouldn't even have been called
+ for(int len : findBand.getOnOffSequence())
+ findDeviceInterval += len;
- if(findBand.getRepeat() > 0)
- findDeviceInterval *= findBand.getRepeat();
+ if(findBand.getRepeat() > 0)
+ findDeviceInterval *= findBand.getRepeat();
- if(findDeviceInterval > 10000) // 10 seconds, about as long as Mi Fit allows
+ if(findDeviceInterval > 10000) // 10 seconds, about as long as Mi Fit allows
+ findDeviceInterval = 10000;
+ } else {
findDeviceInterval = 10000;
+ }
return findDeviceInterval;
}
@@ -1893,18 +1888,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
case HuamiDeviceEvent.MTU_REQUEST:
int mtu = (value[2] & 0xff) << 8 | value[1] & 0xff;
LOG.info("device announced MTU of " + mtu);
- Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
- if (!prefs.getBoolean(PREF_ALLOW_HIGH_MTU, false)) {
- break;
- }
- if (mtu < 23) {
- LOG.error("Device announced unreasonable low MTU of " + mtu + ", ignoring");
- break;
- }
- mMTU = mtu;
- if (huami2021ChunkedEncoder != null) {
- huami2021ChunkedEncoder.setMTU(mtu);
- }
+ setMtu(mtu);
/*
* not really sure if this would make sense, is this event already a proof of a successful MTU
* negotiation initiated by the Huami device, and acknowledged by the phone? do we really have to
@@ -2114,15 +2098,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
- final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
-
- if (!prefs.getBoolean(PREF_ALLOW_HIGH_MTU, false)) {
- LOG.warn("Ignoring MTU change to {}", mtu);
- return;
- }
-
LOG.info("MTU changed to {}", mtu);
- this.mMTU = mtu;
+ setMtu(mtu);
}
protected void acknowledgeFindPhone() {
@@ -2772,22 +2749,13 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
setRotateWristToSwitchInfo(builder);
break;
case ActivityUser.PREF_USER_STEPS_GOAL:
+ case ActivityUser.PREF_USER_CALORIES_BURNT:
+ case ActivityUser.PREF_USER_SLEEP_DURATION:
+ case ActivityUser.PREF_USER_GOAL_WEIGHT_KG:
+ case ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS:
+ case ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES:
setFitnessGoal(builder);
break;
- case PREF_SCREEN_ON_ON_NOTIFICATIONS:
- setScreenOnOnNotification(builder);
- break;
- case PREF_SCREEN_BRIGHTNESS:
- setScreenBrightness(builder);
- break;
- case PREF_SCREEN_TIMEOUT:
- setScreenTimeout(builder);
- break;
- case PREF_ALWAYS_ON_DISPLAY_MODE:
- case PREF_ALWAYS_ON_DISPLAY_START:
- case PREF_ALWAYS_ON_DISPLAY_END:
- setAlwaysOnDisplay(builder);
- break;
case MiBandConst.PREF_NIGHT_MODE:
case MiBandConst.PREF_NIGHT_MODE_START:
case MiBandConst.PREF_NIGHT_MODE_END:
@@ -2855,6 +2823,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
case PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS:
case PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER:
case PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND:
+ case PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST:
+ case PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE:
case PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS:
case PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL:
case PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS:
@@ -2863,6 +2833,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
case PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS:
case PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER:
case PREF_HUAMI_VIBRATION_COUNT_FIND_BAND:
+ case PREF_HUAMI_VIBRATION_COUNT_TODO_LIST:
+ case PREF_HUAMI_VIBRATION_COUNT_SCHEDULE:
case PREF_HUAMI_VIBRATION_TRY_APP_ALERTS:
case PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL:
case PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS:
@@ -2871,6 +2843,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
case PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS:
case PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER:
case PREF_HUAMI_VIBRATION_TRY_FIND_BAND:
+ case PREF_HUAMI_VIBRATION_TRY_TODO_LIST:
+ case PREF_HUAMI_VIBRATION_TRY_SCHEDULE:
setVibrationPattern(builder, config);
break;
case PREF_HEARTRATE_ACTIVITY_MONITORING:
@@ -2881,21 +2855,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
case PREF_HEARTRATE_ALERT_LOW_THRESHOLD:
setHeartrateAlert(builder);
break;
- case PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING:
- setHeartrateSleepBreathingQualityMonitoring(builder);
- break;
- case PREF_SPO2_ALL_DAY_MONITORING:
- setSPO2AllDayMonitoring(builder);
- break;
- case PREF_SPO2_LOW_ALERT_THRESHOLD:
- setSPO2AlertThreshold(builder);
- break;
case PREF_HEARTRATE_STRESS_MONITORING:
setHeartrateStressMonitoring(builder);
break;
- case PREF_HEARTRATE_STRESS_RELAXATION_REMINDER:
- setHeartrateStressRelaxationReminder(builder);
- break;
case PasswordCapabilityImpl.PREF_PASSWORD:
case PasswordCapabilityImpl.PREF_PASSWORD_ENABLED:
setPassword(builder);
@@ -2933,13 +2895,24 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
final HuamiVibrationPatternNotificationType notificationType = HuamiVibrationPatternNotificationType.valueOf(notificationTypeName);
final boolean isTry = preferenceKey.startsWith(PREF_HUAMI_VIBRATION_TRY_PREFIX);
- final VibrationProfile vibrationProfile = HuamiCoordinator.getVibrationProfile(getDevice().getAddress(), notificationType);
+ final VibrationProfile vibrationProfile = HuamiCoordinator.getVibrationProfile(
+ getDevice().getAddress(),
+ notificationType,
+ supportsDeviceDefaultVibrationProfiles()
+ );
setVibrationPattern(builder, notificationType, isTry, vibrationProfile);
return this;
}
+ /**
+ * Whether the device supports built-in default vibration profiles.
+ */
+ protected boolean supportsDeviceDefaultVibrationProfiles() {
+ return false;
+ }
+
/**
* Test or set a {@link VibrationProfile}.
*
@@ -2949,9 +2922,14 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
* @param profile the {@link VibrationProfile}
*/
protected void setVibrationPattern(final TransactionBuilder builder,
- final HuamiVibrationPatternNotificationType notificationType,
- final boolean test,
- final VibrationProfile profile) {
+ final HuamiVibrationPatternNotificationType notificationType,
+ final boolean test,
+ final VibrationProfile profile) {
+ if (profile == null) {
+ LOG.error("Vibration profile is null for {}", notificationType);
+ return;
+ }
+
final int MAX_TOTAL_LENGTH_MS = 10_000; // 10 seconds, about as long as Mi Fit allows
// The on-off sequence, until the max total length is reached
@@ -2977,6 +2955,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
protected List truncateVibrationsOnOff(final VibrationProfile profile, final int limitMillis) {
+ if (profile == null) {
+ return Collections.emptyList();
+ }
+
int totalLengthMs = 0;
// The on-off sequence, until the max total length is reached
@@ -3304,11 +3286,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return this;
}
- protected HuamiSupport setAlwaysOnDisplay(TransactionBuilder builder) {
- LOG.warn("Always on display not implemented");
- return this;
- }
-
protected HuamiSupport setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext(), gbDevice.getAddress());
LOG.info("Setting activate display on lift wrist to " + displayOnLift);
@@ -3812,24 +3789,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return this;
}
- protected HuamiSupport setScreenOnOnNotification(TransactionBuilder builder) {
- LOG.warn("Function not implemented");
-
- return this;
- }
-
- protected HuamiSupport setScreenBrightness(TransactionBuilder builder) {
- LOG.warn("Function not implemented");
-
- return this;
- }
-
- protected HuamiSupport setScreenTimeout(TransactionBuilder builder) {
- LOG.warn("Function not implemented");
-
- return this;
- }
-
protected HuamiSupport setLanguage(TransactionBuilder builder) {
String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
if (localeString == null || localeString.equals("auto")) {
@@ -3978,10 +3937,18 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
}
+ protected void writeToChunked2021(TransactionBuilder builder, short type, byte data, boolean encrypt) {
+ writeToChunked2021(builder, type, new byte[]{data}, encrypt);
+ }
+
protected void writeToChunked2021(TransactionBuilder builder, short type, byte[] data, boolean encrypt) {
huami2021ChunkedEncoder.write(builder, type, data, force2021Protocol(), encrypt);
}
+ protected void writeToChunked2021(final String taskName, short type, byte data, boolean encrypt) {
+ writeToChunked2021(taskName, type, new byte[]{data}, encrypt);
+ }
+
protected void writeToChunked2021(final String taskName, short type, byte[] data, boolean encrypt) {
try {
final TransactionBuilder builder = performInitialized(taskName);
@@ -4020,11 +3987,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return this;
}
- protected HuamiSupport requestShortcuts(TransactionBuilder builder) {
- LOG.warn("Function not implemented");
- return this;
- }
-
@Override
public String customStringFilter(String inputString) {
if (HuamiCoordinator.getUseCustomFont(gbDevice.getAddress())) {
@@ -4066,7 +4028,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
}
public void phase3Initialize(TransactionBuilder builder) {
- final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
+ final HuamiCoordinator coordinator = getCoordinator();
LOG.info("phase3Initialize...");
@@ -4094,7 +4056,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
setHeartrateMeasurementInterval(builder, HuamiCoordinator.getHeartRateMeasurementInterval(getDevice().getAddress()));
sendReminders(builder);
setWorldClocks(builder);
- for (final HuamiVibrationPatternNotificationType type : HuamiVibrationPatternNotificationType.values()) {
+ for (final HuamiVibrationPatternNotificationType type : coordinator.getVibrationPatternNotificationTypes(getDevice())) {
final String typeKey = type.name().toLowerCase(Locale.ROOT);
setVibrationPattern(builder, HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
}
@@ -4116,6 +4078,24 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return mMTU;
}
+ protected void setMtu(final int mtu) {
+ final Prefs prefs = getDevicePrefs();
+ if (!prefs.getBoolean(PREF_ALLOW_HIGH_MTU, false)) {
+ LOG.warn("High MTU is not allowed, ignoring");
+ return;
+ }
+
+ if (mtu < 23) {
+ LOG.error("Device announced unreasonable low MTU of {}, ignoring", mtu);
+ return;
+ }
+
+ this.mMTU = mtu;
+ if (huami2021ChunkedEncoder != null) {
+ huami2021ChunkedEncoder.setMTU(mtu);
+ }
+ }
+
public int getActivitySampleSize() {
return mActivitySampleSize;
}
@@ -4124,6 +4104,14 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("force_new_protocol", false);
}
+ protected HuamiCoordinator getCoordinator() {
+ return (HuamiCoordinator) DeviceHelper.getInstance().getCoordinator(gbDevice);
+ }
+
+ protected Prefs getDevicePrefs() {
+ return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+ }
+
@Override
public void handle2021Payload(int type, byte[] payload) {
if (type == Huami2021Service.CHUNKED2021_ENDPOINT_COMPAT) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java
index ba9ea0aae..a0fee9aeb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiVibrationPatternNotificationType.java
@@ -28,7 +28,10 @@ public enum HuamiVibrationPatternNotificationType {
ALARM(0x05),
IDLE_ALERTS(0x06),
EVENT_REMINDER(0x08),
- FIND_BAND(0x09);
+ FIND_BAND(0x09),
+ TODO_LIST(0x0a),
+ SCHEDULE(0x0c),
+ ;
private final byte code;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr3/AmazfitGTR3Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr3/AmazfitGTR3Support.java
index b6bad994b..74dd268e2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr3/AmazfitGTR3Support.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr3/AmazfitGTR3Support.java
@@ -24,7 +24,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
-import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr3.AmazfitGTR3FWHelper;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr4/AmazfitGTR4FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr4/AmazfitGTR4FirmwareInfo.java
new file mode 100644
index 000000000..5cc0687cb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr4/AmazfitGTR4FirmwareInfo.java
@@ -0,0 +1,60 @@
+/* Copyright (C) 2022 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.amazfitgtr4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021FirmwareInfo;
+
+public class AmazfitGTR4FirmwareInfo extends Huami2021FirmwareInfo {
+ private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTR4FirmwareInfo.class);
+
+ private static final Map crcToVersion = new HashMap() {{
+ // firmware
+ }};
+
+ public AmazfitGTR4FirmwareInfo(final byte[] bytes) {
+ super(bytes);
+ }
+
+ @Override
+ public String deviceName() {
+ return HuamiConst.AMAZFIT_GTR4_NAME;
+ }
+
+ @Override
+ public byte[] getExpectedFirmwareHeader() {
+ return new byte[]{(byte) 0x51, (byte) 0x71, (byte) 0x9c}; // Probably bogus, only checked against 1 firmware files
+ }
+
+ @Override
+ public boolean isGenerallyCompatibleWith(final GBDevice device) {
+ return isHeaderValid() && device.getType() == DeviceType.AMAZFITGTR4;
+ }
+
+ @Override
+ protected Map getCrcMap() {
+ return crcToVersion;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr4/AmazfitGTR4Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr4/AmazfitGTR4Support.java
new file mode 100644
index 000000000..a21d71343
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/amazfitgtr4/AmazfitGTR4Support.java
@@ -0,0 +1,38 @@
+/* Copyright (C) 2022 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.amazfitgtr4;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr4.AmazfitGTR4FWHelper;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Support;
+
+public class AmazfitGTR4Support extends Huami2021Support {
+ private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTR4Support.class);
+
+ @Override
+ public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
+ return new AmazfitGTR4FWHelper(uri, context);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
index f7134a219..8992463f0 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
@@ -209,8 +209,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
}
@Override
- public void onPhoneFound() {
- byte[] bytes = gbDeviceProtocol.encodePhoneFound();
+ public void onFindPhone(boolean start) {
+ byte[] bytes = gbDeviceProtocol.encodeFindPhone(start);
sendToDevice(bytes);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
index 927b97c35..5875623ec 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
@@ -110,7 +110,7 @@ public abstract class GBDeviceProtocol {
return null;
}
- public byte[] encodePhoneFound() {
+ public byte[] encodeFindPhone(boolean start) {
return null;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
index 7681bf445..c1f687bd7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
@@ -82,6 +82,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband6.MiBand6Coordin
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband7.MiBand7Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts3.AmazfitGTS3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr3.AmazfitGTR3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr4.AmazfitGTR4Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppe.ZeppECoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr2.AmazfitGTR2eCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts.AmazfitGTSCoordinator;
@@ -293,6 +294,7 @@ public class DeviceHelper {
result.add(new MiBand7Coordinator());
result.add(new AmazfitGTS3Coordinator());
result.add(new AmazfitGTR3Coordinator());
+ result.add(new AmazfitGTR4Coordinator());
result.add(new MiBand2HRXCoordinator());
result.add(new MiBand2Coordinator()); // Note: MiBand2 and all of the above must come before MiBand because detection is hacky, atm
result.add(new MiBandCoordinator());
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java
index d426f5a56..822a11cdf 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/Prefs.java
@@ -19,9 +19,13 @@ package nodomain.freeyourgadget.gadgetbridge.util;
import android.content.SharedPreferences;
import android.util.Log;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
+import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
/**
@@ -169,10 +173,27 @@ public class Prefs {
return Arrays.asList(stringValue.split(","));
}
+ public Date getTimePreference(final String key, final String defaultValue) {
+ final String time = getString(key, defaultValue);
+
+ final DateFormat df = new SimpleDateFormat("HH:mm", Locale.ROOT);
+ try {
+ return df.parse(time);
+ } catch (final Exception e) {
+ Log.e(TAG, "Error reading datetime preference value: " + key + "; returning default current time", e); // log the first exception
+ }
+
+ return new Date();
+ }
+
private void logReadError(String key, Exception ex) {
Log.e(TAG, "Error reading preference value: " + key + "; returning default value", ex); // log the first exception
}
+ public boolean contains(final String key) {
+ return preferences.contains(key);
+ }
+
/**
* Access to the underlying SharedPreferences, typically only used for editing values.
* @return the underlying SharedPreferences object.
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
index ea9d415ee..854f93013 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/StringUtils.java
@@ -18,6 +18,7 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.util;
+import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
@@ -147,6 +148,23 @@ public class StringUtils {
return null;
}
+ @Nullable
+ public static String untilNullTerminator(final ByteBuffer buf) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ while (buf.position() < buf.limit()) {
+ final byte b = buf.get();
+
+ if (b == 0) {
+ return baos.toString();
+ }
+
+ baos.write(b);
+ }
+
+ return null;
+ }
+
public static String bytesToHex(byte[] array) {
return GB.hexdump(array, 0, -1);
}
diff --git a/app/src/main/res/drawable/ic_checklist.xml b/app/src/main/res/drawable/ic_checklist.xml
new file mode 100644
index 000000000..ad49fcec2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_checklist.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_volume_off.xml b/app/src/main/res/drawable/ic_volume_off.xml
new file mode 100644
index 000000000..4e5fae419
--- /dev/null
+++ b/app/src/main/res/drawable/ic_volume_off.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 41d682345..21bb021c3 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -146,6 +146,15 @@
- right
+
+ - @string/buttons_on_left
+ - @string/buttons_on_right
+
+
+ - buttons_on_left
+ - buttons_on_right
+
+
- @string/horizontal
- @string/vertical
@@ -156,6 +165,7 @@
+ - @string/vibration_profile_default
- @string/vibration_profile_staccato
- @string/vibration_profile_short
- @string/vibration_profile_medium
@@ -165,6 +175,8 @@
- @string/vibration_profile_alarm_clock
+
+ - @string/p_default
- @string/p_staccato
- @string/p_short
@@ -624,12 +636,208 @@
- @string/p_menuitem_music
-
+
-
-
+
+ - @string/activity_type_walking
+ - @string/activity_type_indoor_walking
+ - @string/activity_type_outdoor_running
+ - @string/activity_type_treadmill
+ - @string/activity_type_outdoor_cycling
+ - @string/activity_type_pool_swimming
+ - @string/activity_type_elliptical
+ - @string/activity_type_rowing_machine
+
+
+ - walking
+ - indoor_walking
+ - outdoor_running
+ - treadmill
+ - outdoor_cycling
+ - pool_swimming
+ - elliptical
+ - rowing_machine
+
+
+
+ - @string/accuracy
+ - @string/balanced
+ - @string/power_saving
+ - @string/custom
+
+
+ - accuracy
+ - balanced
+ - power_saving
+ - custom
+
+
+
+ - @string/single_band
+ - @string/dual_band
+
+
+ - single_band
+ - dual_band
+
+
+
+ - @string/low_power_gps
+ - @string/gps
+ - @string/gps_bds
+ - @string/gps_gnolass
+ - @string/gps_galileo
+ - @string/all_satellites
+
+
+ - low_power_gps
+ - gps
+ - gps_bds
+ - gps_gnolass
+ - gps_galileo
+ - all_satellites
+
+
+
+ - @string/speed_first
+ - @string/accuracy_first
+
+
+ - speed_first
+ - accuracy_first
+
+
+
+ - @string/sony_speak_to_chat_sensitivity_high
+ - @string/sony_speak_to_chat_sensitivity_standard
+ - @string/sony_speak_to_chat_sensitivity_low
+
+
+ - high
+ - standard
+ - low
+
+
+
+ - @string/menuitem_activity
+ - @string/menuitem_alarm
+ - @string/menuitem_alexa
+ - @string/menuitem_barometer
+ - @string/menuitem_breathing
+ - @string/menuitem_calendar
+ - @string/menuitem_compass
+ - @string/menuitem_countdown
+ - @string/menuitem_eventreminder
+ - @string/menuitem_events
+ - @string/menuitem_female_health
+ - @string/menuitem_findphone
+ - @string/menuitem_flashlight
+ - @string/menuitem_hr
+ - @string/menuitem_membership_cards
+ - @string/menuitem_music
+ - @string/menuitem_mutephone
+ - @string/menuitem_offline_voice
+ - @string/menuitem_one_tap_measuring
+ - @string/menuitem_pai
+ - @string/menuitem_personal_activity_intelligence
+ - @string/menuitem_phone
+ - @string/menuitem_pomodoro
+ - @string/menuitem_settings
+ - @string/menuitem_sleep
+ - @string/menuitem_spo2
+ - @string/menuitem_stopwatch
+ - @string/menuitem_stress
+ - @string/menuitem_sun_moon
+ - @string/menuitem_takephoto
+ - @string/menuitem_todo
+ - @string/menuitem_voice_memos
+ - @string/menuitem_weather
+ - @string/menuitem_workout_history
+ - @string/menuitem_workout
+ - @string/menuitem_workout_status
+ - @string/menuitem_worldclock
+
+ - @string/menuitem_more
+
+
+
+ - activity
+ - alarm
+ - alexa
+ - barometer
+ - breathing
+ - calendar
+ - compass
+ - countdown
+ - eventreminder
+ - events
+ - female_health
+ - findphone
+ - flashlight
+ - hr
+ - membership_cards
+ - music
+ - mutephone
+ - offline_voice
+ - one_tap_measuring
+ - pai
+ - personal_activity_intelligence
+ - phone
+ - pomodoro
+ - settings
+ - sleep
+ - spo2
+ - stopwatch
+ - stress
+ - sun_moon
+ - takephoto
+ - todo
+ - voice_memos
+ - weather
+ - workout_history
+ - workout
+ - workout_status
+ - worldclock
+
+ - more
+
+
+
+ - @string/battery
+ - @string/menuitem_dnd
+ - @string/menuitem_sleep
+ - @string/menuitem_theater_mode
+ - @string/menuitem_calendar
+ - @string/menuitem_volume
+ - @string/menuitem_screen_always_lit
+ - @string/menuitem_brightness
+ - @string/menuitem_settings
+ - @string/menuitem_flashlight
+ - @string/menuitem_bluetooth
+ - @string/menuitem_wifi
+ - @string/menuitem_lockscreen
+ - @string/menuitem_findphone
+ - @string/menuitem_eject_water
+
+
+
+ - battery
+ - dnd
+ - sleep
+ - theater_mode
+ - calendar
+ - volume
+ - screen_always_lit
+ - brightness
+ - settings
+ - flashlight
+ - bluetooth
+ - wifi
+ - lockscreen
+ - findphone
+ - eject_water
@@ -1852,7 +2060,7 @@
- 1800
-
+
- @string/seconds_5
- @string/seconds_6
- @string/seconds_7
@@ -1864,8 +2072,11 @@
- @string/seconds_13
- @string/seconds_14
- @string/seconds_15
+ - @string/seconds_20
+ - @string/seconds_25
+ - @string/seconds_30
-
+
- 5
- 6
- 7
@@ -1877,6 +2088,9 @@
- 13
- 14
- 15
+ - 20
+ - 25
+ - 30
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eaea5a14b..c9718c2e1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -58,6 +58,21 @@
Calibrate Device
Get heart rate measurement
+ Accuracy
+ Balanced
+ Power Saving
+ Custom
+ Single Band
+ Dual Band
+ Low Power GPS
+ GPS
+ GPS + BDS
+ GPS + GNOLASS
+ GPS + GALILEO
+ All Satellites
+ Speed first
+ Accuracy First
+
Activity info on device card
Choose what activity details are displayed on device card
Show Activity info on device card
@@ -134,6 +149,7 @@
You are about to install the %s firmware on your Xiaomi Smart Band 7.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!
You are about to install the %s firmware on your Amazfit GTS 3.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!
You are about to install the %s firmware on your Amazfit GTR 3.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!
+ You are about to install the %s firmware on your Amazfit GTR 4.\n\nYour band will reboot after installing the .zip file.\n\nPROCEED AT YOUR OWN RISK!
You are about to install the %s firmware on your Amazfit X.\n\nPlease make sure to install the .fw file, and after that the .res file. Your band will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nPROCEED AT YOUR OWN RISK!
You are about to install the %s firmware on your Amazfit Neo.
\n
@@ -165,6 +181,8 @@
Connection
Display
Health
+ Sound & Vibration
+ Offline Voice
Time
Workout
Equalizer
@@ -414,10 +432,38 @@
Heart rate alarm during sports activity
Low limit
High limit
+ GPS
+ GPS Mode
+ GPS Band
+ GPS Combination
+ Satellite Search
+ Crown Vibration
+ Alert Tone
+ Cover to Mute
+ Vibrate for Alert
+ Text to Speech
+ Respond when turning the wrist
+ Respond when screen on
+ Respond during screen lighting
+ AGPS
+ AGPS Expiry Reminder
+ AGPS Expiry Reminder Time
Fitness app tracking
Start/stop fitness app tracking on phone when a GPS workout is started on the band
Send GPS during workout
Send the current GPS location to the band during a workout
+ Workout Detection
+ Detect workout automatically
+ Workout Categories
+ Workouts categories to detect automatically
+ Alert
+ Notify when a workout is detected
+ Sensitivity
+ Sleep Mode
+ Sleep Screen
+ Show the Sleep Screen when waking the screen during sleep mode, to reduce distractions
+ Smart Enable
+ Enable the sleep mode automatically when wearing the band during sleep
Auto export
Auto export enabled
@@ -486,6 +532,8 @@
Right
Horizontal
Vertical
+ Buttons on left
+ Buttons on right
No valid user data given, using dummy user data for now.
When your Mi Band vibrates and blinks, tap it a few times in a row.
Install
@@ -506,7 +554,9 @@
Fetching activity data
From %1$s to %2$s
Wearing left or right?
+ Wearing direction
Vibration profile
+ Default
Staccato
Short
Medium
@@ -529,6 +579,8 @@
Inactivity notification
Low power warning
Anti-loss warning
+ Schedule
+ To-Do List
Whole day HR measurement
Event reminder
Find device
@@ -712,6 +764,7 @@
14 seconds
15 seconds
20 seconds
+ 25 seconds
30 seconds
1 minute
5 minutes
@@ -725,6 +778,8 @@
Do not ACK activity data transfer
If not ACKed to the band, activity data is not cleared. Useful if GB is used together with other apps.
Will keep activity data on the device even after synchronization. Useful if GB is used together with other apps.
+ Enable unsupported settings
+ This will enable access to all available settings, even if unsupported by the device. This can cause instability and crashes on the device.
Use low-latency mode for firmware flashing
This might help on devices where firmware flashing fails.
Allow 3rd party apps to change settings
@@ -790,6 +845,8 @@
Heart Rate Monitoring
Configure heart rate monitoring
Always On Display
+ Style follows Watchface
+ Style
Keep the band\'s display always on
Password
Lock the band with a password when removed from the wrist
@@ -806,6 +863,8 @@
Lower band screen brightness automatically at night
Shortcuts
Choose the shortcuts on the band screen
+ Control Center
+ Choose the items on the control center dropdown
Sensitivity
Screen Timeout
Workout Activity Types
@@ -863,6 +922,7 @@
Gender
Height in cm
Weight in kg
+ Target weight in kg
Step length in cm
Charts Settings
@@ -903,6 +963,8 @@
Daily target: calories burnt
Daily target: distance in meters
Daily target: active time in minutes
+ Daily target: standing time in minutes
+ Daily target: fat burn time in minutes
Store raw record in the database
Stores the data \"as is\", increasing the database usage to allow for later interpretation.
Data management
@@ -1014,10 +1076,12 @@
Running
Outdoor Running
Walking
+ Indoor Walking
Freestyle
Hiking
Climbing
Swimming
+ Pool Swimming
Swimming (Open water)
Indoor Cycling
Outdoor Cycling
@@ -1088,6 +1152,7 @@
Xiaomi Smart Band 7
Amazfit GTS 3
Amazfit GTR 3
+ Amazfit GTR 4
Amazfit Band 5
Amazfit Neo
Amazfit Bip
@@ -1208,7 +1273,24 @@
Workout History
Female Health
Workout Status
+ Calendar
+ To-Do
+ Voice Memos
+ Sun & Moon
+ One-tap Measuring
+ Offline Voice
+ Membership Cards
+ Phone
+ Theater Mode
+ Volume
+ Screen Always Lit
+ Brightness
+ Bluetooth
+ Wi-Fi
+ Lockscreen
+ Eject Water
Unknown (%s)
+ [UNSUPPORTED] %s
Minutes:
Hours:
Seconds:
@@ -1290,6 +1372,8 @@
Workout
Stopwatch
Commute
+ Upper Button long press action
+ Lower Button short press action
Upper Button short
Middle Button short
Lower Button short
@@ -1753,6 +1837,7 @@
Automatic
High
Low
+ Standard
Focus on Voice
Timeout
Off
@@ -1775,6 +1860,8 @@
Ambient Sound Control
Playback Control
Volume Control
+ Auto Brightness
+ Adjust screen brightness according to ambient light
Screen Brightness
Custom widget
Time zone
diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml
index 3a2ca0067..6bc2ec737 100644
--- a/app/src/main/res/values/values.xml
+++ b/app/src/main/res/values/values.xml
@@ -1,6 +1,7 @@
+ - default
- staccato
- short
- medium
diff --git a/app/src/main/res/xml/about_user.xml b/app/src/main/res/xml/about_user.xml
index 8a62e160f..81bfd1870 100644
--- a/app/src/main/res/xml/about_user.xml
+++ b/app/src/main/res/xml/about_user.xml
@@ -31,6 +31,12 @@
android:maxLength="3"
android:title="@string/activity_prefs_weight_kg" />
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_always_on_display.xml b/app/src/main/res/xml/devicesettings_always_on_display.xml
index cd608d685..e0fff4312 100644
--- a/app/src/main/res/xml/devicesettings_always_on_display.xml
+++ b/app/src/main/res/xml/devicesettings_always_on_display.xml
@@ -27,5 +27,18 @@
android:defaultValue="00:00"
android:key="always_on_display_end"
android:title="@string/mi2_prefs_do_not_disturb_end" />
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml b/app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml
new file mode 100644
index 000000000..56f28bd9f
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_buttonactions_lower_short.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml b/app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml
new file mode 100644
index 000000000..9287a5b50
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_buttonactions_upper_long.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml b/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml
index 63f7413d3..d27210cba 100644
--- a/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml
+++ b/app/src/main/res/xml/devicesettings_donotdisturb_withauto_and_always.xml
@@ -2,7 +2,7 @@
diff --git a/app/src/main/res/xml/devicesettings_gps_agps.xml b/app/src/main/res/xml/devicesettings_gps_agps.xml
new file mode 100644
index 000000000..1a6847a8a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_gps_agps.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_header_sound_vibration.xml b/app/src/main/res/xml/devicesettings_header_sound_vibration.xml
new file mode 100644
index 000000000..9042ce79a
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_header_sound_vibration.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml b/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml
index 70052caa1..332de9bb6 100644
--- a/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml
+++ b/app/src/main/res/xml/devicesettings_heartrate_sleep_alert_activity_stress_spo2.xml
@@ -2,7 +2,7 @@
@@ -38,6 +38,15 @@
android:title="@string/prefs_title_heartrate_measurement_interval" />
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml b/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml
index 5628fc0fb..46a4c4fa7 100644
--- a/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml
+++ b/app/src/main/res/xml/devicesettings_huami2021_displayitems.xml
@@ -1,10 +1,10 @@
@@ -10,8 +10,8 @@
+ android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+ android:title="@string/mi2_prefs_inactivity_warnings" />
@@ -13,8 +13,8 @@
+ android:summary="@string/mi2_prefs_inactivity_warnings_summary"
+ android:title="@string/mi2_prefs_inactivity_warnings" />
diff --git a/app/src/main/res/xml/devicesettings_nightmode.xml b/app/src/main/res/xml/devicesettings_nightmode.xml
index e59577e91..01b00995d 100644
--- a/app/src/main/res/xml/devicesettings_nightmode.xml
+++ b/app/src/main/res/xml/devicesettings_nightmode.xml
@@ -3,7 +3,7 @@
diff --git a/app/src/main/res/xml/devicesettings_offline_voice.xml b/app/src/main/res/xml/devicesettings_offline_voice.xml
new file mode 100644
index 000000000..3070e12c5
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_offline_voice.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_password.xml b/app/src/main/res/xml/devicesettings_password.xml
index e8bface25..1a75186cd 100644
--- a/app/src/main/res/xml/devicesettings_password.xml
+++ b/app/src/main/res/xml/devicesettings_password.xml
@@ -2,7 +2,7 @@
@@ -19,6 +19,6 @@
android:icon="@drawable/ic_password"
android:inputType="numberPassword"
android:key="pref_password"
- android:title="@string/prefs_password"/>
+ android:title="@string/prefs_password" />
diff --git a/app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml b/app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml
new file mode 100644
index 000000000..8f70a766c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_screen_brightness_withauto.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_screen_timeout_5_to_15.xml b/app/src/main/res/xml/devicesettings_screen_timeout.xml
similarity index 76%
rename from app/src/main/res/xml/devicesettings_screen_timeout_5_to_15.xml
rename to app/src/main/res/xml/devicesettings_screen_timeout.xml
index 6978f8a71..bb7f87467 100644
--- a/app/src/main/res/xml/devicesettings_screen_timeout_5_to_15.xml
+++ b/app/src/main/res/xml/devicesettings_screen_timeout.xml
@@ -2,8 +2,8 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_sound_and_vibration.xml b/app/src/main/res/xml/devicesettings_sound_and_vibration.xml
new file mode 100644
index 000000000..0b389e06c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_sound_and_vibration.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_vibrationpatterns.xml b/app/src/main/res/xml/devicesettings_vibrationpatterns.xml
index 265023836..6e244f6a5 100644
--- a/app/src/main/res/xml/devicesettings_vibrationpatterns.xml
+++ b/app/src/main/res/xml/devicesettings_vibrationpatterns.xml
@@ -19,7 +19,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_weardirection.xml b/app/src/main/res/xml/devicesettings_weardirection.xml
new file mode 100644
index 000000000..dbfe705c2
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_weardirection.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/xml/devicesettings_workout_detection.xml b/app/src/main/res/xml/devicesettings_workout_detection.xml
new file mode 100644
index 000000000..3b58eb98c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_workout_detection.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
index ca4661917..b7284f896 100644
--- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
@@ -153,7 +153,7 @@ class TestDeviceSupport extends AbstractDeviceSupport {
}
@Override
- public void onPhoneFound() {
+ public void onFindPhone(boolean start) {
}