diff --git a/res/values/strings.xml b/res/values/strings.xml index 5b2046118..0235ba076 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -169,4 +169,8 @@ Never In landscape orientation On wide screens + Backup + Import settings + Export settings + Operation failed diff --git a/res/xml/settings.xml b/res/xml/settings.xml index 63f894aaa..0dc5221af 100644 --- a/res/xml/settings.xml +++ b/res/xml/settings.xml @@ -68,4 +68,8 @@ - + + + + + \ No newline at end of file diff --git a/srcs/juloo.keyboard2/SettingsActivity.java b/srcs/juloo.keyboard2/SettingsActivity.java index dffc986dc..d0ad9c61a 100644 --- a/srcs/juloo.keyboard2/SettingsActivity.java +++ b/srcs/juloo.keyboard2/SettingsActivity.java @@ -1,29 +1,78 @@ package juloo.keyboard2; +import android.app.Activity; +import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; public class SettingsActivity extends PreferenceActivity { + // Request code for file picker + private static final int REQUEST_EXPORT = 1001; + private static final int REQUEST_IMPORT = 1002; + + private SharedPreferences sharedPreferences; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + this.sharedPreferences = getPreferenceManager().getSharedPreferences(); // The preferences can't be read when in direct-boot mode. Avoid crashing // and don't allow changing the settings. // Run the config migration on this prefs as it might be different from the // one used by the keyboard, which have been migrated. try { - Config.migrate(getPreferenceManager().getSharedPreferences()); + Config.migrate(sharedPreferences); } catch (Exception _e) { fallbackEncrypted(); return; } addPreferencesFromResource(R.xml.settings); + final Preference importDataPreference = findPreference("settings_import"); + importDataPreference.setOnPreferenceClickListener((Preference p) -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("text/*"); + this.startActivityForResult(Intent.createChooser(intent, getString(R.string.pref_settings_import)), REQUEST_IMPORT); + + return true; + }); + + final Preference exportDataPreference = findPreference("settings_export"); + exportDataPreference.setOnPreferenceClickListener((final Preference p) -> { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TITLE, "unexpected-keyboard-prefs.txt"); + this.startActivityForResult(Intent.createChooser(intent, getString(R.string.pref_settings_export)), REQUEST_EXPORT); + + return true; + }); + boolean foldableDevice = FoldStateTracker.isFoldableDevice(this); findPreference("margin_bottom_portrait_unfolded").setEnabled(foldableDevice); findPreference("margin_bottom_landscape_unfolded").setEnabled(foldableDevice); @@ -33,6 +82,25 @@ public void onCreate(Bundle savedInstanceState) findPreference("keyboard_height_landscape_unfolded").setEnabled(foldableDevice); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode != RESULT_OK || data == null) { + return; + } + + Uri uri = data.getData(); + if (uri != null) { + if (requestCode == REQUEST_IMPORT) { + importFromFile(uri); + } + if (requestCode == REQUEST_EXPORT) { + exportToFile(uri); + } + } + } + void fallbackEncrypted() { // Can't communicate with the user here. @@ -43,7 +111,78 @@ protected void onStop() { DirectBootAwarePreferences .copy_preferences_to_protected_storage(this, - getPreferenceManager().getSharedPreferences()); + sharedPreferences); super.onStop(); } + + private void exportToFile(Uri uri) { + try (OutputStream stream = this.getContentResolver().openOutputStream(uri); + OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) { + Map allPrefs = sharedPreferences.getAll(); + JSONArray json = new JSONArray(); + for (String key : allPrefs.keySet()) { + Object value = allPrefs.get(key); + if (value == null) continue; + String valueType = value.getClass().getSimpleName(); + JSONObject entry = new JSONObject(); + entry.put("key", key); + entry.put("type", valueType); + entry.put("value", value.toString()); + json.put(entry); + } + writer.write(json.toString(2)); + + } catch (Exception e) { + Log.e("Settings", "Error exporting prefs", e); + post_toast(R.string.export_import_fail); + } + } + private void importFromFile(Uri uri) { + try (InputStream inputStream = this.getContentResolver().openInputStream(uri); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + + // Clear all existing preferences + SharedPreferences.Editor editor = sharedPreferences.edit(); + + StringBuilder fileContent = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + fileContent.append(line); + } + + JSONArray json = new JSONArray(fileContent.toString()); + for (int i = 0; i < json.length(); i++) { + JSONObject entry = json.getJSONObject(i); + String key = entry.getString("key"); + String type = entry.getString("type"); + String value = entry.getString("value"); + switch (type) { + case "Integer": + editor.putInt(key, Integer.parseInt(value)); + break; + case "Float": + editor.putFloat(key, Float.parseFloat(value)); + break; + case "Boolean": + editor.putBoolean(key, Boolean.parseBoolean(value)); + break; + case "String": + editor.putString(key, value); + break; + } + } + editor.apply(); + + // Refresh UI + onCreate(null); + } catch (Exception e) { + Log.e("Settings", "Error importing prefs", e); + post_toast(R.string.export_import_fail); + } + } + + private void post_toast(int msg_id) + { + Toast.makeText(this, msg_id, Toast.LENGTH_SHORT).show(); + } }