Browse Source

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

pull/551/head
Eric Kok 4 years ago
parent
commit
c39e7e38cc
  1. 70
      app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
  2. 96
      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

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

@ -16,11 +16,9 @@
*/ */
package org.transdroid.core.app.settings; package org.transdroid.core.app.settings;
import java.io.File; import android.content.SharedPreferences;
import java.io.FileInputStream; import android.content.SharedPreferences.Editor;
import java.io.FileNotFoundException; import android.os.Environment;
import java.io.FileWriter;
import java.io.IOException;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
@ -30,9 +28,13 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import android.content.SharedPreferences; import java.io.File;
import android.content.SharedPreferences.Editor; import java.io.FileInputStream;
import android.os.Environment; 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. * 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; protected SystemSettings systemSettings;
public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString() public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString()
+ "/Transdroid"; + "/Transdroid/";
public static final String DEFAULT_SETTINGS_FILENAME = "/settings.json"; public static final String DEFAULT_SETTINGS_FILENAME = "settings.json";
public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME); 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 * @throws JSONException Thrown when the file did not contain valid JSON content
*/ */
public void importSettingsAsString(SharedPreferences prefs, String contents) throws JSONException { public void importSettingsAsString(SharedPreferences prefs, String contents) throws JSONException {
importSettings(prefs, new JSONObject(contents)); 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 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 * @throws JSONException Thrown when the file did not contain valid JSON content
*/ */
public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, JSONException {
JSONException { importSettingsFromStream(prefs, new FileInputStream(settingsFile));
String raw = HttpHelper.convertStreamToString(new FileInputStream(settingsFile));
importSettings(prefs, new JSONObject(raw));
} }
/**
* 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 { public void importSettings(SharedPreferences prefs, JSONObject json) throws JSONException {
Editor editor = prefs.edit(); Editor editor = prefs.edit();
@ -237,20 +245,30 @@ public class SettingsPersistence {
* @throws IOException Thrown when the settings file could not be created or written to * @throws IOException Thrown when the settings file could not be created or written to
*/ */
public void exportSettingsToFile(SharedPreferences prefs, File settingsFile) throws JSONException, IOException { public void exportSettingsToFile(SharedPreferences prefs, File settingsFile) throws JSONException, IOException {
JSONObject json = exportSettings(prefs);
// Serialise the JSON object to a file
if (settingsFile.exists()) { if (settingsFile.exists()) {
settingsFile.delete(); settingsFile.delete();
} }
settingsFile.getParentFile().mkdirs(); settingsFile.getParentFile().mkdirs();
settingsFile.createNewFile(); settingsFile.createNewFile();
FileWriter writer = new FileWriter(settingsFile); exportSettingsToStream(prefs, new FileOutputStream(settingsFile));
writer.write(json.toString(2)); }
writer.flush();
writer.close();
/**
* 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 { private JSONObject exportSettings(SharedPreferences prefs) throws JSONException {

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

@ -50,6 +50,8 @@ import org.transdroid.core.service.AppUpdateJob;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@EActivity @EActivity
public class SystemSettingsActivity extends PreferenceCompatActivity { public class SystemSettingsActivity extends PreferenceCompatActivity {
@ -72,6 +74,9 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
return true; return true;
} }
}; };
protected static final int ACTIVITY_IMPORT_SETTINGS = 1;
protected static final int ACTIVITY_EXPORT_SETTINGS = 2;
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@Bean @Bean
@ -99,7 +104,8 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
private OnClickListener importSettingsFromFile = new OnClickListener() { private OnClickListener importSettingsFromFile = new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { 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 return; // We are requesting permission to access file storage
importSettingsFromFile(); importSettingsFromFile();
} }
@ -114,7 +120,8 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
private OnClickListener exportSettingsToFile = new OnClickListener() { private OnClickListener exportSettingsToFile = new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { 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 return; // We are requesting permission to access file storage
exportSettingsToFile(); exportSettingsToFile();
} }
@ -171,10 +178,17 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
} }
private void importSettingsFromFile() { private void importSettingsFromFile() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try { try {
settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success)); 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) { } catch (FileNotFoundException e) {
SnackbarManager SnackbarManager
.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found).colorResource(R.color.red)); .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() { private void exportSettingsToFile() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try { try {
settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success)); 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) { } catch (JSONException | IOException e) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file) SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file)
.colorResource(R.color.red)); .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) @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS)
public void onQrCodeScanned(@SuppressWarnings("UnusedParameters") int resultCode, Intent data) { public void onQrCodeScanned(@SuppressWarnings("UnusedParameters") int resultCode, Intent data) {
// We should have received Intent extras with the QR-decoded data representing Transdroid settings // 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) { switch (id) {
case DIALOG_IMPORTSETTINGS: case DIALOG_IMPORTSETTINGS:
// @formatter:off // @formatter:off
return new AlertDialog.Builder(this) return new AlertDialog.Builder(this)
.setMessage( .setMessage(
getString( getString(
R.string.pref_import_dialog, Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT
getString(R.string.app_name), ? R.string.pref_import_dialog : R.string.pref_import_dialog_android10,
SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) getString(R.string.app_name),
.setPositiveButton(R.string.pref_import_fromfile, importSettingsFromFile) SettingsPersistence.DEFAULT_SETTINGS_FILE.toString()))
.setNeutralButton(R.string.pref_import_fromqr, importSettingsFromQr) .setPositiveButton(R.string.pref_import_fromfile, importSettingsFromFile)
.setNegativeButton(android.R.string.cancel, null).create(); .setNeutralButton(R.string.pref_import_fromqr, importSettingsFromQr)
// @formatter:on .setNegativeButton(android.R.string.cancel, null).create();
// @formatter:on
case DIALOG_EXPORTSETTINGS: case DIALOG_EXPORTSETTINGS:
// @formatter:off // @formatter:off
return new AlertDialog.Builder(this) return new AlertDialog.Builder(this)
.setMessage( .setMessage(
getString( 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), getString(R.string.app_name),
SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) SettingsPersistence.DEFAULT_SETTINGS_FILE.toString()))
.setPositiveButton(R.string.pref_export_tofile, exportSettingsToFile) .setPositiveButton(R.string.pref_export_tofile, exportSettingsToFile)

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

@ -19,6 +19,7 @@
<string name="system_changelog"> <string name="system_changelog">
Transdroid 2.5.18\n Transdroid 2.5.18\n
- BitComet details fixes\n - BitComet details fixes\n
- Settings import/export on Android 10+\n
\n \n
Transdroid 2.5.17\n Transdroid 2.5.17\n
- qBittorrent 4.2+ support\n - qBittorrent 4.2+ support\n

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

@ -356,11 +356,13 @@
<string name="pref_clearsearch_success">Search history is cleared</string> <string name="pref_clearsearch_success">Search history is cleared</string>
<string name="pref_import">Import settings</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">%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_fromfile">Use file</string>
<string name="pref_import_fromqr">Use QR code</string> <string name="pref_import_fromqr">Use QR code</string>
<string name="pref_import_success">Settings successfully imported</string> <string name="pref_import_success">Settings successfully imported</string>
<string name="pref_export">Export settings</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">%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_tofile">To file</string>
<string name="pref_export_toqr">To QR code</string> <string name="pref_export_toqr">To QR code</string>
<string name="pref_export_success">Settings successfully exported</string> <string name="pref_export_success">Settings successfully exported</string>

Loading…
Cancel
Save