Browse Source

Use document provider (by intent) APIs for import/export of settings to/from a file; fixes #543

pull/551/head
Eric Kok 5 years ago
parent
commit
c39e7e38cc
  1. 72
      app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
  2. 104
      app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java
  3. 1
      app/src/main/res/values/changelog.xml
  4. 2
      app/src/main/res/values/strings.xml

72
app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java

@ -16,11 +16,9 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 {

104
app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java

@ -1,16 +1,16 @@ @@ -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 <http://www.gnu.org/licenses/>.
*/
@ -50,6 +50,8 @@ import org.transdroid.core.service.AppUpdateJob; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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)

1
app/src/main/res/values/changelog.xml

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
<string name="system_changelog">
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

2
app/src/main/res/values/strings.xml

@ -356,11 +356,13 @@ @@ -356,11 +356,13 @@
<string name="pref_clearsearch_success">Search history is cleared</string>
<string name="pref_import">Import settings</string>
<string name="pref_import_dialog">%1$s will try to import server, web search, RSS and system settings from: %2$s</string>
<string name="pref_import_dialog_android10">%1$s will try to import server, web search, RSS and system settings</string>
<string name="pref_import_fromfile">Use file</string>
<string name="pref_import_fromqr">Use QR code</string>
<string name="pref_import_success">Settings successfully imported</string>
<string name="pref_export">Export settings</string>
<string name="pref_export_dialog">%1$s will export server (including passwords), web search, RSS and system settings to the following plain text JSON file: %2$s</string>
<string name="pref_export_dialog_android10">%1$s will export server (including passwords), web search, RSS and system settings</string>
<string name="pref_export_tofile">To file</string>
<string name="pref_export_toqr">To QR code</string>
<string name="pref_export_success">Settings successfully exported</string>

Loading…
Cancel
Save