From 21bfc81b357a5f4c1195a50d1287971cc1b1a5dd Mon Sep 17 00:00:00 2001 From: Johannes Krude Date: Sat, 2 Sep 2023 14:40:53 +0200 Subject: [PATCH] Casio GW-B5600: watch settings --- .../DeviceSettingsPreferenceConst.java | 6 + .../DeviceSpecificSettingsFragment.java | 4 + .../CasioGWB5600DeviceCoordinator.java | 38 ++ .../devices/casio/Casio2C2DSupport.java | 536 +++++++++++++++++- .../gbx100/CasioGBX100DeviceSupport.java | 6 + .../gwb5600/CasioGWB5600DeviceSupport.java | 18 + .../gadgetbridge/util/GBPrefs.java | 24 + app/src/main/res/drawable/ic_sync.xml | 10 + app/src/main/res/values/arrays.xml | 63 ++ app/src/main/res/values/strings.xml | 23 + app/src/main/res/values/values.xml | 3 + ...vicesettings_casio_connection_duration.xml | 11 + ...icesettings_dateformat_day_month_order.xml | 12 + .../devicesettings_hourly_chime_enable.xml | 8 + .../devicesettings_light_duration_longer.xml | 8 + .../xml/devicesettings_operating_sounds.xml | 4 +- .../res/xml/devicesettings_power_saving.xml | 8 + .../main/res/xml/devicesettings_time_sync.xml | 8 + 18 files changed, 786 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_sync.xml create mode 100644 app/src/main/res/xml/devicesettings_casio_connection_duration.xml create mode 100644 app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml create mode 100644 app/src/main/res/xml/devicesettings_hourly_chime_enable.xml create mode 100644 app/src/main/res/xml/devicesettings_light_duration_longer.xml create mode 100644 app/src/main/res/xml/devicesettings_power_saving.xml create mode 100644 app/src/main/res/xml/devicesettings_time_sync.xml 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 7c84c6f4a..276898715 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 @@ -62,6 +62,9 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_DEVICE_REGION = "device_region"; public static final String PREF_DEVICE_NAME = "pref_device_name"; public static final String PREF_DATEFORMAT = "dateformat"; + public static final String PREF_DATEFORMAT_AUTO = "auto"; + public static final String PREF_DATEFORMAT_DAY_MONTH = "day_month"; + public static final String PREF_DATEFORMAT_MONTH_DAY = "month_day"; public static final String PREF_TIMEFORMAT = "timeformat"; public static final String PREF_TIMEFORMAT_24H = "24h"; public static final String PREF_TIMEFORMAT_12H = "am/pm"; @@ -79,6 +82,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_RESERVER_REMINDERS_CALENDAR = "reserve_reminders_calendar"; public static final String PREF_ALLOW_HIGH_MTU = "allow_high_mtu"; public static final String PREF_SYNC_CALENDAR = "sync_calendar"; + public static final String PREF_TIME_SYNC = "time_sync"; public static final String PREF_USE_CUSTOM_DEVICEICON = "use_custom_deviceicon"; public static final String PREF_BUTTON_1_FUNCTION_SHORT = "button_1_function_short"; public static final String PREF_BUTTON_2_FUNCTION_SHORT = "button_2_function_short"; @@ -202,6 +206,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_AUTOHEARTRATE_END = "pref_autoheartrate_end"; public static final String PREF_POWER_MODE = "power_mode"; + public static final String PREF_CONNECTION_DURATION = "connection_duration"; public static final String PREF_BUTTON_BP_CALIBRATE = "prefs_sensors_button_bp_calibration"; public static final String PREF_ALTITUDE_CALIBRATE = "pref_sensors_altitude"; public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto"; @@ -257,6 +262,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_CASIO_ALERT_OTHER = "casio_alert_other"; public static final String PREF_CASIO_ALERT_SMS = "casio_alert_sms"; + public static final String PREF_LIGHT_DURATION_LONGER = "light_duration_longer"; public static final String PREF_AUTOREMOVE_MESSAGE = "autoremove_message"; public static final String PREF_SEND_APP_NOTIFICATIONS = "send_app_notifications"; public static final String PREF_NOTIFICATION_WAKE_ON_OPEN = "notification_wake_on_open"; 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 dd53f1f6a..5d289b8db 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 @@ -520,6 +520,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION_DOUBLE); addPreferenceHandlerFor(PREF_VIBRATION_STRENGH_PERCENTAGE); addPreferenceHandlerFor(PREF_POWER_MODE); + addPreferenceHandlerFor(PREF_POWER_SAVING); + addPreferenceHandlerFor(PREF_CONNECTION_DURATION); addPreferenceHandlerFor(PREF_LIFTWRIST_NOSHED); addPreferenceHandlerFor(PREF_DISCONNECTNOTIF_NOSHED); addPreferenceHandlerFor(PREF_BUTTON_BP_CALIBRATE); @@ -569,6 +571,7 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_FIND_PHONE); addPreferenceHandlerFor(PREF_FIND_PHONE_DURATION); addPreferenceHandlerFor(PREF_AUTOLIGHT); + addPreferenceHandlerFor(PREF_LIGHT_DURATION_LONGER); addPreferenceHandlerFor(PREF_AUTOREMOVE_MESSAGE); addPreferenceHandlerFor(PREF_AUTOREMOVE_NOTIFICATIONS); addPreferenceHandlerFor(PREF_PREVIEW_MESSAGE_IN_TITLE); @@ -596,6 +599,7 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_BATTERY_POLLING_ENABLE); addPreferenceHandlerFor(PREF_BATTERY_POLLING_INTERVAL); + addPreferenceHandlerFor(PREF_TIME_SYNC); addPreferenceHandlerFor(PREF_BLUETOOTH_CALLS_ENABLED); addPreferenceHandlerFor(PREF_DISPLAY_CALLER); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java index bcc8fa707..afc56746d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gwb5600/CasioGWB5600DeviceCoordinator.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; @@ -50,6 +51,43 @@ public class CasioGWB5600DeviceCoordinator extends CasioDeviceCoordinator { return BONDING_STYLE_BOND; } + + @Override + public int[] getSupportedDeviceSpecificSettings(GBDevice device) { + return new int[]{ + R.xml.devicesettings_timeformat, + R.xml.devicesettings_dateformat_day_month_order, + R.xml.devicesettings_operating_sounds, + R.xml.devicesettings_hourly_chime_enable, + R.xml.devicesettings_autolight, + R.xml.devicesettings_light_duration_longer, + R.xml.devicesettings_power_saving, + R.xml.devicesettings_casio_connection_duration, + R.xml.devicesettings_time_sync, + + // alarms + // timer + // reminder + // world time + }; + } + + @Override + public String[] getSupportedLanguageSettings(GBDevice device) { + return new String[]{ + "auto", + "en_US", + "es_ES", + "fr_FR", + "de_DE", + "it_IT", + "ru_RU", + }; + } + + + // all further methods are boring since they do nothing + @Override public Class getPairingActivity() { return null; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java index db0e2aa97..c5a01e278 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/Casio2C2DSupport.java @@ -19,23 +19,61 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.casio; import java.time.ZonedDateTime; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.widget.Toast; +import android.content.Intent; +import android.content.SharedPreferences; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import java.util.UUID; import java.util.Arrays; +import java.util.List; import java.util.LinkedList; +import java.util.ArrayList; import java.util.Iterator; import java.util.Set; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.HashMap; +import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.Logging; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.Logging; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT_AUTO; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT_DAY_MONTH; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT_MONTH_DAY; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_ENABLE; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIGHT_DURATION_LONGER; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_SAVING; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_CONNECTION_DURATION; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIME_SYNC; + // this class is for those Casio watches which request reads on the 2C characteristic and write on the 2D characteristic @@ -77,6 +115,12 @@ public abstract class Casio2C2DSupport extends CasioSupport { return super.connect(); } + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + initializeDeviceSettings(builder); + return builder; + } + public void writeAllFeatures(TransactionBuilder builder, byte[] arr) { if (!requests.isEmpty()) { LOG.warn("writing while waiting for a response may lead to incorrect received responses"); @@ -92,8 +136,21 @@ public abstract class Casio2C2DSupport extends CasioSupport { void handle(byte[] response); } + public class FeatureResponses extends HashMap { + public byte[][] get(FeatureRequest[] requests) { + byte[][] result = new byte[requests.length][]; + for (int i = 0; i < requests.length; i++) { + byte[] response = get(requests[i]); + if (response == null) + return null; + result[i] = response; + } + return result; + } + } + public interface ResponsesHandler { - void handle(Map responses); + void handle(FeatureResponses responses); } public static class FeatureRequest { @@ -107,6 +164,19 @@ public abstract class Casio2C2DSupport extends CasioSupport { data = new byte[] {arg0, arg1}; } + public static FeatureRequest parse(String str) { + byte[] data = RequestWithData.parseData(str); + if (data == null) + return null; + if (data.length == 1) { + return new FeatureRequest(data[0]); + } else if (data.length == 2) { + return new FeatureRequest(data[0], data[1]); + } else { + return null; + } + } + public byte[] getData() { return data.clone(); } @@ -148,6 +218,37 @@ public abstract class Casio2C2DSupport extends CasioSupport { } } + private static class RequestWithData { + public FeatureRequest request; + public byte[] data; + + public RequestWithData(FeatureRequest request, byte[] data) { + this.request = request; + this.data = data; + } + + public static RequestWithData parse(String str) { + String[] kv = str.split(";"); + if (kv.length != 2) + return null; + FeatureRequest request = FeatureRequest.parse(kv[0]); + byte[] data = parseData(kv[1]); + if (request == null || data == null) + return null; + return new RequestWithData(request, data); + } + + public static byte[] parseData(String str) { + if (!str.matches("\\A\\[[0-9]+(, [0-9]+)*\\]\\z")) + return null; + String[] strings = str.replace("[", "").replace("]", "").split(", "); + byte[] result = new byte[strings.length]; + for (int i = 0; i < result.length; i++) + result[i] = (byte) Integer.parseInt(strings[i]); + return result; + } + } + private static class RequestWithHandler { public FeatureRequest request; public ResponseHandler handler; @@ -165,7 +266,7 @@ public abstract class Casio2C2DSupport extends CasioSupport { } public void requestFeatures(TransactionBuilder builder, Set requests, ResponsesHandler handler) { - HashMap responses = new HashMap(); + FeatureResponses responses = new FeatureResponses(); HashSet missing = new HashSet(); for (FeatureRequest request: requests) { @@ -214,4 +315,435 @@ public abstract class Casio2C2DSupport extends CasioSupport { writeAllFeatures(builder, arr); } + FeatureCache featureCache = new FeatureCache(); + public class FeatureCache { + + Map values = null; + + // can not be done on initialization, since the SharedPreferences are not yet available + void load() { + values = new HashMap(); + Set serialized = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getStringSet("casio_features_current_values", new HashSet()); + if (serialized == null) + return; + + for (String str: serialized) { + if (str == null) + continue; + RequestWithData entry = RequestWithData.parse(str); + if (entry == null) { + LOG.warn("invalid casio_features_current_values entry: " + str); + continue; + } + values.put(entry.request, entry.data); + } + } + + public void save(SharedPreferences.Editor editor) { + if (values == null) + return; + + Set serialized = new HashSet(); + + for (Map.Entry entry: values.entrySet()) { + serialized.add(Arrays.toString(entry.getKey().getData()) + ";" + Arrays.toString(entry.getValue())); + } + + editor.putStringSet("casio_features_current_values", serialized); + } + + public byte[] get(FeatureRequest request) { + if (values == null) + load(); + return values.get(request); + } + + public byte[][] get(FeatureRequest[] requests) { + byte[][] result = new byte[requests.length][]; + for (int i = 0; i < requests.length; i++) { + byte[] response = get(requests[i]); + if (response == null) + return null; + result[i] = response; + } + return result; + } + + public void add(Map entries, SharedPreferences.Editor editor) { + if (values == null) + load(); + for (Map.Entry entry: entries.entrySet()) { + values.put(entry.getKey(), entry.getValue()); + } + save(editor); + } + } + + public abstract class DeviceSetting { + // which features to request + public abstract FeatureRequest[] getFeatureRequests(); + // compares and updates watch data, cached previous data and GB state, returns true if data was changed + public abstract boolean mergeValues(byte[][] data, byte[][] previous, SharedPreferences.Editor editor); + }; + + ArrayList deviceSettings = new ArrayList(); + HashMap devicePreferenceByName = new HashMap(); + { + for (DevicePreference pref: supportedDevicePreferences()) { + deviceSettings.add(pref); + devicePreferenceByName.put(pref.getName(), pref); + } + } + + void initializeDeviceSettings(TransactionBuilder builder) { + Set deviceSettingFeatures = new LinkedHashSet(); + for (DeviceSetting ds: deviceSettings) + deviceSettingFeatures.addAll(Arrays.asList(ds.getFeatureRequests())); + + requestFeatures(builder, deviceSettingFeatures, responses -> { + LinkedHashSet override = new LinkedHashSet(); + + SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit(); + for (DeviceSetting ds: deviceSettings) { + FeatureRequest[] requests = ds.getFeatureRequests(); + byte[][] data = responses.get(requests); + if (data == null) + continue; + byte[][] previous = featureCache.get(requests); + if (ds.mergeValues(data, previous, editor)) { + override.addAll(Arrays.asList(requests)); + } + } + + featureCache.add(responses, editor); + editor.apply(); + + if (!override.isEmpty()) { + ArrayList updatedSettings = new ArrayList(); + for (FeatureRequest fr: override) { + updatedSettings.add(responses.get(fr)); + } + sendSettingsToDevice(updatedSettings.toArray(new byte[][] {})); + } + }); + } + + void sendSettingsToDevice(byte[][] settings) { + TransactionBuilder builder = createTransactionBuilder("DeviceSetting.write"); + for (byte[] data: settings) { + writeAllFeatures(builder, data); + } + builder.run((gatt) -> GB.toast(getContext(), getContext().getString(R.string.user_feedback_set_settings_ok), Toast.LENGTH_SHORT, GB.INFO)); + builder.queue(getQueue()); + } + + public abstract class DevicePreference extends DeviceSetting { + byte feature; + public final FeatureRequest getFeatureRequest() { + return new FeatureRequest(feature); + }; + + @Override + public final FeatureRequest[] getFeatureRequests() { + return new FeatureRequest[] {getFeatureRequest()}; + }; + + String name; + public final String getName() { + return name; + } + + public abstract void updateValue(byte[] data); + + @Override + public final boolean mergeValues(byte[][] data, byte[][] previous, SharedPreferences.Editor editor) { + boolean needsUpdating = false; + // check if GB state has changed + if (previous != null) { + byte[] copy = previous[0].clone(); + updateValue(copy); + if (!Arrays.equals(previous[0], copy)) { + needsUpdating = true; + } + } + // update GB state and check if data needs change + if (!needsUpdating) { + needsUpdating = readValue(data[0], editor); + } + // maybe update data + if (needsUpdating) { + updateValue(data[0]); + return true; + } + return false; + } + + public abstract boolean readValue(byte[] data, SharedPreferences.Editor editor); + + protected Prefs getPrefs() { + return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); + } + + protected GBPrefs getGBPrefs() { + return new GBPrefs(getPrefs()); + } + }; + + public abstract Casio2C2DSupport.DevicePreference[] supportedDevicePreferences(); + + @Override + public void onSendConfiguration(String config) { + DevicePreference pref = devicePreferenceByName.get(config); + if (pref == null) { + LOG.warn("received configuration change for unsupported setting " + config); + return; + } + if (!isInitialized()) { + return; + } + byte[] currentValue = featureCache.get(pref.getFeatureRequest()); + if (currentValue == null) { + LOG.error("unknown current watch value for config " + config); + return; + } + pref.updateValue(currentValue); + + SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit(); + featureCache.save(editor); + editor.apply(); + + sendSettingsToDevice(new byte[][] {currentValue}); + } + + public class UnsignedByteDevicePreference extends DevicePreference { + int index; + + @Override + public void updateValue(byte[] data) { + data[index] = (byte) getGBValue(); + } + + @Override + public boolean readValue(byte[] data, SharedPreferences.Editor editor) { + return setGBValue(editor, data[index] & 0xff); + } + + public int getGBValue() { + return getPrefs().getInt(name, -1); + } + + public boolean setGBValue(SharedPreferences.Editor editor, int value) { + if (value != getGBValue()) { + editor.putString(name, Integer.toString(value)); + } + return false; + } + } + + public class BoolDevicePreference extends DevicePreference { + int index; + byte mask; + + @Override + public void updateValue(byte[] data) { + if (getGBValue()) { + data[index] &= ~mask; + } else { + data[index] |= mask; + } + } + + @Override + public boolean readValue(byte[] data, SharedPreferences.Editor editor) { + if ((data[index] & mask) == 0) { + return setGBValue(editor, true); + } else { + return setGBValue(editor, false); + } + } + + public boolean getGBValue() { + return getPrefs().getBoolean(name, false); + } + + public boolean setGBValue(SharedPreferences.Editor editor, boolean value) { + if (value != getGBValue()) { + editor.putBoolean(name, value); + } + return false; + } + } + + public class InvertedBoolDevicePreference extends BoolDevicePreference { + @Override + public void updateValue(byte[] data) { + if (getGBValue()) { + data[index] |= mask; + } else { + data[index] &= ~mask; + } + } + + @Override + public boolean readValue(byte[] data, SharedPreferences.Editor editor) { + if ((data[index] & mask) == 0) { + return setGBValue(editor, false); + } else { + return setGBValue(editor, true); + } + } + } + + interface AutoGetter { + public String get(GBPrefs gbPrefs); + } + + public class AutoBoolDevicePreference extends BoolDevicePreference { + AutoGetter getter; + + String autoValue; + String trueValue; + String falseValue; + + @Override + public boolean getGBValue() { + return getter.get(getGBPrefs()).equals(trueValue); + } + + @Override + public boolean setGBValue(SharedPreferences.Editor editor, boolean value) { + String strValue = value ? trueValue : falseValue; + if (!getter.get(getGBPrefs()).equals(strValue)) { + if (getPrefs().getString(name, autoValue).equals(autoValue)) { + return true; + } else { + editor.putString(name, strValue); + } + } + return false; + } + } + + public class TimeSyncPreference extends BoolDevicePreference { + { name = PREF_TIME_SYNC; } + { feature = FEATURE_SETTING_FOR_BLE; } + { index = 12; mask = (byte) (0x80 & 0xff); } + } + + public class ConnectionDurationPreference extends UnsignedByteDevicePreference { + { name = PREF_CONNECTION_DURATION; } + { feature = FEATURE_SETTING_FOR_BLE; } + { index = 14; } + + @Override + public int getGBValue() { + return getPrefs().getInt(name, -1); + } + + @Override + public boolean setGBValue(SharedPreferences.Editor editor, int value) { + if (value != getGBValue()) { + editor.putString(name, Integer.toString(value)); + } + return false; + } + } + + public class TimeFormatPreference extends AutoBoolDevicePreference { + { name = PREF_TIMEFORMAT; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 1; mask = 0x01; } + { getter = gbPrefs -> gbPrefs.getTimeFormat(); } + { autoValue = PREF_TIMEFORMAT_AUTO; } + { trueValue = PREF_TIMEFORMAT_12H; } + { falseValue = PREF_TIMEFORMAT_24H; } + } + + public class OperatingSoundPreference extends BoolDevicePreference { + { name = PREF_OPERATING_SOUNDS; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 1; mask = 0x02; } + } + + public class AutoLightPreference extends BoolDevicePreference { + { name = PREF_AUTOLIGHT; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 1; mask = 0x04; } + } + + public class PowerSavingPreference extends BoolDevicePreference { + { name = PREF_POWER_SAVING; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 1; mask = 0x10; } + } + + public class LongerLightDurationPreference extends InvertedBoolDevicePreference { + { name = PREF_LIGHT_DURATION_LONGER; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 2; mask = 0x01; } + } + + public class DayMonthOrderPreference extends AutoBoolDevicePreference { + { name = PREF_DATEFORMAT; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 4; mask = 0x01; } + { getter = gbPrefs -> gbPrefs.getDateFormatDayMonthOrder(); } + { autoValue = PREF_DATEFORMAT_AUTO; } + { trueValue = PREF_DATEFORMAT_MONTH_DAY; } + { falseValue = PREF_DATEFORMAT_DAY_MONTH; } + } + + public class LanguagePreference extends UnsignedByteDevicePreference { + { name = PREF_LANGUAGE; } + { feature = FEATURE_SETTING_FOR_BASIC; } + { index = 5; } + + String[] languages = { "en_US", "es_ES", "fr_FR"," de_DE", "it_IT", "ru_RU" }; + + @Override + public int getGBValue() { + String value = getPrefs().getString(name, PREF_LANGUAGE_AUTO); + int number = 0; + if (value.equals(PREF_LANGUAGE_AUTO)) { + String lang = Locale.getDefault().getLanguage() + "_"; + for (int i=0; i < languages.length; i++) { + if (languages[i].startsWith(lang)) { + number = i; + break; + } + } + } else { + for (int i=0; i < languages.length; i++) { + if (value.equals(languages[i])) { + number = i; + break; + } + } + } + return number; + } + + @Override + public boolean setGBValue(SharedPreferences.Editor editor, int value) { + if (getGBValue() != value) { + if (getPrefs().getString(name, PREF_LANGUAGE_AUTO).equals(PREF_LANGUAGE_AUTO)) { + return true; + } else { + if (value < languages.length) { + editor.putString(name, languages[value]); + } else { + editor.putString(name, "unknown"); + } + } + } + return false; + } + } + + public class HourlyChimePreference extends InvertedBoolDevicePreference { + { name = PREF_HOURLY_CHIME_ENABLE; } + { feature = FEATURE_SETTING_FOR_ALM; } + { index = 1; mask = (byte) (0x80 & 0xff); } + } + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java index 1521067e0..ab3ddb21b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java @@ -94,6 +94,12 @@ public class CasioGBX100DeviceSupport extends Casio2C2DSupport implements Shared super(LOG); } + @Override + public DeviceSetting[] supportedDeviceSettings() { + return new DeviceSetting[] { + }; + } + @Override public boolean useAutoConnect() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java index 6b0225b24..c5b20fc3c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gwb5600/CasioGWB5600DeviceSupport.java @@ -49,6 +49,22 @@ public class CasioGWB5600DeviceSupport extends Casio2C2DSupport { super(LOG); } + @Override + public DeviceSetting[] supportedDeviceSettings() { + return new DeviceSetting[] { + new LanguageSetting(), + new TimeFormatSetting(), + new DayMonthOrderSetting(), + new OperatingSoundSetting(), + new HourlyChimeSetting(), + new AutoLightSetting(), + new LongerLightDurationSetting(), + new PowerSavingSetting(), + new ConnectionDurationSetting(), + new TimeSyncSetting(), + }; + } + @Override public boolean useAutoConnect() { return false; @@ -75,6 +91,8 @@ public class CasioGWB5600DeviceSupport extends Casio2C2DSupport { builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + super.initializeDevice(builder); + // which button was pressed? requestFeature(builder, new FeatureRequest(FEATURE_BLE_FEATURES), data -> { if (data.length > 8 && data[8] == CasioConstants.CONNECT_FIND) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java index 402902f80..2047b6efa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java @@ -33,6 +33,7 @@ import androidx.core.app.ActivityCompat; import java.text.ParseException; import java.time.LocalTime; import java.util.Date; +import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; @@ -133,6 +134,29 @@ public class GBPrefs extends Prefs { return timeFormat; } + public String getDateFormatDayMonthOrder() { + String dateFormat = getString(DeviceSettingsPreferenceConst.PREF_DATEFORMAT, DeviceSettingsPreferenceConst.PREF_DATEFORMAT_AUTO); + if (DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO.equals(dateFormat)) { + String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dM"); + boolean quoted = false; + for (char c: pattern.toCharArray()) { + if (c == '\'') { + quoted = !quoted; + continue; + } + if (quoted) + continue; + if (c == 'd') + return DeviceSettingsPreferenceConst.PREF_DATEFORMAT_DAY_MONTH; + if (c == 'M' || c == 'L') + return DeviceSettingsPreferenceConst.PREF_DATEFORMAT_MONTH_DAY; + } + return DeviceSettingsPreferenceConst.PREF_DATEFORMAT_DAY_MONTH; + } + + return dateFormat; + } + public float[] getLongLat(Context context) { float latitude = getFloat("location_latitude", 0); float longitude = getFloat("location_longitude", 0); diff --git a/app/src/main/res/drawable/ic_sync.xml b/app/src/main/res/drawable/ic_sync.xml new file mode 100644 index 000000000..ffce01ffa --- /dev/null +++ b/app/src/main/res/drawable/ic_sync.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 9b12a6071..8eeacba73 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -283,6 +283,17 @@ MM/DD/YYYY + + @string/automatic + @string/dateformat_day_month + @string/dateformat_month_day + + + @string/p_dateformat_auto + @string/p_dateformat_day_month + @string/p_dateformat_month_day + + @string/mi2_dnd_off @string/mi2_dnd_scheduled @@ -2716,6 +2727,58 @@ 3600 + + @string/minutes_1 + @string/minutes_2 + @string/minutes_3 + @string/minutes_4 + @string/minutes_5 + @string/minutes_6 + @string/minutes_7 + @string/minutes_8 + @string/minutes_9 + @string/minutes_10 + @string/minutes_15 + @string/minutes_20 + @string/minutes_30 + @string/minutes_45 + @string/minutes_60 + @string/minutes_75 + @string/minutes_90 + @string/minutes_120 + @string/minutes_150 + @string/minutes_180 + @string/minutes_210 + @string/minutes_240 + @string/minutes_255 + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 15 + 20 + 30 + 45 + 60 + 75 + 90 + 120 + 150 + 180 + 210 + 240 + 255 + + @string/no_limit @string/seconds_5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5dfe12cc7..392a72edd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -340,6 +340,7 @@ Send sunrise and sunset times based on the location to the Pebble timeline Sync calendar Send calendar events to the timeline + Automatic time sync Show device specific notification icon Show a device specific Android notification icon instead the Gadgetbridge icon when connected Show a preview of the message in the title @@ -803,6 +804,7 @@ Snooze There was an error setting the alarms, please try again. Alarms sent to device. + Settings sent to device. No data. Synchronize device? No activities detected. Do some activity and synchronize device. @@ -924,11 +926,28 @@ 25 seconds 30 seconds 1 minute + 2 minutes + 3 minutes + 4 minutes 5 minutes + 6 minutes + 7 minutes + 8 minutes + 9 minutes 10 minutes + 15 minutes 20 minutes 30 minutes + 45 minutes 60 minutes + 75 minutes + 90 minutes + 120 minutes + 150 minutes + 180 minutes + 210 minutes + 240 minutes + 255 minutes Live activity Steps today, target: %1$s Lack of steps: %1$d @@ -1248,6 +1267,8 @@ Imperial 24H AM/PM + Day, Month + Month, Day Alarm clock Web View Activity (%1$s) @@ -2245,6 +2266,7 @@ %d hours Automatic light + Longer light duration Key Vibration Operating Sounds Fake continuous ringing @@ -2821,6 +2843,7 @@ Enable logs from watch apps Start logging from watch apps Stop logging from watch apps + App connection duration Title Description Preview image diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index fe23a88b7..dbc8f1333 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -10,8 +10,11 @@ ring alarm_clock + auto dateformat_time dateformat_datetime + day_month + month_day clock steps diff --git a/app/src/main/res/xml/devicesettings_casio_connection_duration.xml b/app/src/main/res/xml/devicesettings_casio_connection_duration.xml new file mode 100644 index 000000000..f72ff893e --- /dev/null +++ b/app/src/main/res/xml/devicesettings_casio_connection_duration.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml b/app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml new file mode 100644 index 000000000..4b10ca3e9 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_dateformat_day_month_order.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/xml/devicesettings_hourly_chime_enable.xml b/app/src/main/res/xml/devicesettings_hourly_chime_enable.xml new file mode 100644 index 000000000..2cdf6ef4d --- /dev/null +++ b/app/src/main/res/xml/devicesettings_hourly_chime_enable.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_light_duration_longer.xml b/app/src/main/res/xml/devicesettings_light_duration_longer.xml new file mode 100644 index 000000000..ff15a0cfe --- /dev/null +++ b/app/src/main/res/xml/devicesettings_light_duration_longer.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_operating_sounds.xml b/app/src/main/res/xml/devicesettings_operating_sounds.xml index 046a98611..cd6bfea7f 100644 --- a/app/src/main/res/xml/devicesettings_operating_sounds.xml +++ b/app/src/main/res/xml/devicesettings_operating_sounds.xml @@ -2,8 +2,8 @@ - \ No newline at end of file + diff --git a/app/src/main/res/xml/devicesettings_power_saving.xml b/app/src/main/res/xml/devicesettings_power_saving.xml new file mode 100644 index 000000000..8f8678974 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_power_saving.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_time_sync.xml b/app/src/main/res/xml/devicesettings_time_sync.xml new file mode 100644 index 000000000..8a3a64aac --- /dev/null +++ b/app/src/main/res/xml/devicesettings_time_sync.xml @@ -0,0 +1,8 @@ + + + +