From 96d709bea18505821a230eb71ce69044a4d3b94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 12 Dec 2021 18:38:14 +0000 Subject: [PATCH] Allow devices to update info, preferences and state --- .../DeviceSpecificSettingsFragment.java | 81 +++++++++++++++++ .../GBDeviceEventUpdateDeviceInfo.java | 32 +++++++ .../GBDeviceEventUpdateDeviceState.java | 27 ++++++ .../GBDeviceEventUpdatePreferences.java | 90 +++++++++++++++++++ .../service/AbstractDeviceSupport.java | 35 ++++++++ 5 files changed, 265 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java 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 5b43b9e97..23bfed87a 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 @@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.text.InputType; import android.widget.EditText; @@ -27,7 +28,12 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; +import androidx.preference.SeekBarPreference; +import androidx.preference.SwitchPreference; import com.mobeta.android.dslv.DragSortListPreference; import com.mobeta.android.dslv.DragSortListPreferenceFragment; @@ -204,6 +210,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp static final String FRAGMENT_TAG = "DEVICE_SPECIFIC_SETTINGS_FRAGMENT"; + private final SharedPreferencesChangeHandler sharedPreferencesChangeHandler = new SharedPreferencesChangeHandler(); + private void setSettingsFileSuffix(String settingsFileSuffix, @NonNull int[] supportedSettings, String[] supportedLanguages) { Bundle args = new Bundle(); args.putString("settingsFileSuffix", settingsFileSuffix); @@ -267,6 +275,24 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp setChangeListener(); } + @Override + public void onStart() { + super.onStart(); + + final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences(); + + reloadPreferences(sharedPreferences, getPreferenceScreen()); + + sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesChangeHandler); + } + + @Override + public void onStop() { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(sharedPreferencesChangeHandler); + + super.onStop(); + } + /* * delayed execution so that the preferences are applied first */ @@ -906,4 +932,59 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp }); } } + + /** + * Reload the preferences in the current screen. This is needed when the user enters or exists a PreferenceScreen, + * otherwise the settings won't be reloaded by the {@link SharedPreferencesChangeHandler}, as the preferences return + * null, since they're not visible. + * + * @param sharedPreferences the {@link SharedPreferences} instance + * @param preferenceGroup the {@link PreferenceGroup} for which preferences will be reloaded + */ + private void reloadPreferences(final SharedPreferences sharedPreferences, final PreferenceGroup preferenceGroup) { + for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) { + final Preference preference = preferenceGroup.getPreference(i); + + LOG.debug("Reloading {}", preference.getKey()); + + if (preference instanceof PreferenceCategory) { + reloadPreferences(sharedPreferences, (PreferenceCategory) preference); + continue; + } + + sharedPreferencesChangeHandler.onSharedPreferenceChanged(sharedPreferences, preference.getKey()); + } + } + + /** + * Handler for preference changes, update UI accordingly (if device updates the preferences). + */ + private class SharedPreferencesChangeHandler implements SharedPreferences.OnSharedPreferenceChangeListener { + @Override + public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { + LOG.debug("Preference changed: {}", key); + + final Preference preference = findPreference(key); + if (preference == null) { + LOG.warn("Preference {} not found, ignoring", key); + + return; + } + + if (preference instanceof SeekBarPreference) { + final SeekBarPreference seekBarPreference = (SeekBarPreference) preference; + seekBarPreference.setValue(prefs.getInt(key, seekBarPreference.getValue())); + } else if (preference instanceof SwitchPreference) { + final SwitchPreference switchPreference = (SwitchPreference) preference; + switchPreference.setChecked(prefs.getBoolean(key, switchPreference.isChecked())); + } else if (preference instanceof ListPreference) { + final ListPreference listPreference = (ListPreference) preference; + listPreference.setValue(prefs.getString(key, listPreference.getValue())); + } else if (preference instanceof PreferenceScreen) { + // Ignoring + } else { + LOG.warn("Unknown preference class {}, ignoring", preference.getClass()); + } + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java new file mode 100644 index 000000000..136f98704 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceInfo.java @@ -0,0 +1,32 @@ +/* Copyright (C) 2021 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.deviceevents; + +import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; +import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails; + +public class GBDeviceEventUpdateDeviceInfo extends GBDeviceEvent { + public ItemWithDetails item; + + public GBDeviceEventUpdateDeviceInfo(final ItemWithDetails item) { + this.item = item; + } + + public GBDeviceEventUpdateDeviceInfo(final String name, final String details) { + this(new GenericItem(name, details)); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java new file mode 100644 index 000000000..dbe610deb --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdateDeviceState.java @@ -0,0 +1,27 @@ +/* Copyright (C) 2021 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.deviceevents; + +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GBDeviceEventUpdateDeviceState extends GBDeviceEvent { + public GBDevice.State state; + + public GBDeviceEventUpdateDeviceState(final GBDevice.State state) { + this.state = state; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java new file mode 100644 index 000000000..a467d06ad --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventUpdatePreferences.java @@ -0,0 +1,90 @@ +/* Copyright (C) 2021 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.deviceevents; + +import android.content.SharedPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class GBDeviceEventUpdatePreferences extends GBDeviceEvent { + private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventUpdatePreferences.class); + + public final Map preferences; + + public GBDeviceEventUpdatePreferences() { + this.preferences = new HashMap<>(); + } + + public GBDeviceEventUpdatePreferences(final Map preferences) { + this.preferences = preferences; + } + + public GBDeviceEventUpdatePreferences(final String key, final Object value) { + this.preferences = new HashMap<>(); + this.preferences.put(key, value); + } + + public GBDeviceEventUpdatePreferences withPreference(final String key, final Object value) { + this.preferences.put(key, value); + + return this; + } + + public GBDeviceEventUpdatePreferences withPreferences(final Map preferences) { + this.preferences.putAll(preferences); + + return this; + } + + /** + * Update a {@link SharedPreferences} instance with the preferences in the event. + * + * @param prefs the SharedPreferences object to update. + */ + public void update(final SharedPreferences prefs) { + final SharedPreferences.Editor editor = prefs.edit(); + + for (String key : preferences.keySet()) { + final Object value = preferences.get(key); + + LOG.debug("Updating {} = {}", key, value); + + if (value instanceof Integer) { + editor.putInt(key, (Integer) value); + } else if (value instanceof Boolean) { + editor.putBoolean(key, (Boolean) value); + } else if (value instanceof String) { + editor.putString(key, (String) value); + } else if (value instanceof Float) { + editor.putFloat(key, (Float) value); + } else if (value instanceof Long) { + editor.putLong(key, (Long) value); + } else if (value instanceof Set) { + editor.putStringSet(key, (Set) value); + } else { + LOG.warn("Unknown preference value type {} for {}", value.getClass(), key); + } + } + + editor.apply(); + } +} 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 e5cc1b6ee..0848c1117 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -61,9 +61,12 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallContro import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevel; @@ -172,6 +175,12 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { handleGBDeviceEvent((GBDeviceEventFindPhone) deviceEvent); } else if (deviceEvent instanceof GBDeviceEventLEDColor) { handleGBDeviceEvent((GBDeviceEventLEDColor) deviceEvent); + } else if (deviceEvent instanceof GBDeviceEventUpdateDeviceInfo) { + handleGBDeviceEvent((GBDeviceEventUpdateDeviceInfo) deviceEvent); + } else if (deviceEvent instanceof GBDeviceEventUpdatePreferences) { + handleGBDeviceEvent((GBDeviceEventUpdatePreferences) deviceEvent); + } else if (deviceEvent instanceof GBDeviceEventUpdateDeviceState) { + handleGBDeviceEvent((GBDeviceEventUpdateDeviceState) deviceEvent); } else if (deviceEvent instanceof GBDeviceEventFmFrequency) { handleGBDeviceEvent((GBDeviceEventFmFrequency) deviceEvent); } @@ -279,6 +288,32 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { gbDevice.sendDeviceUpdateIntent(context); } + protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceInfo itemEvent) { + if (gbDevice == null) { + return; + } + + gbDevice.addDeviceInfo(itemEvent.item); + gbDevice.sendDeviceUpdateIntent(context); + } + + protected void handleGBDeviceEvent(GBDeviceEventUpdatePreferences savePreferencesEvent) { + if (gbDevice == null) { + return; + } + + savePreferencesEvent.update(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress())); + } + + protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceState updateDeviceState) { + if (gbDevice == null) { + return; + } + + gbDevice.setState(updateDeviceState.state); + gbDevice.sendDeviceUpdateIntent(getContext()); + } + protected void handleGBDeviceEvent(GBDeviceEventFmFrequency frequencyEvent) { Context context = getContext(); LOG.info("Got event for FM Frequency");