/* * This source is part of the * _____ ___ ____ * __ / / _ \/ _ | / __/___ _______ _ * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / * /___/ * repository. * * Copyright (C) 2015-2017 Carmen Alvarez (c@rmen.ca) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ca.rmen.android.networkmonitor.app.prefs; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.preference.PreferenceManager; import android.support.design.widget.Snackbar; import java.io.File; import java.util.Map; import ca.rmen.android.networkmonitor.Constants; import ca.rmen.android.networkmonitor.R; import ca.rmen.android.networkmonitor.app.dbops.ui.Share; import ca.rmen.android.networkmonitor.util.IoUtil; import ca.rmen.android.networkmonitor.util.Log; /** * Export and import shared preferences. */ final class SettingsExportImport { private static final String TAG = Constants.TAG + SettingsExportImport.class.getSimpleName(); private static final String PREF_IMPORT_VERIFICATION = "import_verification"; interface SettingsImportCallback { /** * Called when the settings have been successfully imported. */ void onSettingsImported(); } private SettingsExportImport() { // prevent instantiation } /** * Copies the shared preferences file to the sd card, and prompts the user to share it. */ static void exportSettings(final Activity activity) { final File inputFile = getSharedPreferencesFile(activity); final File outputFile = Share.getExportFile(activity, inputFile.getName()); new AsyncTask<Void, Void, Boolean>() { @SuppressLint("CommitPrefEdits") @Override protected Boolean doInBackground(Void... params) { if (outputFile == null) return false; // Just in case: make sure we don't have our temp setting. SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(activity); if (sharedPrefs.contains(PREF_IMPORT_VERIFICATION)) { Log.w(TAG, "Didn't expect to see the " + PREF_IMPORT_VERIFICATION + " setting when exporting"); sharedPrefs.edit().remove(PREF_IMPORT_VERIFICATION).commit(); } return IoUtil.copy(inputFile, outputFile); } @Override protected void onPostExecute(Boolean result) { if (result) { // Bring up the chooser to share the file. Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_SUBJECT, activity.getString(R.string.export_subject_send_settings)); Share.addFileToShareIntent(activity, sendIntent, outputFile.getName()); sendIntent.putExtra(Intent.EXTRA_TEXT, activity.getString(R.string.export_settings_message_text)); Intent chooserIntent = Intent.createChooser(sendIntent, activity.getResources().getText(R.string.action_share)); activity.startActivity(chooserIntent); } else { Snackbar.make(activity.getWindow().getDecorView().getRootView(), R.string.export_settings_failure, Snackbar.LENGTH_LONG).show(); } } }.execute(); } /** * Overwrites the app's default shared preferences file with the file at the given uri. */ static void importSettings(final Activity activity, final Uri uri, final SettingsImportCallback callback) { final File outputFile = getSharedPreferencesFile(activity); final File backupFile = new File(activity.getCacheDir(), outputFile.getName() + ".bak"); new AsyncTask<Void, Void, Boolean>() { private String mFileDisplayName; private void rollback() { IoUtil.copy(backupFile, outputFile); reloadSettings(activity); } @Override protected void onPreExecute() { Snackbar.make(activity.getWindow().getDecorView().getRootView(), R.string.import_settings_starting, Snackbar.LENGTH_LONG).show(); } @SuppressLint("CommitPrefEdits") @Override protected Boolean doInBackground(Void... params) { mFileDisplayName = Share.readDisplayName(activity, uri); // Make a backup of our shared prefs in case the import file is corrupt. if (!IoUtil.copy(outputFile, backupFile)) { return false; } // Set a temp preference now. We expect it to disappear after importing. PreferenceManager.getDefaultSharedPreferences(activity) .edit() .putBoolean(PREF_IMPORT_VERIFICATION, true) .commit(); // Attempt the copy if (IoUtil.copy(activity, uri, outputFile)) { Log.v(TAG, "Copy successful"); } else { rollback(); return false; } // Check that we have valid settings. if (!reloadSettings(activity)) { rollback(); return false; } return true; } @Override protected void onPostExecute(Boolean result) { if (result) { Snackbar.make(activity.getWindow().getDecorView().getRootView(), activity.getString(R.string.import_notif_complete_content, mFileDisplayName), Snackbar.LENGTH_LONG).show(); callback.onSettingsImported(); } else { Snackbar.make(activity.getWindow().getDecorView().getRootView(), activity.getString(R.string.import_notif_error_content, mFileDisplayName), Snackbar.LENGTH_LONG).show(); } } }.execute(); } private static boolean reloadSettings(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { if (!reloadSettingsPreV11(context)) return false; } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (!reloadSettingsV11(context)) return false; } else { if (!reloadSettingsV23(context)) return false; } // We expect our temporary preference to have been erased. return !PreferenceManager.getDefaultSharedPreferences(context).contains(PREF_IMPORT_VERIFICATION); } private static boolean reloadSettingsPreV11(Context context) { return reloadSettings(context, Context.MODE_PRIVATE); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private static boolean reloadSettingsV11(Context context) { //noinspection deprecation return reloadSettings(context, Context.MODE_MULTI_PROCESS); } @TargetApi(Build.VERSION_CODES.M) private static boolean reloadSettingsV23(Context context) { // This mode is deprecated but we still need this it in order to force a reread of preferences // from the disk. //noinspection deprecation return reloadSettings(context, Context.MODE_MULTI_PROCESS); } private static boolean reloadSettings(Context context, int preferencesReadMode) { String sharedPreferencesName = getSharedPreferencesName(context); SharedPreferences sharedPreferences = context.getSharedPreferences(sharedPreferencesName, preferencesReadMode); Map<String, ?> allSettings = sharedPreferences.getAll(); Log.v(TAG, "allSettings: " + allSettings); return !allSettings.isEmpty(); } private static String getSharedPreferencesName(Context context) { return context.getPackageName() + "_preferences"; } private static File getSharedPreferencesFile(Context context) { File dataDir = new File(context.getApplicationInfo().dataDir); File sharedPrefsDir = new File(dataDir, "shared_prefs"); String sharedPreferencesName = getSharedPreferencesName(context); return new File(sharedPrefsDir, sharedPreferencesName + ".xml"); } }