/* Copyright (C) 2024 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.util.backup; import android.content.SharedPreferences; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapterFactory; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; public class JsonBackupPreferences { private static final Gson GSON = new GsonBuilder() .registerTypeAdapterFactory(getTypeAdapterFactory()) .setPrettyPrinting() .serializeNulls() .create(); private final Map preferences; private static final String BOOLEAN = "Boolean"; private static final String FLOAT = "Float"; private static final String INTEGER = "Integer"; private static final String LONG = "Long"; private static final String STRING = "String"; private static final String HASHSET = "HashSet"; public JsonBackupPreferences(final Map preferences) { this.preferences = preferences; } public static JsonBackupPreferences fromJson(final InputStream inputStream) { return GSON.fromJson( new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonBackupPreferences.class ); } public String toJson() { return GSON.toJson(this); } /** * @noinspection BooleanMethodIsAlwaysInverted */ public boolean importInto(final SharedPreferences sharedPreferences) { final SharedPreferences.Editor editor = sharedPreferences.edit(); editor.clear(); for (final Map.Entry e : preferences.entrySet()) { e.getValue().put(editor, e.getKey()); } return editor.commit(); } public static JsonBackupPreferences exportFrom(final SharedPreferences sharedPreferences) { final Map values = new HashMap<>(); for (final Map.Entry entry : sharedPreferences.getAll().entrySet()) { final String key = entry.getKey(); final Object valueObject = entry.getValue(); // Skip this entry if the value is null; if (valueObject == null) continue; final String valueType = valueObject.getClass().getSimpleName(); if (BOOLEAN.equals(valueType)) { values.put(key, new BooleanPreferenceValue((Boolean) valueObject)); } else if (FLOAT.equals(valueType)) { values.put(key, new FloatPreferenceValue((Float) valueObject)); } else if (INTEGER.equals(valueType)) { values.put(key, new IntegerPreferenceValue((Integer) valueObject)); } else if (LONG.equals(valueType)) { values.put(key, new LongPreferenceValue((Long) valueObject)); } else if (STRING.equals(valueType)) { values.put(key, new StringPreferenceValue((String) valueObject)); } else if (HASHSET.equals(valueType)) { values.put(key, new StringSetPreferenceValue((HashSet) valueObject)); } else { throw new IllegalArgumentException("Unknown preference type " + valueType); } } return new JsonBackupPreferences(values); } public interface PreferenceValue { void put(final SharedPreferences.Editor editor, final String key); } public static class BooleanPreferenceValue implements PreferenceValue { private final boolean value; public BooleanPreferenceValue(final boolean value) { this.value = value; } @Override public void put(final SharedPreferences.Editor editor, final String key) { editor.putBoolean(key, value); } } public static class FloatPreferenceValue implements PreferenceValue { private final float value; public FloatPreferenceValue(final float value) { this.value = value; } @Override public void put(final SharedPreferences.Editor editor, final String key) { editor.putFloat(key, value); } } public static class IntegerPreferenceValue implements PreferenceValue { private final int value; public IntegerPreferenceValue(final int value) { this.value = value; } @Override public void put(final SharedPreferences.Editor editor, final String key) { editor.putInt(key, value); } } public static class LongPreferenceValue implements PreferenceValue { private final long value; public LongPreferenceValue(final long value) { this.value = value; } @Override public void put(final SharedPreferences.Editor editor, final String key) { editor.putLong(key, value); } } public static class StringPreferenceValue implements PreferenceValue { private final String value; public StringPreferenceValue(final String value) { this.value = value; } @Override public void put(final SharedPreferences.Editor editor, final String key) { editor.putString(key, value); } } public static class StringSetPreferenceValue implements PreferenceValue { private final Set value; public StringSetPreferenceValue(final Set value) { this.value = value; } @Override public void put(final SharedPreferences.Editor editor, final String key) { editor.putStringSet(key, new HashSet<>(value)); } } public static TypeAdapterFactory getTypeAdapterFactory() { return RuntimeTypeAdapterFactory .of(PreferenceValue.class, "type") .registerSubtype(BooleanPreferenceValue.class, BOOLEAN) .registerSubtype(FloatPreferenceValue.class, FLOAT) .registerSubtype(IntegerPreferenceValue.class, INTEGER) .registerSubtype(LongPreferenceValue.class, LONG) .registerSubtype(StringPreferenceValue.class, STRING) .registerSubtype(StringSetPreferenceValue.class, HASHSET) .recognizeSubtypes(); } }