From 219b2b71bcd02b1c745e01e03257ca2c2ab248c4 Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Tue, 18 Nov 2014 10:36:47 +0100 Subject: [PATCH] Added support for importing/exporting settings via QR code. Fixes #178. --- .../app/settings/ApplicationSettings.java | 30 +-- .../app/settings/SettingsPersistence.java | 95 +++++-- .../transdroid/core/gui/TorrentsActivity.java | 4 +- .../core/gui/search/BarcodeHelper.java | 66 +++-- .../gui/settings/SystemSettingsActivity.java | 236 ++++++++++-------- app/src/main/res/values/changelog.xml | 4 +- app/src/main/res/values/strings.xml | 4 + 7 files changed, 281 insertions(+), 158 deletions(-) diff --git a/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java b/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java index 3369b0ef..830539d2 100644 --- a/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java +++ b/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java @@ -258,7 +258,7 @@ public class ApplicationSettings { edit.putInt("header_defaultserver", --order); } - edit.commit(); + edit.apply(); } @@ -352,7 +352,7 @@ public class ApplicationSettings { * @param order The key identifying the specific server */ public void setLastUsedServerKey(int order) { - prefs.edit().putInt("system_lastusedserver", order).commit(); + prefs.edit().putInt("system_lastusedserver", order).apply(); } /** @@ -367,10 +367,10 @@ public class ApplicationSettings { /** * Registers some navigation filter as being the last used by the user - * @param server The navigation filter that the user last used in the interface + * @param filter The navigation filter that the user last used in the interface */ public void setLastUsedNavigationFilter(NavigationFilter filter) { - prefs.edit().putString("system_lastusedfilter", filter.getCode()).commit(); + prefs.edit().putString("system_lastusedfilter", filter.getCode()).apply(); } /** @@ -432,7 +432,7 @@ public class ApplicationSettings { edit.remove("websearch_name_" + max); edit.remove("websearch_baseurl_" + max); edit.remove("websearch_cookies_" + max); - edit.commit(); + edit.apply(); } @@ -503,7 +503,7 @@ public class ApplicationSettings { edit.remove("rssfeed_reqauth_" + max); edit.remove("rssfeed_alarmnew_" + max); edit.remove("rssfeed_lastviewed_" + max); - edit.commit(); + edit.apply(); } @@ -522,7 +522,7 @@ public class ApplicationSettings { Editor edit = prefs.edit(); edit.putLong("rssfeed_lastviewed_" + order, lastViewed.getTime()); edit.putString("rssfeed_lastvieweditemurl_" + order, lastViewedItemUrl); - edit.commit(); + edit.apply(); } /** @@ -531,8 +531,10 @@ public class ApplicationSettings { * @param currentSortAscending The sort order direction that was last used */ public void setLastUsedSortOrder(TorrentsSortBy currentSortOrder, boolean currentSortAscending) { - prefs.edit().putInt("system_lastusedsortorder", currentSortOrder.getCode()).commit(); - prefs.edit().putBoolean("system_lastusedsortdirection", currentSortAscending).commit(); + Editor edit = prefs.edit(); + edit.putInt("system_lastusedsortorder", currentSortOrder.getCode()); + edit.putBoolean("system_lastusedsortdirection", currentSortAscending); + edit.apply(); } /** @@ -631,10 +633,10 @@ public class ApplicationSettings { /** * Registers the unique key of some web search or in-app search site as being last used by the user - * @param order The key identifying the specific server + * @param site The site settings to register as being last used */ public void setLastUsedSearchSite(SearchSetting site) { - prefs.edit().putString("header_setsearchsite", site.getKey()).commit(); + prefs.edit().putString("header_setsearchsite", site.getKey()).apply(); } /** @@ -660,7 +662,7 @@ public class ApplicationSettings { * @param lastStats A JSON array of JSON objects that each represent a single seen torrent */ public void setServerLastStats(ServerSetting server, JSONArray lastStats) { - prefs.edit().putString(server.getUniqueIdentifier(), lastStats.toString()).commit(); + prefs.edit().putString(server.getUniqueIdentifier(), lastStats.toString()).apply(); } /** @@ -699,7 +701,7 @@ public class ApplicationSettings { edit.putBoolean("widget_reverse_" + appWidgetId, settings.shouldReserveSort()); edit.putBoolean("widget_showstatus_" + appWidgetId, settings.shouldShowStatusView()); edit.putBoolean("widget_darktheme_" + appWidgetId, settings.shouldUseDarkTheme()); - edit.commit(); + edit.apply(); } /** @@ -714,7 +716,7 @@ public class ApplicationSettings { edit.remove("widget_reverse_" + appWidgetId); edit.remove("widget_showstatus_" + appWidgetId); edit.remove("widget_darktheme_" + appWidgetId); - edit.commit(); + edit.apply(); } } 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 a57b1f72..cdbe0956 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 @@ -52,21 +52,37 @@ public class SettingsPersistence { public static final String DEFAULT_SETTINGS_FILENAME = "/settings.json"; public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME); + /** + * Reads the server, web searches, RSS feed, background service and system settings from a JSON-encoded String, such as when read via a QR code. + * @param prefs The application-global preferences object to write settings to + * @param contents The JSON-encoded settings as raw String + * @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)); + + } + /** * Synchronously reads the server, web searches, RSS feed, background service and system settings from a file in * JSON format. - * @param settingsFile The local file to write the settings to - * @throws FileNotFoundException Thrown when the settings file doesn't exist or couln't be read + * @param prefs The application-global preferences object to write settings to + * @param settingsFile The local file to read the settings from + * @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 importSettings(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, + public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, JSONException { - Editor editor = prefs.edit(); - - // Read the settings file String raw = HttpHelper.convertStreamToString(new FileInputStream(settingsFile)); - JSONObject json = new JSONObject(raw); + importSettings(prefs, new JSONObject(raw)); + + } + + public void importSettings(SharedPreferences prefs, JSONObject json) throws JSONException { + + Editor editor = prefs.edit(); // Import servers if (json.has("servers")) { @@ -96,7 +112,7 @@ public class SettingsPersistence { if (server.has("folder")) editor.putString("server_folder_" + postfix, server.getString("folder")); if (server.has("use_auth")) - editor.putBoolean("server_useauth_" + postfix, server.getBoolean("use_auth")); + editor.putBoolean("server_disableauth_" + postfix, !server.getBoolean("use_auth")); if (server.has("username")) editor.putString("server_user_" + postfix, server.getString("username")); if (server.has("password")) @@ -132,6 +148,8 @@ public class SettingsPersistence { editor.putString("websearch_name_" + postfix, site.getString("name")); if (site.has("url")) editor.putString("websearch_baseurl_" + postfix, site.getString("url")); + if (site.has("cookies")) + editor.putString("websearch_cookies_" + postfix, site.getString("cookies")); } } @@ -149,6 +167,8 @@ public class SettingsPersistence { editor.putString("rssfeed_url_" + postfix, feed.getString("url")); if (feed.has("needs_auth")) editor.putBoolean("rssfeed_reqauth_" + postfix, feed.getBoolean("needs_auth")); + if (feed.has("new_item_alarm")) + editor.putBoolean("rssfeed_alarmnew_" + postfix, feed.getBoolean("new_item_alarm")); if (feed.has("last_seen")) editor.putString("rssfeed_lastnew_" + postfix, feed.getString("last_seen")); @@ -168,24 +188,54 @@ public class SettingsPersistence { editor.putInt("notifications_ledcolour", json.getInt("alarm_ledcolour")); if (json.has("alarm_adwnotifications")) editor.putBoolean("notifications_adwnotify", json.getBoolean("alarm_adwnotifications")); + if (json.has("system_dormantasinactive")) + editor.putBoolean("system_dormantasinactive", json.getBoolean("system_dormantasinactive")); + if (json.has("system_autorefresh")) + editor.putString("system_autorefresh", json.getString("system_autorefresh")); if (json.has("system_checkupdates")) editor.putBoolean("system_checkupdates", json.getBoolean("system_checkupdates")); if (json.has("system_usedarktheme")) editor.putBoolean("system_usedarktheme", json.getBoolean("system_usedarktheme")); - editor.commit(); + editor.apply(); } + /** + * Returns encoded server, web searches, RSS feed, background service and system settings as a JSON data object structure, serialized to a String. + * @param prefs The application-global preferences object to read settings from + * @throws JSONException Thrown when the JSON content could not be constructed properly + */ + 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. - * @param prefs The application-global preferences object to write settings to + * @param prefs The application-global preferences object to read settings from * @param settingsFile The local file to read the settings from * @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 exportSettings(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()) { + settingsFile.delete(); + } + settingsFile.getParentFile().mkdirs(); + settingsFile.createNewFile(); + FileWriter writer = new FileWriter(settingsFile); + writer.write(json.toString(2)); + writer.flush(); + writer.close(); + + } + + private JSONObject exportSettings(SharedPreferences prefs) throws JSONException { // Create a single JSON object that will contain all settings JSONObject json = new JSONObject(); @@ -207,7 +257,7 @@ public class SettingsPersistence { server.put("ssl_accept_all", prefs.getBoolean("server_ssltrustall_" + postfixi, false)); server.put("ssl_trust_key", prefs.getString("server_ssltrustkey_" + postfixi, null)); server.put("folder", prefs.getString("server_folder_" + postfixi, null)); - server.put("use_auth", prefs.getBoolean("server_useauth_" + postfixi, true)); + server.put("use_auth", !prefs.getBoolean("server_disableauth_" + postfixi, false)); server.put("username", prefs.getString("server_user_" + postfixi, null)); server.put("password", prefs.getString("server_pass_" + postfixi, null)); server.put("extra_password", prefs.getString("server_extrapass_" + postfixi, null)); @@ -234,6 +284,7 @@ public class SettingsPersistence { JSONObject site = new JSONObject(); site.put("name", prefs.getString("websearch_name_" + postfixj, null)); site.put("url", prefs.getString("websearch_baseurl_" + postfixj, null)); + site.put("cookies", prefs.getString("websearch_cookies_" + postfixj, null)); sites.put(site); j++; @@ -251,6 +302,7 @@ public class SettingsPersistence { feed.put("name", prefs.getString("rssfeed_name_" + postfixk, null)); feed.put("url", prefs.getString("rssfeed_url_" + postfixk, null)); feed.put("needs_auth", prefs.getBoolean("rssfeed_reqauth_" + postfixk, false)); + feed.put("new_item_alarm", prefs.getBoolean("rssfeed_alarmnew_" + postfixk, false)); feed.put("last_seen", prefs.getString("rssfeed_lastnew_" + postfixk, null)); feeds.put(feed); @@ -266,20 +318,13 @@ public class SettingsPersistence { json.put("alarm_vibrate", prefs.getBoolean("notifications_vibrate", false)); json.put("alarm_ledcolour", prefs.getInt("notifications_ledcolour", -1)); json.put("alarm_adwnotifications", prefs.getBoolean("notifications_adwnotify", false)); - json.put("system_checkupdates", prefs.getBoolean("system_checkupdates", true)); + json.put("system_dormantasinactive", prefs.getBoolean("system_dormantasinactive", false)); + json.put("system_autorefresh", prefs.getString("system_autorefresh", "0")); json.put("system_usedarktheme", prefs.getBoolean("system_usedarktheme", false)); + json.put("system_checkupdates", prefs.getBoolean("system_checkupdates", true)); - // 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(); - + return json; + } - + } diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java index a763be98..ee62c0b2 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java @@ -712,11 +712,11 @@ public class TorrentsActivity extends Activity implements OnNavigationListener, @OptionsItem(resName = "action_add_frombarcode") protected void startBarcodeScanner() { - BarcodeHelper.startBarcodeScanner(this); + BarcodeHelper.startBarcodeScanner(this, BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT); } @Background - @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE) + @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT) public void onBarcodeScanned(int resultCode, Intent data) { // We receive from the helper either a URL (as string) or a query we can start a search for String query = BarcodeHelper.handleScanResult(resultCode, data); diff --git a/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java b/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java index 39cbf425..a61c8672 100644 --- a/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java +++ b/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java @@ -16,9 +16,6 @@ */ package org.transdroid.core.gui.search; -import org.transdroid.R; -import org.transdroid.core.app.search.GoogleWebSearchBarcodeResolver; - import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; @@ -29,23 +26,53 @@ import android.content.Intent; import android.net.Uri; import android.text.TextUtils; +import org.transdroid.R; +import org.transdroid.core.app.search.GoogleWebSearchBarcodeResolver; + public class BarcodeHelper { - public static final int ACTIVITY_BARCODE = 0x0000c0de; // A 'random' ID to identify scan intents + public static final int ACTIVITY_BARCODE_ADDTORRENT = 0x0000c0de; + // A 'random' ID to identify torrent adding scan intents + public static final int ACTIVITY_BARCODE_QRSETTINGS = 0x0000c0df; + // A 'random' ID to identify QR-encoded settings scan intents public static final Uri SCANNER_MARKET_URI = Uri.parse("market://search?q=pname:com.google.zxing.client.android"); /** - * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with ID - * {@link #ACTIVITY_BARCODE}. From there {@link #handleScanResult(int, Intent)} should be called to parse the result - * into a search query. + * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with ID {@link + * #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there {@link #handleScanResult(int, + * Intent)} can be called to parse the result into a search query, in case of {@link #ACTIVITY_BARCODE_ADDTORRENT} + * scans. + * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install + * the bar code scanner + * @param requestCode {@link #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS + */ + public static void startBarcodeScanner(final Activity activity, int requestCode) { + // Start a bar code scanner that can handle the SCAN intent (specifically ZXing) + startBarcodeIntent(activity, new Intent("com.google.zxing.client.android.SCAN"), requestCode); + } + + /** + * Call this to share content encoded in a QR code, specially used to share settings. The calling activity will + * receive an Intent result with ID {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there the returned intent will + * contain the data as SCAN_RESULT String extra. * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install - * the bar code scanner + * the bar code scanner + * @param content The content to share, that is, the raw data (Transdroid settings encoded as JSON data structure) + * to share as QR code */ + public static void shareContentBarcode(final Activity activity, final String content) { + // Start a bar code encoded that can handle the ENCODE intent (specifically ZXing) + Intent encodeIntent = new Intent("com.google.zxing.client.android.ENCODE"); + encodeIntent.putExtra("ENCODE_TYPE", "TEXT_TYPE"); + encodeIntent.putExtra("ENCODE_DATA", content); + encodeIntent.putExtra("ENCODE_SHOW_CONTENTS", false); + startBarcodeIntent(activity, encodeIntent, -1); + } + @SuppressLint("ValidFragment") - public static void startBarcodeScanner(final Activity activity) { + private static void startBarcodeIntent(final Activity activity, final Intent intent, int requestCode) { try { - // Start a bar code scanner that can handle the SCAN intent (specifically ZXing) - activity.startActivityForResult(new Intent("com.google.zxing.client.android.SCAN"), ACTIVITY_BARCODE); + activity.startActivityForResult(intent, requestCode); } catch (Exception e) { // Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present new DialogFragment() { @@ -55,22 +82,26 @@ public class BarcodeHelper { .setPositiveButton(android.R.string.yes, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if (activity != null) + if (activity != null) { activity.startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI)); + } } }).setNegativeButton(android.R.string.no, null).create(); - }; + } + + ; }.show(activity.getFragmentManager(), "installscanner"); } } /** - * The activity that called {@link #startBarcodeScanner(Activity)} should call this after the scan - * result was returned. This will parse the scan data and return a query search query appropriate to the bar code. + * The activity that called {@link #startBarcodeScanner(android.app.Activity, int)} with {@link + * #ACTIVITY_BARCODE_ADDTORRENT} should call this after the scan result was returned. This will parse the scan data + * and return a query search query appropriate to the bar code. * @param resultCode The raw result code as returned by the bar code scanner * @param data The raw data as returned from the bar code scanner * @return A String that can be used as new search query, or null if the bar code could not be scanned or no query - * can be constructed for it + * can be constructed for it */ public static String handleScanResult(int resultCode, Intent data) { String contents = data.getStringExtra("SCAN_RESULT"); @@ -79,8 +110,9 @@ public class BarcodeHelper { // Scanned barcode was a QR code: return the contents directly return contents; } else { - if (TextUtils.isEmpty(contents)) + if (TextUtils.isEmpty(contents)) { return null; + } // Get a meaningful search query based on a Google Search product lookup return GoogleWebSearchBarcodeResolver.resolveBarcode(contents); } 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 03b2f5d0..52def869 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 @@ -16,21 +16,6 @@ */ package org.transdroid.core.gui.settings; -import java.io.FileNotFoundException; -import java.io.IOException; - -import org.androidannotations.annotations.Bean; -import org.androidannotations.annotations.EActivity; -import org.androidannotations.annotations.OptionsItem; -import org.json.JSONException; -import org.transdroid.R; -import org.transdroid.core.app.settings.ApplicationSettings; -import org.transdroid.core.app.settings.SettingsPersistence; -import org.transdroid.core.gui.log.ErrorLogSender; -import org.transdroid.core.gui.navigation.NavigationHelper; -import org.transdroid.core.gui.search.SearchHistoryProvider; -import org.transdroid.core.service.BootReceiver; - import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.Dialog; @@ -45,14 +30,48 @@ import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; +import android.text.TextUtils; + +import org.androidannotations.annotations.Bean; +import org.androidannotations.annotations.EActivity; +import org.androidannotations.annotations.OnActivityResult; +import org.androidannotations.annotations.OptionsItem; +import org.json.JSONException; +import org.transdroid.R; +import org.transdroid.core.app.settings.ApplicationSettings; +import org.transdroid.core.app.settings.SettingsPersistence; +import org.transdroid.core.gui.log.ErrorLogSender; +import org.transdroid.core.gui.navigation.NavigationHelper; +import org.transdroid.core.gui.search.BarcodeHelper; +import org.transdroid.core.gui.search.SearchHistoryProvider; +import org.transdroid.core.service.BootReceiver; + +import java.io.FileNotFoundException; +import java.io.IOException; + import de.keyboardsurfer.android.widget.crouton.Crouton; @EActivity public class SystemSettingsActivity extends PreferenceActivity { protected static final int DIALOG_IMPORTSETTINGS = 0; + private OnPreferenceClickListener onImportSettingsClick = new OnPreferenceClickListener() { + @SuppressWarnings("deprecation") + @Override + public boolean onPreferenceClick(Preference preference) { + showDialog(DIALOG_IMPORTSETTINGS); + return true; + } + }; protected static final int DIALOG_EXPORTSETTINGS = 1; - + private OnPreferenceClickListener onExportSettingsClick = new OnPreferenceClickListener() { + @SuppressWarnings("deprecation") + @Override + public boolean onPreferenceClick(Preference preference) { + showDialog(DIALOG_EXPORTSETTINGS); + return true; + } + }; @Bean protected NavigationHelper navigationHelper; @Bean @@ -61,63 +80,17 @@ public class SystemSettingsActivity extends PreferenceActivity { protected ErrorLogSender errorLogSender; @Bean protected SettingsPersistence settingsPersistence; - - @SuppressWarnings("deprecation") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getActionBar().setDisplayHomeAsUpEnabled(true); - - // Just load the system-related preferences from XML - addPreferencesFromResource(R.xml.pref_system); - - // Handle outgoing links and preference changes - if (navigationHelper.enableUpdateChecker()) { - findPreference("system_checkupdates").setOnPreferenceClickListener(onCheckUpdatesClick); - } else { - getPreferenceScreen().removePreference(findPreference("system_checkupdates")); - } - findPreference("system_clearsearch").setOnPreferenceClickListener(onClearSearchClick); - findPreference("system_importsettings").setOnPreferenceClickListener(onImportSettingsClick); - findPreference("system_exportsettings").setOnPreferenceClickListener(onExportSettingsClick); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - @OptionsItem(android.R.id.home) - protected void navigateUp() { - MainSettingsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); - } - private OnPreferenceClickListener onCheckUpdatesClick = new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - if (((CheckBoxPreference) preference).isChecked()) + if (((CheckBoxPreference) preference).isChecked()) { BootReceiver.startAppUpdatesService(getApplicationContext()); - else + } else { BootReceiver.cancelAppUpdates(getApplicationContext()); + } return true; } }; - - private OnPreferenceClickListener onImportSettingsClick = new OnPreferenceClickListener() { - @SuppressWarnings("deprecation") - @Override - public boolean onPreferenceClick(Preference preference) { - showDialog(DIALOG_IMPORTSETTINGS); - return true; - } - }; - - private OnPreferenceClickListener onExportSettingsClick = new OnPreferenceClickListener() { - @SuppressWarnings("deprecation") - @Override - public boolean onPreferenceClick(Preference preference) { - showDialog(DIALOG_EXPORTSETTINGS); - return true; - } - }; - private OnPreferenceClickListener onClearSearchClick = new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { @@ -127,41 +100,12 @@ public class SystemSettingsActivity extends PreferenceActivity { return true; } }; - - protected Dialog onCreateDialog(int id) { - 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(android.R.string.ok, importSettings) - .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, - getString(R.string.app_name), - SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) - .setPositiveButton(android.R.string.ok, exportSettings) - .setNegativeButton(android.R.string.cancel, null).create(); - // @formatter:on - } - return null; - } - - private OnClickListener importSettings = new OnClickListener() { + private OnClickListener importSettingsFromFile = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); try { - settingsPersistence.importSettings(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); + settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); Crouton.showText(SystemSettingsActivity.this, R.string.pref_import_success, NavigationHelper.CROUTON_INFO_STYLE); } catch (FileNotFoundException e) { @@ -174,13 +118,18 @@ public class SystemSettingsActivity extends PreferenceActivity { } } }; - - private OnClickListener exportSettings = new OnClickListener() { + private OnClickListener importSettingsFromQr = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + BarcodeHelper.startBarcodeScanner(SystemSettingsActivity.this, BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS); + } + }; + private OnClickListener exportSettingsToFile = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); try { - settingsPersistence.exportSettings(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); + settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); Crouton.showText(SystemSettingsActivity.this, R.string.pref_export_success, NavigationHelper.CROUTON_INFO_STYLE); } catch (JSONException e) { @@ -192,5 +141,94 @@ public class SystemSettingsActivity extends PreferenceActivity { } } }; + private OnClickListener exportSettingsToQr = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); + try { + String settings = settingsPersistence.exportSettingsAsString(prefs); + BarcodeHelper.shareContentBarcode(SystemSettingsActivity.this, settings); + } catch (JSONException e) { + Crouton.showText(SystemSettingsActivity.this, R.string.error_cant_write_settings_file, + NavigationHelper.CROUTON_ERROR_STYLE); + } + } + }; + + @SuppressWarnings("deprecation") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getActionBar().setDisplayHomeAsUpEnabled(true); + + // Just load the system-related preferences from XML + addPreferencesFromResource(R.xml.pref_system); + + // Handle outgoing links and preference changes + if (navigationHelper.enableUpdateChecker()) { + findPreference("system_checkupdates").setOnPreferenceClickListener(onCheckUpdatesClick); + } else { + getPreferenceScreen().removePreference(findPreference("system_checkupdates")); + } + findPreference("system_clearsearch").setOnPreferenceClickListener(onClearSearchClick); + findPreference("system_importsettings").setOnPreferenceClickListener(onImportSettingsClick); + findPreference("system_exportsettings").setOnPreferenceClickListener(onExportSettingsClick); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @OptionsItem(android.R.id.home) + protected void navigateUp() { + MainSettingsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); + } + + @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS) + public void onQrCodeScanned(int resultCode, Intent data) { + // We should have received Intent extras with the QR-decoded data representing Transdroid settings + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); + String contents = data.getStringExtra("SCAN_RESULT"); + String formatName = data.getStringExtra("SCAN_RESULT_FORMAT"); + if (formatName != null && formatName.equals("QR_CODE") && !TextUtils.isEmpty(contents)) { + try { + settingsPersistence.importSettingsAsString(prefs, contents); + Crouton.showText(SystemSettingsActivity.this, R.string.pref_import_success, + NavigationHelper.CROUTON_INFO_STYLE); + } catch (JSONException e) { + Crouton.showText(SystemSettingsActivity.this, R.string.error_file_not_found, + NavigationHelper.CROUTON_ERROR_STYLE); + } + } + } + + @SuppressWarnings("deprecation") + protected Dialog onCreateDialog(int id) { + 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 + case DIALOG_EXPORTSETTINGS: + // @formatter:off + return new AlertDialog.Builder(this) + .setMessage( + getString( + R.string.pref_export_dialog, + getString(R.string.app_name), + SettingsPersistence.DEFAULT_SETTINGS_FILE.toString())) + .setPositiveButton(R.string.pref_export_tofile, exportSettingsToFile) + .setNeutralButton(R.string.pref_export_toqr, exportSettingsToQr) + .setNegativeButton(android.R.string.cancel, null).create(); + // @formatter:on + } + return null; + } } diff --git a/app/src/main/res/values/changelog.xml b/app/src/main/res/values/changelog.xml index afc5f824..9d547b22 100644 --- a/app/src/main/res/values/changelog.xml +++ b/app/src/main/res/values/changelog.xml @@ -21,8 +21,10 @@ Transdroid 2.3.0\n - Aria2 support\n - Improved SSL support with TLS 1.1/1.2 and SNI hostname verification\n - Fixed server checker when one is unavailable\n -- Fixed Deluge magnet links from Chrome\n +- Fixed magnet links from Chrome\n - Enable RSS notifications per feed\n +- Improved bug logging\n +- Import/export settings using QR code\n \n Transdroid 2.2.0\n - Dropped legacy Android support; Android 4.0.3 is the new minimum\n diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4a6dd857..19e12734 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -321,9 +321,13 @@ Search history is cleared Import settings %1$s will try to import server, web search, RSS and system settings from: %2$s + 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 + To file + To QR code Settings successfully exported Transdroid help