diff options
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java')
-rw-r--r-- | app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java | 413 |
1 files changed, 0 insertions, 413 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java deleted file mode 100644 index 7c607c242..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java +++ /dev/null @@ -1,413 +0,0 @@ -package de.danoeh.antennapod.fragment.preferences; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; - -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.activity.result.contract.ActivityResultContracts.GetContent; -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.documentfile.provider.DocumentFile; -import androidx.preference.SwitchPreferenceCompat; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import androidx.core.app.ShareCompat; -import androidx.core.content.FileProvider; -import androidx.preference.PreferenceFragmentCompat; -import com.google.android.material.snackbar.Snackbar; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.OpmlImportActivity; -import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.model.feed.FeedItem; -import de.danoeh.antennapod.model.feed.FeedItemFilter; -import de.danoeh.antennapod.model.feed.SortOrder; -import de.danoeh.antennapod.storage.importexport.AutomaticDatabaseExportWorker; -import de.danoeh.antennapod.storage.importexport.DatabaseExporter; -import de.danoeh.antennapod.storage.importexport.FavoritesWriter; -import de.danoeh.antennapod.storage.importexport.HtmlWriter; -import de.danoeh.antennapod.storage.importexport.OpmlWriter; -import de.danoeh.antennapod.storage.preferences.UserPreferences; -import io.reactivex.Completable; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { - private static final String TAG = "ImportExPrefFragment"; - private static final String PREF_OPML_EXPORT = "prefOpmlExport"; - private static final String PREF_OPML_IMPORT = "prefOpmlImport"; - private static final String PREF_HTML_EXPORT = "prefHtmlExport"; - private static final String PREF_DATABASE_IMPORT = "prefDatabaseImport"; - private static final String PREF_DATABASE_EXPORT = "prefDatabaseExport"; - private static final String PREF_AUTOMATIC_DATABASE_EXPORT = "prefAutomaticDatabaseExport"; - private static final String PREF_FAVORITE_EXPORT = "prefFavoritesExport"; - private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds-%s.opml"; - private static final String CONTENT_TYPE_OPML = "text/x-opml"; - private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds-%s.html"; - private static final String CONTENT_TYPE_HTML = "text/html"; - private static final String DEFAULT_FAVORITES_OUTPUT_NAME = "antennapod-favorites-%s.html"; - private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup-%s.db"; - - private final ActivityResultLauncher<Intent> chooseOpmlExportPathLauncher = - registerForActivityResult(new StartActivityForResult(), - result -> exportToDocument(result, Export.OPML)); - private final ActivityResultLauncher<Intent> chooseHtmlExportPathLauncher = - registerForActivityResult(new StartActivityForResult(), - result -> exportToDocument(result, Export.HTML)); - private final ActivityResultLauncher<Intent> chooseFavoritesExportPathLauncher = - registerForActivityResult(new StartActivityForResult(), - result -> exportToDocument(result, Export.FAVORITES)); - private final ActivityResultLauncher<Intent> restoreDatabaseLauncher = - registerForActivityResult(new StartActivityForResult(), this::restoreDatabaseResult); - private final ActivityResultLauncher<String> backupDatabaseLauncher = - registerForActivityResult(new BackupDatabase(), this::backupDatabaseResult); - private final ActivityResultLauncher<String> chooseOpmlImportPathLauncher = - registerForActivityResult(new GetContent(), uri -> { - if (uri != null) { - final Intent intent = new Intent(getContext(), OpmlImportActivity.class); - intent.setData(uri); - startActivity(intent); - } - }); - private final ActivityResultLauncher<Uri> automaticBackupLauncher = - registerForActivityResult(new PickWritableFolder(), this::setupAutomaticBackup); - - private Disposable disposable; - private ProgressDialog progressDialog; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_import_export); - setupStorageScreen(); - progressDialog = new ProgressDialog(getContext()); - progressDialog.setIndeterminate(true); - progressDialog.setMessage(getContext().getString(R.string.please_wait)); - } - - @Override - public void onStart() { - super.onStart(); - ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.import_export_pref); - } - - @Override - public void onStop() { - super.onStop(); - if (disposable != null) { - disposable.dispose(); - } - } - - private void setupStorageScreen() { - findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( - preference -> { - openExportPathPicker(Export.OPML, chooseOpmlExportPathLauncher); - return true; - } - ); - findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( - preference -> { - openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher); - return true; - }); - findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( - preference -> { - try { - chooseOpmlImportPathLauncher.launch("*/*"); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - return true; - }); - findPreference(PREF_DATABASE_IMPORT).setOnPreferenceClickListener( - preference -> { - importDatabase(); - return true; - }); - findPreference(PREF_DATABASE_EXPORT).setOnPreferenceClickListener( - preference -> { - backupDatabaseLauncher.launch(dateStampFilename(DATABASE_EXPORT_FILENAME)); - return true; - }); - ((SwitchPreferenceCompat) findPreference(PREF_AUTOMATIC_DATABASE_EXPORT)) - .setChecked(UserPreferences.getAutomaticExportFolder() != null); - findPreference(PREF_AUTOMATIC_DATABASE_EXPORT).setOnPreferenceChangeListener( - (preference, newValue) -> { - if (Boolean.TRUE.equals(newValue)) { - try { - automaticBackupLauncher.launch(null); - } catch (ActivityNotFoundException e) { - e.printStackTrace(); - Snackbar.make(getView(), R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG) - .show(); - } - return false; - } else { - UserPreferences.setAutomaticExportFolder(null); - AutomaticDatabaseExportWorker.enqueueIfNeeded(getContext(), false); - } - return true; - }); - findPreference(PREF_FAVORITE_EXPORT).setOnPreferenceClickListener( - preference -> { - openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher); - return true; - }); - } - - private String dateStampFilename(String fname) { - return String.format(fname, new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(new Date())); - } - - private void importDatabase() { - // setup the alert builder - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()); - builder.setTitle(R.string.database_import_label); - builder.setMessage(R.string.database_import_warning); - - // add a button - builder.setNegativeButton(R.string.no, null); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - restoreDatabaseLauncher.launch(intent); - }); - - // create and show the alert dialog - builder.show(); - } - - private void showDatabaseImportSuccessDialog() { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext()); - builder.setTitle(R.string.successful_import_label); - builder.setMessage(R.string.import_ok); - builder.setCancelable(false); - builder.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> forceRestart()); - builder.show(); - } - - void showExportSuccessSnackbar(Uri uri, String mimeType) { - Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG) - .setAction(R.string.share_label, v -> - new ShareCompat.IntentBuilder(getContext()) - .setType(mimeType) - .addStream(uri) - .setChooserTitle(R.string.share_label) - .startChooser()) - .show(); - } - - private void showExportErrorDialog(final Throwable error) { - progressDialog.dismiss(); - final MaterialAlertDialogBuilder alert = new MaterialAlertDialogBuilder(getContext()); - alert.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_error_label); - alert.setMessage(error.getMessage()); - alert.show(); - } - - private void restoreDatabaseResult(final ActivityResult result) { - if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { - return; - } - final Uri uri = result.getData().getData(); - progressDialog.show(); - disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - showDatabaseImportSuccessDialog(); - progressDialog.dismiss(); - }, this::showExportErrorDialog); - } - - private void backupDatabaseResult(final Uri uri) { - if (uri == null) { - return; - } - progressDialog.show(); - disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - showExportSuccessSnackbar(uri, "application/x-sqlite3"); - progressDialog.dismiss(); - }, this::showExportErrorDialog); - } - - private void openExportPathPicker(Export exportType, ActivityResultLauncher<Intent> result) { - String title = dateStampFilename(exportType.outputNameTemplate); - - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(exportType.contentType) - .putExtra(Intent.EXTRA_TITLE, title); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - result.launch(intentPickAction); - return; - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - - // If we are using a SDK lower than API 21 or the implicit intent failed - // fallback to the legacy export process - File output = new File(UserPreferences.getDataFolder("export/"), title); - exportToFile(exportType, output); - } - - private void exportToFile(Export exportType, File output) { - progressDialog.show(); - disposable = Observable.create( - subscriber -> { - if (output.exists()) { - boolean success = output.delete(); - Log.w(TAG, "Overwriting previously exported file: " + success); - } - try (FileOutputStream fileOutputStream = new FileOutputStream(output)) { - writeToStream(fileOutputStream, exportType); - subscriber.onNext(output); - } catch (IOException e) { - subscriber.onError(e); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(outputFile -> { - progressDialog.dismiss(); - Uri fileUri = FileProvider.getUriForFile(getActivity().getApplicationContext(), - getString(R.string.provider_authority), output); - showExportSuccessSnackbar(fileUri, exportType.contentType); - }, this::showExportErrorDialog, progressDialog::dismiss); - } - - private void exportToDocument(final ActivityResult result, Export exportType) { - if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { - return; - } - progressDialog.show(); - DocumentFile output = DocumentFile.fromSingleUri(getContext(), result.getData().getData()); - disposable = Observable.create( - subscriber -> { - try (OutputStream outputStream = getContext().getContentResolver() - .openOutputStream(output.getUri(), "wt")) { - writeToStream(outputStream, exportType); - subscriber.onNext(output); - } catch (IOException e) { - subscriber.onError(e); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignore -> { - progressDialog.dismiss(); - showExportSuccessSnackbar(output.getUri(), exportType.contentType); - }, this::showExportErrorDialog, progressDialog::dismiss); - } - - private void writeToStream(OutputStream outputStream, Export type) throws IOException { - try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"))) { - switch (type) { - case HTML: - HtmlWriter.writeDocument(DBReader.getFeedList(), writer, getContext()); - break; - case OPML: - OpmlWriter.writeDocument(DBReader.getFeedList(), writer); - break; - case FAVORITES: - List<FeedItem> allFavorites = DBReader.getEpisodes(0, Integer.MAX_VALUE, - new FeedItemFilter(FeedItemFilter.IS_FAVORITE), SortOrder.DATE_NEW_OLD); - FavoritesWriter.writeDocument(allFavorites, writer, getContext()); - break; - default: - showExportErrorDialog(new Exception("Invalid export type")); - break; - } - } - } - - private void setupAutomaticBackup(Uri uri) { - if (uri == null) { - return; - } - getActivity().getContentResolver().takePersistableUriPermission(uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - UserPreferences.setAutomaticExportFolder(uri.toString()); - AutomaticDatabaseExportWorker.enqueueIfNeeded(getContext(), true); - ((SwitchPreferenceCompat) findPreference(PREF_AUTOMATIC_DATABASE_EXPORT)).setChecked(true); - } - - private void forceRestart() { - PackageManager pm = getContext().getPackageManager(); - Intent intent = pm.getLaunchIntentForPackage(getContext().getPackageName()); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getContext().getApplicationContext().startActivity(intent); - Runtime.getRuntime().exit(0); - } - - private static class BackupDatabase extends ActivityResultContracts.CreateDocument { - - BackupDatabase() { - super("application/x-sqlite3"); - } - - @NonNull - @Override - public Intent createIntent(@NonNull final Context context, @NonNull final String input) { - return super.createIntent(context, input) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType("application/x-sqlite3"); - } - } - - private static class PickWritableFolder extends ActivityResultContracts.OpenDocumentTree { - @NonNull - @Override - public Intent createIntent(@NonNull final Context context, @Nullable final Uri input) { - return super.createIntent(context, input) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - } - } - - private enum Export { - OPML(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME, R.string.opml_export_label), - HTML(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME, R.string.html_export_label), - FAVORITES(CONTENT_TYPE_HTML, DEFAULT_FAVORITES_OUTPUT_NAME, R.string.favorites_export_label); - - final String contentType; - final String outputNameTemplate; - @StringRes - final int labelResId; - - Export(String contentType, String outputNameTemplate, int labelResId) { - this.contentType = contentType; - this.outputNameTemplate = outputNameTemplate; - this.labelResId = labelResId; - } - } -} |