|
| 1 | +package org.schabi.newpipe.settings; |
| 2 | + |
| 3 | +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; |
| 4 | +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; |
| 5 | + |
| 6 | +import android.app.Activity; |
| 7 | +import android.app.AlertDialog; |
| 8 | +import android.content.Context; |
| 9 | +import android.content.Intent; |
| 10 | +import android.content.SharedPreferences; |
| 11 | +import android.net.Uri; |
| 12 | +import android.os.Bundle; |
| 13 | +import android.widget.Toast; |
| 14 | + |
| 15 | +import androidx.activity.result.ActivityResult; |
| 16 | +import androidx.activity.result.ActivityResultLauncher; |
| 17 | +import androidx.activity.result.contract.ActivityResultContracts; |
| 18 | +import androidx.annotation.NonNull; |
| 19 | +import androidx.annotation.Nullable; |
| 20 | +import androidx.core.content.ContextCompat; |
| 21 | +import androidx.preference.Preference; |
| 22 | +import androidx.preference.PreferenceManager; |
| 23 | + |
| 24 | +import org.schabi.newpipe.NewPipeDatabase; |
| 25 | +import org.schabi.newpipe.R; |
| 26 | +import org.schabi.newpipe.error.ErrorUtil; |
| 27 | +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; |
| 28 | +import org.schabi.newpipe.streams.io.StoredFileHelper; |
| 29 | +import org.schabi.newpipe.util.NavigationHelper; |
| 30 | +import org.schabi.newpipe.util.ZipHelper; |
| 31 | + |
| 32 | +import java.io.File; |
| 33 | +import java.io.IOException; |
| 34 | +import java.text.SimpleDateFormat; |
| 35 | +import java.util.Date; |
| 36 | +import java.util.Locale; |
| 37 | +import java.util.Objects; |
| 38 | + |
| 39 | +public class BackupRestoreSettingsFragment extends BasePreferenceFragment { |
| 40 | + |
| 41 | + private static final String ZIP_MIME_TYPE = "application/zip"; |
| 42 | + |
| 43 | + private final SimpleDateFormat exportDateFormat = |
| 44 | + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); |
| 45 | + private ContentSettingsManager manager; |
| 46 | + private String importExportDataPathKey; |
| 47 | + private final ActivityResultLauncher<Intent> requestImportPathLauncher = |
| 48 | + registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), |
| 49 | + this::requestImportPathResult); |
| 50 | + private final ActivityResultLauncher<Intent> requestExportPathLauncher = |
| 51 | + registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), |
| 52 | + this::requestExportPathResult); |
| 53 | + |
| 54 | + |
| 55 | + @Override |
| 56 | + public void onCreatePreferences(@Nullable final Bundle savedInstanceState, |
| 57 | + @Nullable final String rootKey) { |
| 58 | + final File homeDir = ContextCompat.getDataDir(requireContext()); |
| 59 | + Objects.requireNonNull(homeDir); |
| 60 | + manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir)); |
| 61 | + manager.deleteSettingsFile(); |
| 62 | + |
| 63 | + importExportDataPathKey = getString(R.string.import_export_data_path); |
| 64 | + |
| 65 | + |
| 66 | + addPreferencesFromResourceRegistry(); |
| 67 | + |
| 68 | + final Preference importDataPreference = requirePreference(R.string.import_data); |
| 69 | + importDataPreference.setOnPreferenceClickListener((Preference p) -> { |
| 70 | + NoFileManagerSafeGuard.launchSafe( |
| 71 | + requestImportPathLauncher, |
| 72 | + StoredFileHelper.getPicker(requireContext(), |
| 73 | + ZIP_MIME_TYPE, getImportExportDataUri()), |
| 74 | + TAG, |
| 75 | + getContext() |
| 76 | + ); |
| 77 | + |
| 78 | + return true; |
| 79 | + }); |
| 80 | + |
| 81 | + final Preference exportDataPreference = requirePreference(R.string.export_data); |
| 82 | + exportDataPreference.setOnPreferenceClickListener((final Preference p) -> { |
| 83 | + NoFileManagerSafeGuard.launchSafe( |
| 84 | + requestExportPathLauncher, |
| 85 | + StoredFileHelper.getNewPicker(requireContext(), |
| 86 | + "NewPipeData-" + exportDateFormat.format(new Date()) + ".zip", |
| 87 | + ZIP_MIME_TYPE, getImportExportDataUri()), |
| 88 | + TAG, |
| 89 | + getContext() |
| 90 | + ); |
| 91 | + |
| 92 | + return true; |
| 93 | + }); |
| 94 | + |
| 95 | + final Preference resetSettings = findPreference(getString(R.string.reset_settings)); |
| 96 | + // Resets all settings by deleting shared preference and restarting the app |
| 97 | + // A dialogue will pop up to confirm if user intends to reset all settings |
| 98 | + assert resetSettings != null; |
| 99 | + resetSettings.setOnPreferenceClickListener(preference -> { |
| 100 | + // Show Alert Dialogue |
| 101 | + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); |
| 102 | + builder.setMessage(R.string.reset_all_settings); |
| 103 | + builder.setCancelable(true); |
| 104 | + builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> { |
| 105 | + // Deletes all shared preferences xml files. |
| 106 | + final SharedPreferences sharedPreferences = |
| 107 | + PreferenceManager.getDefaultSharedPreferences(requireContext()); |
| 108 | + sharedPreferences.edit().clear().apply(); |
| 109 | + // Restarts the app |
| 110 | + if (getActivity() == null) { |
| 111 | + return; |
| 112 | + } |
| 113 | + NavigationHelper.restartApp(getActivity()); |
| 114 | + }); |
| 115 | + builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> { |
| 116 | + }); |
| 117 | + final AlertDialog alertDialog = builder.create(); |
| 118 | + alertDialog.show(); |
| 119 | + return true; |
| 120 | + }); |
| 121 | + } |
| 122 | + |
| 123 | + private void requestExportPathResult(final ActivityResult result) { |
| 124 | + assureCorrectAppLanguage(requireContext()); |
| 125 | + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { |
| 126 | + // will be saved only on success |
| 127 | + final Uri lastExportDataUri = result.getData().getData(); |
| 128 | + |
| 129 | + final StoredFileHelper file = new StoredFileHelper( |
| 130 | + requireContext(), result.getData().getData(), ZIP_MIME_TYPE); |
| 131 | + |
| 132 | + exportDatabase(file, lastExportDataUri); |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + private void requestImportPathResult(final ActivityResult result) { |
| 137 | + assureCorrectAppLanguage(requireContext()); |
| 138 | + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { |
| 139 | + // will be saved only on success |
| 140 | + final Uri lastImportDataUri = result.getData().getData(); |
| 141 | + |
| 142 | + final StoredFileHelper file = new StoredFileHelper( |
| 143 | + requireContext(), result.getData().getData(), ZIP_MIME_TYPE); |
| 144 | + |
| 145 | + new androidx.appcompat.app.AlertDialog.Builder(requireActivity()) |
| 146 | + .setMessage(R.string.override_current_data) |
| 147 | + .setPositiveButton(R.string.ok, (d, id) -> |
| 148 | + importDatabase(file, lastImportDataUri)) |
| 149 | + .setNegativeButton(R.string.cancel, (d, id) -> |
| 150 | + d.cancel()) |
| 151 | + .show(); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + private void exportDatabase(final StoredFileHelper file, final Uri exportDataUri) { |
| 156 | + try { |
| 157 | + //checkpoint before export |
| 158 | + NewPipeDatabase.checkpoint(); |
| 159 | + |
| 160 | + final SharedPreferences preferences = PreferenceManager |
| 161 | + .getDefaultSharedPreferences(requireContext()); |
| 162 | + manager.exportDatabase(preferences, file); |
| 163 | + |
| 164 | + saveLastImportExportDataUri(exportDataUri); // save export path only on success |
| 165 | + Toast.makeText(requireContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT) |
| 166 | + .show(); |
| 167 | + } catch (final Exception e) { |
| 168 | + ErrorUtil.showUiErrorSnackbar(this, "Exporting database", e); |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + private void importDatabase(final StoredFileHelper file, final Uri importDataUri) { |
| 173 | + // check if file is supported |
| 174 | + if (!ZipHelper.isValidZipFile(file)) { |
| 175 | + Toast.makeText(requireContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT) |
| 176 | + .show(); |
| 177 | + return; |
| 178 | + } |
| 179 | + |
| 180 | + try { |
| 181 | + if (!manager.ensureDbDirectoryExists()) { |
| 182 | + throw new IOException("Could not create databases dir"); |
| 183 | + } |
| 184 | + |
| 185 | + if (!manager.extractDb(file)) { |
| 186 | + Toast.makeText(requireContext(), R.string.could_not_import_all_files, |
| 187 | + Toast.LENGTH_LONG) |
| 188 | + .show(); |
| 189 | + } |
| 190 | + |
| 191 | + // if settings file exist, ask if it should be imported. |
| 192 | + if (manager.extractSettings(file)) { |
| 193 | + new androidx.appcompat.app.AlertDialog.Builder(requireContext()) |
| 194 | + .setTitle(R.string.import_settings) |
| 195 | + .setNegativeButton(R.string.cancel, (dialog, which) -> { |
| 196 | + dialog.dismiss(); |
| 197 | + finishImport(importDataUri); |
| 198 | + }) |
| 199 | + .setPositiveButton(R.string.ok, (dialog, which) -> { |
| 200 | + dialog.dismiss(); |
| 201 | + final Context context = requireContext(); |
| 202 | + final SharedPreferences prefs = PreferenceManager |
| 203 | + .getDefaultSharedPreferences(context); |
| 204 | + manager.loadSharedPreferences(prefs); |
| 205 | + cleanImport(context, prefs); |
| 206 | + finishImport(importDataUri); |
| 207 | + }) |
| 208 | + .show(); |
| 209 | + } else { |
| 210 | + finishImport(importDataUri); |
| 211 | + } |
| 212 | + } catch (final Exception e) { |
| 213 | + ErrorUtil.showUiErrorSnackbar(this, "Importing database", e); |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + /** |
| 218 | + * Remove settings that are not supposed to be imported on different devices |
| 219 | + * and reset them to default values. |
| 220 | + * @param context the context used for the import |
| 221 | + * @param prefs the preferences used while running the import |
| 222 | + */ |
| 223 | + private void cleanImport(@NonNull final Context context, |
| 224 | + @NonNull final SharedPreferences prefs) { |
| 225 | + // Check if media tunnelling needs to be disabled automatically, |
| 226 | + // if it was disabled automatically in the imported preferences. |
| 227 | + final String tunnelingKey = context.getString(R.string.disable_media_tunneling_key); |
| 228 | + final String automaticTunnelingKey = |
| 229 | + context.getString(R.string.disabled_media_tunneling_automatically_key); |
| 230 | + // R.string.disable_media_tunneling_key should always be true |
| 231 | + // if R.string.disabled_media_tunneling_automatically_key equals 1, |
| 232 | + // but we double check here just to be sure and to avoid regressions |
| 233 | + // caused by possible later modification of the media tunneling functionality. |
| 234 | + // R.string.disabled_media_tunneling_automatically_key == 0: |
| 235 | + // automatic value overridden by user in settings |
| 236 | + // R.string.disabled_media_tunneling_automatically_key == -1: not set |
| 237 | + final boolean wasMediaTunnelingDisabledAutomatically = |
| 238 | + prefs.getInt(automaticTunnelingKey, -1) == 1 |
| 239 | + && prefs.getBoolean(tunnelingKey, false); |
| 240 | + if (wasMediaTunnelingDisabledAutomatically) { |
| 241 | + prefs.edit() |
| 242 | + .putInt(automaticTunnelingKey, -1) |
| 243 | + .putBoolean(tunnelingKey, false) |
| 244 | + .apply(); |
| 245 | + NewPipeSettings.setMediaTunneling(context); |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + /** |
| 250 | + * Save import path and restart system. |
| 251 | + * |
| 252 | + * @param importDataUri The import path to save |
| 253 | + */ |
| 254 | + private void finishImport(final Uri importDataUri) { |
| 255 | + // save import path only on success |
| 256 | + saveLastImportExportDataUri(importDataUri); |
| 257 | + // restart app to properly load db |
| 258 | + NavigationHelper.restartApp(requireActivity()); |
| 259 | + } |
| 260 | + |
| 261 | + private Uri getImportExportDataUri() { |
| 262 | + final String path = defaultPreferences.getString(importExportDataPathKey, null); |
| 263 | + return isBlank(path) ? null : Uri.parse(path); |
| 264 | + } |
| 265 | + |
| 266 | + private void saveLastImportExportDataUri(final Uri importExportDataUri) { |
| 267 | + final SharedPreferences.Editor editor = defaultPreferences.edit() |
| 268 | + .putString(importExportDataPathKey, importExportDataUri.toString()); |
| 269 | + editor.apply(); |
| 270 | + } |
| 271 | +} |
0 commit comments