From c39e7e38cc70111aba3605060e7c60817decdd11 Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Tue, 2 Jun 2020 16:14:56 +0200 Subject: [PATCH] Use document provider (by intent) APIs for import/export of settings to/from a file; fixes #543 --- .../app/settings/SettingsPersistence.java | 72 +++++++----- .../gui/settings/SystemSettingsActivity.java | 104 ++++++++++++++---- app/src/main/res/values/changelog.xml | 1 + app/src/main/res/values/strings.xml | 2 + 4 files changed, 129 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java b/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java index f1bb39f2..60781efa 100644 --- a/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java +++ b/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java @@ -16,11 +16,9 @@ */ package org.transdroid.core.app.settings; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Environment; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EBean; @@ -30,9 +28,13 @@ import org.json.JSONException; import org.json.JSONObject; import org.transdroid.daemon.util.HttpHelper; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.os.Environment; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; /** * Singleton class that can persist user settings (servers, RSS feeds, etc.) to and from a plain text JSON file. @@ -48,8 +50,8 @@ public class SettingsPersistence { protected SystemSettings systemSettings; public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString() - + "/Transdroid"; - public static final String DEFAULT_SETTINGS_FILENAME = "/settings.json"; + + "/Transdroid/"; + public static final String DEFAULT_SETTINGS_FILENAME = "settings.json"; public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME); /** @@ -59,9 +61,7 @@ public class SettingsPersistence { * @throws JSONException Thrown when the file did not contain valid JSON content */ public void importSettingsAsString(SharedPreferences prefs, String contents) throws JSONException { - importSettings(prefs, new JSONObject(contents)); - } /** @@ -72,14 +72,22 @@ public class SettingsPersistence { * @throws FileNotFoundException Thrown when the settings file doesn't exist or couldn't be read * @throws JSONException Thrown when the file did not contain valid JSON content */ - public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, - JSONException { - - String raw = HttpHelper.convertStreamToString(new FileInputStream(settingsFile)); - importSettings(prefs, new JSONObject(raw)); - + public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, JSONException { + importSettingsFromStream(prefs, new FileInputStream(settingsFile)); } + /** + * Synchronously reads the server, web searches, RSS feed, background service and system settings from a stream (file) in + * JSON format. + * @param prefs The application-global preferences object to write settings to + * @param settingsStream The stream to read the settings from + * @throws JSONException Thrown when the file did not contain valid JSON content + */ + public void importSettingsFromStream(SharedPreferences prefs, InputStream settingsStream) throws JSONException { + String raw = HttpHelper.convertStreamToString(settingsStream); + importSettings(prefs, new JSONObject(raw)); + } + public void importSettings(SharedPreferences prefs, JSONObject json) throws JSONException { Editor editor = prefs.edit(); @@ -227,7 +235,7 @@ public class SettingsPersistence { public String exportSettingsAsString(SharedPreferences prefs) throws JSONException { return exportSettings(prefs).toString(); } - + /** * Synchronously writes the server, web searches, RSS feed, background service and system settings to a file in JSON * format. @@ -237,20 +245,30 @@ public class SettingsPersistence { * @throws IOException Thrown when the settings file could not be created or written to */ public void exportSettingsToFile(SharedPreferences prefs, File settingsFile) throws JSONException, IOException { - - JSONObject json = exportSettings(prefs); - - // Serialise the JSON object to a file if (settingsFile.exists()) { settingsFile.delete(); } settingsFile.getParentFile().mkdirs(); settingsFile.createNewFile(); - FileWriter writer = new FileWriter(settingsFile); - writer.write(json.toString(2)); - writer.flush(); - writer.close(); + exportSettingsToStream(prefs, new FileOutputStream(settingsFile)); + } + /** + * Synchronously writes the server, web searches, RSS feed, background service and system settings to a stream (file) in JSON format. The stream + * will be closed regardless of success. + * + * @param prefs The application-global preferences object to read settings from + * @param settingsStream The stream to read the settings to + * @throws JSONException Thrown when the JSON content could not be constructed properly + * @throws IOException Thrown when the settings file could not be created or written to + */ + public void exportSettingsToStream(SharedPreferences prefs, OutputStream settingsStream) throws JSONException, IOException { + try { + JSONObject json = exportSettings(prefs); + settingsStream.write(json.toString(2).getBytes()); + } finally { + settingsStream.close(); + } } private JSONObject exportSettings(SharedPreferences prefs) throws JSONException { diff --git a/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java index 5c96b72c..ebc1e066 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java @@ -1,16 +1,16 @@ -/* +/* * Copyright 2010-2018 Eric Kok et al. - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid 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 General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . */ @@ -50,6 +50,8 @@ import org.transdroid.core.service.AppUpdateJob; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; @EActivity public class SystemSettingsActivity extends PreferenceCompatActivity { @@ -72,6 +74,9 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { return true; } }; + protected static final int ACTIVITY_IMPORT_SETTINGS = 1; + protected static final int ACTIVITY_EXPORT_SETTINGS = 2; + @Bean protected NavigationHelper navigationHelper; @Bean @@ -99,7 +104,8 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { private OnClickListener importSettingsFromFile = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if (!navigationHelper.checkSettingsReadPermission(SystemSettingsActivity.this)) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT + && !navigationHelper.checkSettingsReadPermission(SystemSettingsActivity.this)) return; // We are requesting permission to access file storage importSettingsFromFile(); } @@ -114,7 +120,8 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { private OnClickListener exportSettingsToFile = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if (!navigationHelper.checkSettingsWritePermission(SystemSettingsActivity.this)) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT + && !navigationHelper.checkSettingsWritePermission(SystemSettingsActivity.this)) return; // We are requesting permission to access file storage exportSettingsToFile(); } @@ -171,10 +178,17 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { } private void importSettingsFromFile() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); try { - settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); - SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success)); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); + settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); + SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success)); + } else { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + startActivityForResult(intent, ACTIVITY_IMPORT_SETTINGS); + } } catch (FileNotFoundException e) { SnackbarManager .show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found).colorResource(R.color.red)); @@ -184,17 +198,59 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { } } + @OnActivityResult(ACTIVITY_IMPORT_SETTINGS) + public void importSettingsFilePicked(int resultCode, Intent data) { + if (resultCode == RESULT_OK && data != null && data.getData() != null) { + try { + InputStream fis = getContentResolver().openInputStream(data.getData()); + if (fis != null) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); + settingsPersistence.importSettingsFromStream(prefs, fis); + SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success)); + } + } catch (IOException | JSONException e) { + SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found) + .colorResource(R.color.red)); + } + } + } + private void exportSettingsToFile() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); try { - settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); - SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success)); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); + settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); + SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success)); + } else { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + intent.putExtra(Intent.EXTRA_TITLE, SettingsPersistence.DEFAULT_SETTINGS_FILENAME); + startActivityForResult(intent, ACTIVITY_EXPORT_SETTINGS); + } } catch (JSONException | IOException e) { SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file) .colorResource(R.color.red)); } } + @OnActivityResult(ACTIVITY_EXPORT_SETTINGS) + public void exportSettingsFilePicked(int resultCode, Intent data) { + if (resultCode == RESULT_OK && data != null && data.getData() != null) { + try { + OutputStream fos = getContentResolver().openOutputStream(data.getData()); + if (fos != null) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); + settingsPersistence.exportSettingsToStream(prefs, fos); + SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success)); + } + } catch (IOException | JSONException e) { + SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file) + .colorResource(R.color.red)); + } + } + } + @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS) public void onQrCodeScanned(@SuppressWarnings("UnusedParameters") int resultCode, Intent data) { // We should have received Intent extras with the QR-decoded data representing Transdroid settings @@ -219,22 +275,24 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { switch (id) { case DIALOG_IMPORTSETTINGS: // @formatter:off - return new AlertDialog.Builder(this) - .setMessage( - getString( - R.string.pref_import_dialog, - getString(R.string.app_name), - SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) - .setPositiveButton(R.string.pref_import_fromfile, importSettingsFromFile) - .setNeutralButton(R.string.pref_import_fromqr, importSettingsFromQr) - .setNegativeButton(android.R.string.cancel, null).create(); - // @formatter:on + return new AlertDialog.Builder(this) + .setMessage( + getString( + Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT + ? R.string.pref_import_dialog : R.string.pref_import_dialog_android10, + getString(R.string.app_name), + SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) + .setPositiveButton(R.string.pref_import_fromfile, importSettingsFromFile) + .setNeutralButton(R.string.pref_import_fromqr, importSettingsFromQr) + .setNegativeButton(android.R.string.cancel, null).create(); + // @formatter:on case DIALOG_EXPORTSETTINGS: // @formatter:off return new AlertDialog.Builder(this) .setMessage( getString( - R.string.pref_export_dialog, + Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT + ? R.string.pref_export_dialog : R.string.pref_export_dialog_android10, getString(R.string.app_name), SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) .setPositiveButton(R.string.pref_export_tofile, exportSettingsToFile) diff --git a/app/src/main/res/values/changelog.xml b/app/src/main/res/values/changelog.xml index 7c2ea118..2e8dfc0f 100644 --- a/app/src/main/res/values/changelog.xml +++ b/app/src/main/res/values/changelog.xml @@ -19,6 +19,7 @@ Transdroid 2.5.18\n - BitComet details fixes\n +- Settings import/export on Android 10+\n \n Transdroid 2.5.17\n - qBittorrent 4.2+ support\n diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 642bf0ed..7da99ef8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -356,11 +356,13 @@ Search history is cleared Import settings %1$s will try to import server, web search, RSS and system settings from: %2$s + %1$s will try to import server, web search, RSS and system settings Use file Use QR code Settings successfully imported Export settings %1$s will export server (including passwords), web search, RSS and system settings to the following plain text JSON file: %2$s + %1$s will export server (including passwords), web search, RSS and system settings To file To QR code Settings successfully exported