diff options
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod')
12 files changed, 549 insertions, 735 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java deleted file mode 100644 index f85a1cd77..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java +++ /dev/null @@ -1,218 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.ComponentName; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.ParcelFileDescriptor; -import com.google.android.material.snackbar.Snackbar; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; -import android.view.MenuItem; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.FileChannel; -import java.util.Arrays; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.PodDBAdapter; - -/** - * Displays the 'import/export' screen - */ -public class ImportExportActivity extends AppCompatActivity { - private static final int REQUEST_CODE_RESTORE = 43; - private static final int REQUEST_CODE_BACKUP_DOCUMENT = 44; - private static final String EXPORT_FILENAME = "AntennaPodBackup.db"; - private static final String TAG = ImportExportActivity.class.getSimpleName(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayShowHomeEnabled(true); - } - setContentView(R.layout.import_export_activity); - - findViewById(R.id.button_export).setOnClickListener(view -> backup()); - findViewById(R.id.button_import).setOnClickListener(view -> restore()); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void backup() { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType("application/x-sqlite3") - .putExtra(Intent.EXTRA_TITLE, EXPORT_FILENAME); - - startActivityForResult(intent, REQUEST_CODE_BACKUP_DOCUMENT); - } else { - try { - File sd = Environment.getExternalStorageDirectory(); - File backupDB = new File(sd, EXPORT_FILENAME); - writeBackupTo(new FileOutputStream(backupDB)); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } - } - } - - private void restore() { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - startActivityForResult(intent, REQUEST_CODE_RESTORE); - } else { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - startActivityForResult(Intent.createChooser(intent, - getString(R.string.import_select_file)), REQUEST_CODE_RESTORE); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent resultData) { - if (resultCode != RESULT_OK || resultData == null) { - return; - } - Uri uri = resultData.getData(); - - if (requestCode == REQUEST_CODE_RESTORE) { - restoreFrom(uri); - } else if (requestCode == REQUEST_CODE_BACKUP_DOCUMENT) { - backupToDocument(uri); - } - } - - private void restoreFrom(Uri inputUri) { - InputStream inputStream = null; - try { - if (!validateDB(inputUri)) { - displayBadFileDialog(); - return; - } - - File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); - inputStream = getContentResolver().openInputStream(inputUri); - FileUtils.copyInputStreamToFile(inputStream, currentDB); - displayImportSuccessDialog(); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - private static final byte[] SQLITE3_MAGIC = "SQLite format 3\0".getBytes(); - private boolean validateDB(Uri inputUri) throws IOException { - try (InputStream inputStream = getContentResolver().openInputStream(inputUri)) { - byte[] magicBuf = new byte[SQLITE3_MAGIC.length]; - if (inputStream.read(magicBuf) == magicBuf.length) { - return Arrays.equals(SQLITE3_MAGIC, magicBuf); - } - } - - return false; - } - - private void displayBadFileDialog() { - AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this); - d.setMessage(R.string.import_bad_file) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, ((dialogInterface, i) -> { - // do nothing - })) - .show(); - } - - private void displayImportSuccessDialog() { - AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this); - d.setMessage(R.string.import_ok); - d.setCancelable(false); - d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { - Intent intent = new Intent(getApplicationContext(), SplashActivity.class); - ComponentName cn = intent.getComponent(); - Intent mainIntent = Intent.makeRestartActivityTask(cn); - startActivity(mainIntent); - }); - d.show(); - } - - private void backupToDocument(Uri uri) { - ParcelFileDescriptor pfd = null; - FileOutputStream fileOutputStream = null; - try { - pfd = getContentResolver().openFileDescriptor(uri, "w"); - fileOutputStream = new FileOutputStream(pfd.getFileDescriptor()); - writeBackupTo(fileOutputStream); - - Snackbar.make(findViewById(R.id.import_export_layout), - R.string.export_ok, Snackbar.LENGTH_SHORT).show(); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(fileOutputStream); - - if (pfd != null) { - try { - pfd.close(); - } catch (IOException e) { - Log.d(TAG, "Unable to close ParcelFileDescriptor"); - } - } - } - } - - private void writeBackupTo(FileOutputStream outFileStream) { - FileChannel src = null; - FileChannel dst = null; - try { - File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); - - if (currentDB.exists()) { - src = new FileInputStream(currentDB).getChannel(); - dst = outFileStream.getChannel(); - dst.transferFrom(src, 0, src.size()); - - Snackbar.make(findViewById(R.id.import_export_layout), - R.string.export_ok, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.import_export_layout), - "Can not access current database", Snackbar.LENGTH_SHORT).show(); - } - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(src); - IOUtils.closeQuietly(dst); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 538ed1231..021ff774d 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -358,10 +358,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite); } - boolean sleepTimerSet = controller.sleepTimerActive(); - boolean sleepTimerNotSet = controller.sleepTimerNotActive(); - menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet); - menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet); + menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive()); + menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive()); if (this instanceof AudioplayerActivity) { int[] attrs = {R.attr.action_bar_icon_color}; @@ -422,30 +420,9 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements .show(); } break; - case R.id.disable_sleeptimer_item: - if (controller.serviceAvailable()) { - - new AlertDialog.Builder(this) - .setTitle(R.string.sleep_timer_label) - .setMessage(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) controller - .getSleepTimerTimeLeft())) - .setPositiveButton(R.string.disable_sleeptimer_label, (dialog, which) - -> controller.disableSleepTimer()) - .setNegativeButton(R.string.cancel_label, null) - .show(); - } - break; + case R.id.disable_sleeptimer_item: // Fall-through case R.id.set_sleeptimer_item: - if (controller.serviceAvailable()) { - SleepTimerDialog td = new SleepTimerDialog(this) { - @Override - public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) { - controller.setSleepTimer(millis, shakeToReset, vibrate); - } - }; - td.createNewDialog().show(); - } + new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog"); break; case R.id.audio_controls: boolean isPlayingVideo = controller.getMedia().getMediaType() == MediaType.VIDEO; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index d7a4b9517..03e6b89db 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -4,35 +4,53 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; +import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.core.app.ActivityCompat; import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; - -import org.apache.commons.lang3.ArrayUtils; - -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; - +import androidx.core.app.ActivityCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.OpmlFeedQueuer; import de.danoeh.antennapod.asynctask.OpmlImportWorker; import de.danoeh.antennapod.core.export.opml.OpmlElement; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.LangUtils; +import org.apache.commons.lang3.ArrayUtils; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; /** - * Base activity for Opml Import - e.g. with code what to do afterwards + * Activity for Opml Import. * */ -public class OpmlImportBaseActivity extends AppCompatActivity { - +public class OpmlImportActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; - private OpmlImportWorker importWorker; @Nullable private Uri uri; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + Uri uri = getIntent().getData(); + if (uri != null && uri.toString().startsWith("/")) { + uri = Uri.parse("file://" + uri.toString()); + } else { + String extraText = getIntent().getStringExtra(Intent.EXTRA_TEXT); + if (extraText != null) { + uri = Uri.parse(extraText); + } + } + importUri(uri); + } + /** * Handles the choices made by the user in the OpmlFeedChooserActivity and * starts the OpmlFeedQueuer if necessary. @@ -42,9 +60,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { Log.d(TAG, "Received result"); if (resultCode == RESULT_CANCELED) { Log.d(TAG, "Activity was cancelled"); - if (finishWhenCanceled()) { - finish(); - } + finish(); } else { int[] selected = data.getIntArrayExtra(OpmlFeedChooserActivity.EXTRA_SELECTED_ITEMS); if (selected != null && selected.length > 0) { @@ -53,7 +69,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { @Override protected void onPostExecute(Void result) { super.onPostExecute(result); - Intent intent = new Intent(OpmlImportBaseActivity.this, MainActivity.class); + Intent intent = new Intent(OpmlImportActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); @@ -68,7 +84,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { } void importUri(@Nullable Uri uri) { - if(uri == null) { + if (uri == null) { new AlertDialog.Builder(this) .setMessage(R.string.opml_import_error_no_file) .setPositiveButton(android.R.string.ok, null) @@ -76,9 +92,10 @@ public class OpmlImportBaseActivity extends AppCompatActivity { return; } this.uri = uri; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { - int permission = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { + int permission = ActivityCompat.checkSelfPermission(this, + android.Manifest.permission.READ_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { requestPermission(); return; @@ -93,9 +110,8 @@ public class OpmlImportBaseActivity extends AppCompatActivity { } @Override - public void onRequestPermissionsResult(int requestCode, - String[] permissions, - int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { return; } @@ -113,8 +129,8 @@ public class OpmlImportBaseActivity extends AppCompatActivity { /** Starts the import process. */ private void startImport() { try { - Reader mReader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); - importWorker = new OpmlImportWorker(this, mReader) { + Reader reader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); + OpmlImportWorker importWorker = new OpmlImportWorker(this, reader) { @Override protected void onPostExecute(ArrayList<OpmlElement> result) { @@ -123,7 +139,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { Log.d(TAG, "Parsing was successful"); OpmlImportHolder.setReadElements(result); startActivityForResult(new Intent( - OpmlImportBaseActivity.this, + OpmlImportActivity.this, OpmlFeedChooserActivity.class), 0); } else { Log.d(TAG, "Parser error occurred"); @@ -140,10 +156,4 @@ public class OpmlImportBaseActivity extends AppCompatActivity { .show(); } } - - boolean finishWhenCanceled() { - return false; - } - - } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java deleted file mode 100644 index 557510808..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; - -import de.danoeh.antennapod.core.preferences.UserPreferences; - -/** - * Lets the user start the OPML-import process. - */ -public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { - - private static final String TAG = "OpmlImportFromIntentAct"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - Uri uri = getIntent().getData(); - if (uri != null && uri.toString().startsWith("/")) { - uri = Uri.parse("file://" + uri.toString()); - } else { - String extraText = getIntent().getStringExtra(Intent.EXTRA_TEXT); - if(extraText != null) { - uri = Uri.parse(extraText); - } - } - importUri(uri); - } - - @Override - protected boolean finishWhenCanceled() { - return true; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java deleted file mode 100644 index 158e3b7a4..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.StorageUtils; - -/** - * Lets the user start the OPML-import process from a path - */ -public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { - - private static final String TAG = "OpmlImportFromPathAct"; - - private static final int CHOOSE_OPML_FILE = 1; - - private Intent intentGetContentAction; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.opml_import); - - final TextView txtvHeaderExplanation = findViewById(R.id.txtvHeadingExplanation); - final TextView txtvExplanation = findViewById(R.id.txtvExplanation); - final TextView txtvHeaderExplanationOpenWith = findViewById(R.id.txtvHeadingExplanationOpenWith); - - Button butChooseFilesystem = findViewById(R.id.butChooseFileFromFilesystem); - butChooseFilesystem.setOnClickListener(v -> chooseFileFromExternal()); - - int nextOption = 1; - String optionLabel = getString(R.string.opml_import_option); - intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); - intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentGetContentAction.setType("*/*"); - - if (IntentUtils.isCallable(getApplicationContext(), intentGetContentAction)) { - txtvHeaderExplanation.setText(String.format(optionLabel, nextOption)); - nextOption++; - } else { - txtvHeaderExplanation.setVisibility(View.GONE); - txtvExplanation.setVisibility(View.GONE); - findViewById(R.id.divider).setVisibility(View.GONE); - butChooseFilesystem.setVisibility(View.GONE); - } - - txtvHeaderExplanationOpenWith.setText(String.format(optionLabel, nextOption)); - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return false; - } - } - - private void chooseFileFromExternal() { - try { - startActivityForResult(intentGetContentAction, CHOOSE_OPML_FILE); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - /** - * Gets the path of the file chosen with chooseFileToImport() - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK && requestCode == CHOOSE_OPML_FILE) { - Uri uri = data.getData(); - if(uri != null && uri.toString().startsWith("/")) { - uri = Uri.parse("file://" + uri.toString()); - } - importUri(uri); - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index a0f9bf6d8..dd91b7e2a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -16,6 +16,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.GpodderPreferencesFragment; +import de.danoeh.antennapod.fragment.preferences.ImportExportPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.IntegrationsPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment; @@ -59,6 +60,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe prefFragment = new NetworkPreferencesFragment(); } else if (screen == R.xml.preferences_storage) { prefFragment = new StoragePreferencesFragment(); + } else if (screen == R.xml.preferences_import_export) { + prefFragment = new ImportExportPreferencesFragment(); } else if (screen == R.xml.preferences_autodownload) { prefFragment = new AutoDownloadPreferencesFragment(); } else if (screen == R.xml.preferences_gpodder) { @@ -79,6 +82,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe return R.string.playback_pref; case R.xml.preferences_storage: return R.string.storage_pref; + case R.xml.preferences_import_export: + return R.string.import_export_pref; case R.xml.preferences_user_interface: return R.string.user_interface_label; case R.xml.preferences_integrations: diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index b88b58537..e037eb392 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -16,84 +16,78 @@ import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.export.opml.OpmlElement; import de.danoeh.antennapod.core.export.opml.OpmlReader; -public class OpmlImportWorker extends - AsyncTask<Void, Void, ArrayList<OpmlElement>> { - private static final String TAG = "OpmlImportWorker"; +public class OpmlImportWorker extends AsyncTask<Void, Void, ArrayList<OpmlElement>> { + private static final String TAG = "OpmlImportWorker"; - private final Context context; - private Exception exception; + private final Context context; + private Exception exception; + private ProgressDialog progDialog; - private ProgressDialog progDialog; - - private final Reader mReader; + private final Reader reader; public OpmlImportWorker(Context context, Reader reader) { super(); this.context = context; - this.mReader=reader; + this.reader = reader; } - @Override - protected ArrayList<OpmlElement> doInBackground(Void... params) { - Log.d(TAG, "Starting background work"); + @Override + protected ArrayList<OpmlElement> doInBackground(Void... params) { + Log.d(TAG, "Starting background work"); - if (mReader==null) { + if (reader == null) { return null; } - OpmlReader opmlReader = new OpmlReader(); - try { - ArrayList<OpmlElement> result = opmlReader.readDocument(mReader); - mReader.close(); - return result; - } catch (XmlPullParserException e) { - e.printStackTrace(); - exception = e; - return null; - } catch (IOException e) { - e.printStackTrace(); - exception = e; - return null; - } - - } - - @Override - protected void onPostExecute(ArrayList<OpmlElement> result) { - if (mReader != null) { + OpmlReader opmlReader = new OpmlReader(); + try { + ArrayList<OpmlElement> result = opmlReader.readDocument(reader); + reader.close(); + return result; + } catch (XmlPullParserException e) { + e.printStackTrace(); + exception = e; + return null; + } catch (IOException e) { + e.printStackTrace(); + exception = e; + return null; + } + + } + + @Override + protected void onPostExecute(ArrayList<OpmlElement> result) { + if (reader != null) { try { - mReader.close(); + reader.close(); } catch (IOException e) { e.printStackTrace(); } } - progDialog.dismiss(); - if (exception != null) { - Log.d(TAG, "An error occurred while trying to parse the opml document"); - AlertDialog.Builder alert = new AlertDialog.Builder(context); - alert.setTitle(R.string.error_label); - alert.setMessage(context.getString(R.string.opml_reader_error) - + exception.getMessage()); - alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.create().show(); - } - } - - @Override - protected void onPreExecute() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.reading_opml_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.show(); - } - - public boolean wasSuccessful() { - return exception != null; - } - - public void executeAsync() { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } + progDialog.dismiss(); + if (exception != null) { + Log.d(TAG, "An error occurred while trying to parse the opml document"); + AlertDialog.Builder alert = new AlertDialog.Builder(context); + alert.setTitle(R.string.error_label); + alert.setMessage(context.getString(R.string.opml_reader_error) + + exception.getMessage()); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.create().show(); + } + } + + @Override + protected void onPreExecute() { + progDialog = new ProgressDialog(context); + progDialog.setMessage(context.getString(R.string.please_wait)); + progDialog.setIndeterminate(true); + progDialog.setCancelable(false); + progDialog.show(); + } + + public void executeAsync() { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java index 8d176c708..d36f97c7a 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -1,67 +1,101 @@ package de.danoeh.antennapod.dialog; +import android.app.Dialog; import android.content.Context; +import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; - +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; -import org.greenrobot.eventbus.EventBus; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; -public abstract class SleepTimerDialog { - - private static final String TAG = SleepTimerDialog.class.getSimpleName(); +import java.util.concurrent.TimeUnit; - private final Context context; +public class SleepTimerDialog extends DialogFragment { + private PlaybackController controller; + private Disposable timeUpdater; - private AlertDialog dialog; private EditText etxtTime; private Spinner spTimeUnit; private CheckBox cbShakeToReset; private CheckBox cbVibrate; private CheckBox chAutoEnable; + private LinearLayout timeSetup; + private LinearLayout timeDisplay; + private TextView time; + public SleepTimerDialog() { - protected SleepTimerDialog(Context context) { - this.context = context; } - public AlertDialog createNewDialog() { - View content = View.inflate(context, R.layout.time_dialog, null); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.set_sleeptimer_label); - builder.setView(content); - builder.setNegativeButton(R.string.cancel_label, (dialog, which) -> dialog.dismiss()); - builder.setPositiveButton(R.string.set_sleeptimer_label, (dialog, which) -> { - try { - savePreferences(); - long input = SleepTimerPreferences.timerMillis(); - onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked()); - dialog.dismiss(); - } catch (NumberFormatException e) { - e.printStackTrace(); - Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input, - Toast.LENGTH_LONG); - toast.show(); + @Override + public void onStart() { + super.onStart(); + controller = new PlaybackController(getActivity(), false) { + @Override + public void setupGUI() { + updateTime(); } - }); - dialog = builder.create(); + + @Override + public void onSleepTimerUpdate() { + updateTime(); + } + }; + controller.init(); + timeUpdater = Observable.interval(1, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(tick -> updateTime()); + } + + @Override + public void onStop() { + super.onStop(); + if (controller != null) { + controller.release(); + } + if (timeUpdater != null) { + timeUpdater.dispose(); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View content = View.inflate(getContext(), R.layout.time_dialog, null); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.sleep_timer_label); + builder.setView(content); + builder.setPositiveButton(android.R.string.ok, null); etxtTime = content.findViewById(R.id.etxtTime); spTimeUnit = content.findViewById(R.id.spTimeUnit); cbShakeToReset = content.findViewById(R.id.cbShakeToReset); cbVibrate = content.findViewById(R.id.cbVibrate); chAutoEnable = content.findViewById(R.id.chAutoEnable); + timeSetup = content.findViewById(R.id.timeSetup); + timeDisplay = content.findViewById(R.id.timeDisplay); + time = content.findViewById(R.id.time); + AlertDialog dialog = builder.create(); etxtTime.setText(SleepTimerPreferences.lastTimerValue()); etxtTime.addTextChangedListener(new TextWatcher() { @Override @@ -78,15 +112,15 @@ public abstract class SleepTimerDialog { } }); etxtTime.postDelayed(() -> { - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); }, 100); String[] spinnerContent = new String[] { - context.getString(R.string.time_seconds), - context.getString(R.string.time_minutes), - context.getString(R.string.time_hours) }; - ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(context, + getString(R.string.time_seconds), + getString(R.string.time_minutes), + getString(R.string.time_hours) }; + ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, spinnerContent); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spTimeUnit.setAdapter(spinnerAdapter); @@ -96,16 +130,33 @@ public abstract class SleepTimerDialog { cbVibrate.setChecked(SleepTimerPreferences.vibrate()); chAutoEnable.setChecked(SleepTimerPreferences.autoEnable()); - chAutoEnable.setOnCheckedChangeListener((compoundButton, isChecked) -> { - SleepTimerPreferences.setAutoEnable(isChecked); - int messageString = isChecked ? R.string.sleep_timer_enabled_label : R.string.sleep_timer_disabled_label; - EventBus.getDefault().post(new MessageEvent(context.getString(messageString))); + chAutoEnable.setOnCheckedChangeListener((compoundButton, isChecked) + -> SleepTimerPreferences.setAutoEnable(isChecked)); + Button disableButton = content.findViewById(R.id.disableSleeptimerButton); + disableButton.setOnClickListener(v -> { + if (controller != null) { + controller.disableSleepTimer(); + } + }); + Button setButton = content.findViewById(R.id.setSleeptimerButton); + setButton.setOnClickListener(v -> { + if (!PlaybackService.isRunning) { + Toast.makeText(getContext(), R.string.no_media_playing_label, Toast.LENGTH_LONG).show(); + } + try { + savePreferences(); + long time = SleepTimerPreferences.timerMillis(); + if (controller != null) { + controller.setSleepTimer(time, cbShakeToReset.isChecked(), cbVibrate.isChecked()); + } + } catch (NumberFormatException e) { + e.printStackTrace(); + Toast.makeText(getContext(), R.string.time_dialog_invalid_input, Toast.LENGTH_LONG).show(); + } }); return dialog; } - public abstract void onTimerSet(long millis, boolean shakeToReset, boolean vibrate); - private void savePreferences() { SleepTimerPreferences.setLastTimer(etxtTime.getText().toString(), spTimeUnit.getSelectedItemPosition()); @@ -114,4 +165,12 @@ public abstract class SleepTimerDialog { SleepTimerPreferences.setAutoEnable(chAutoEnable.isChecked()); } + private void updateTime() { + if (controller == null) { + return; + } + timeSetup.setVisibility(controller.sleepTimerActive() ? View.GONE : View.VISIBLE); + timeDisplay.setVisibility(controller.sleepTimerActive() ? View.VISIBLE : View.GONE); + time.setText(Converter.getDurationStringLong((int) controller.getSleepTimerTimeLeft())); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index 2cfe7c1e8..343cf76ab 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -1,7 +1,11 @@ package de.danoeh.antennapod.fragment; +import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.util.Log; import androidx.fragment.app.Fragment; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -14,7 +18,7 @@ import android.widget.EditText; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; +import de.danoeh.antennapod.activity.OpmlImportActivity; import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment; /** @@ -28,6 +32,7 @@ public class AddFeedFragment extends Fragment { * Preset value for url text field. */ private static final String ARG_FEED_URL = "feedurl"; + private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; private EditText combinedFeedSearchBox; private MainActivity activity; @@ -44,8 +49,16 @@ public class AddFeedFragment extends Fragment { setupSeachBox(root); View butOpmlImport = root.findViewById(R.id.btn_opml_import); - butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), - OpmlImportFromPathActivity.class))); + butOpmlImport.setOnClickListener(v -> { + try { + Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + }); root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); return root; } @@ -130,4 +143,18 @@ public class AddFeedFragment extends Fragment { // persist. mfietz thinks this causes the ActionBar to be invalidated setHasOptionsMenu(true); } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + Uri uri = data.getData(); + + if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { + Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + } } 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 new file mode 100644 index 000000000..9a09d55b0 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -0,0 +1,283 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import androidx.appcompat.app.AlertDialog; +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.activity.SplashActivity; +import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; +import de.danoeh.antennapod.asynctask.ExportWorker; +import de.danoeh.antennapod.core.export.ExportWriter; +import de.danoeh.antennapod.core.export.html.HtmlWriter; +import de.danoeh.antennapod.core.export.opml.OpmlWriter; +import de.danoeh.antennapod.core.storage.DatabaseExporter; +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.util.List; + +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 DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; + private static final String CONTENT_TYPE_OPML = "text/x-opml"; + private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; + private static final String CONTENT_TYPE_HTML = "text/html"; + private static final int REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH = 1; + private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 2; + private static final int REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH = 3; + private static final int REQUEST_CODE_RESTORE_DATABASE = 4; + private static final int REQUEST_CODE_BACKUP_DATABASE = 5; + private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup.db"; + 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(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME, + REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH, new OpmlWriter()); + return true; + } + ); + findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( + preference -> { + openExportPathPicker(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME, + REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH, new HtmlWriter()); + return true; + }); + findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( + preference -> { + try { + Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + } 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 -> { + exportDatabase(); + return true; + }); + } + + private void exportWithWriter(ExportWriter exportWriter, final Uri uri) { + Context context = getActivity(); + progressDialog.show(); + if (uri == null) { + Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable(); + disposable = observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> { + Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), + context.getString(R.string.provider_authority), output); + showExportSuccessDialog(output.toString(), fileUri); + }, this::showExportErrorDialog, progressDialog::dismiss); + } else { + DocumentFileExportWorker worker = new DocumentFileExportWorker(exportWriter, context, uri); + disposable = worker.exportObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> + showExportSuccessDialog(output.getUri().toString(), output.getUri()), + this::showExportErrorDialog, progressDialog::dismiss); + } + } + + private void exportDatabase() { + if (Build.VERSION.SDK_INT >= 19) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("application/x-sqlite3") + .putExtra(Intent.EXTRA_TITLE, DATABASE_EXPORT_FILENAME); + + startActivityForResult(intent, REQUEST_CODE_BACKUP_DATABASE); + } else { + File sd = Environment.getExternalStorageDirectory(); + File backupDB = new File(sd, DATABASE_EXPORT_FILENAME); + progressDialog.show(); + disposable = Completable.fromAction(() -> + DatabaseExporter.exportToStream(new FileOutputStream(backupDB), getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } + } + + private void importDatabase() { + if (Build.VERSION.SDK_INT >= 19) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + startActivityForResult(intent, REQUEST_CODE_RESTORE_DATABASE); + } else { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + startActivityForResult(Intent.createChooser(intent, + getString(R.string.import_select_file)), REQUEST_CODE_RESTORE_DATABASE); + } + } + + private void showDatabaseImportSuccessDialog() { + AlertDialog.Builder d = new AlertDialog.Builder(getContext()); + d.setMessage(R.string.import_ok); + d.setCancelable(false); + d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { + Intent intent = new Intent(getContext(), SplashActivity.class); + ComponentName cn = intent.getComponent(); + Intent mainIntent = Intent.makeRestartActivityTask(cn); + startActivity(mainIntent); + }); + d.show(); + } + + private void showExportSuccessDialog(final String path, final Uri streamUri) { + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_success_title); + alert.setMessage(getContext().getString(R.string.export_success_sum, path)); + alert.setPositiveButton(R.string.send_label, (dialog, which) -> { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); + sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); + sendIntent.setType("text/plain"); + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + List<ResolveInfo> resInfoList = getContext().getPackageManager() + .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } + getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); + }); + alert.create().show(); + } + + private void showExportErrorDialog(final Throwable error) { + progressDialog.dismiss(); + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) + .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_error_label); + alert.setMessage(error.getMessage()); + alert.show(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + Uri uri = data.getData(); + + if (requestCode == REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH) { + exportWithWriter(new OpmlWriter(), uri); + } else if (requestCode == REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH) { + exportWithWriter(new HtmlWriter(), uri); + } else if (requestCode == REQUEST_CODE_RESTORE_DATABASE) { + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + showDatabaseImportSuccessDialog(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } else if (requestCode == REQUEST_CODE_BACKUP_DATABASE) { + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } else if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { + Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + } + + private void openExportPathPicker(String contentType, String title, int requestCode, ExportWriter writer) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(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 { + startActivityForResult(intentPickAction, requestCode); + 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 + exportWithWriter(writer, null); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 5fd38d663..da82d4f8c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -103,6 +103,9 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)); config.index(R.xml.preferences_storage) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_storage)); + config.index(R.xml.preferences_import_export) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_storage)) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_import_export)); config.index(R.xml.preferences_autodownload) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)) .addBreadcrumb(R.string.automation) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java index 5ce852ed2..8a0742b7f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java @@ -1,62 +1,32 @@ package de.danoeh.antennapod.fragment.preferences; import android.Manifest; -import android.annotation.SuppressLint; 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.content.pm.ResolveInfo; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import androidx.core.content.FileProvider; -import androidx.documentfile.provider.DocumentFile; +import android.util.Log; import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceFragmentCompat; -import android.util.Log; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.DirectoryChooserActivity; -import de.danoeh.antennapod.activity.ImportExportActivity; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; -import de.danoeh.antennapod.asynctask.ExportWorker; -import de.danoeh.antennapod.core.export.ExportWriter; -import de.danoeh.antennapod.core.export.html.HtmlWriter; -import de.danoeh.antennapod.core.export.opml.OpmlWriter; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.dialog.ChooseDataFolderDialog; -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.util.List; public class StoragePreferencesFragment extends PreferenceFragmentCompat { private static final String TAG = "StoragePrefFragment"; - 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 IMPORT_EXPORT = "importExport"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; + private static final String PREF_IMPORT_EXPORT = "prefImportExport"; private static final String[] EXTERNAL_STORAGE_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41; - private static final int CHOOSE_OPML_EXPORT_PATH = 1; - private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; - private static final String CONTENT_TYPE_OPML = "text/x-opml"; - private static final int CHOOSE_HTML_EXPORT_PATH = 2; - private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; - private static final String CONTENT_TYPE_HTML = "text/html"; - private Disposable disposable; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -76,51 +46,20 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { setDataFolderText(); } - @Override - public void onStop() { - super.onStop(); - if (disposable != null) { - disposable.dispose(); - } - } - private void setupStorageScreen() { final Activity activity = getActivity(); - - findPreference(IMPORT_EXPORT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, ImportExportActivity.class)); - return true; - } - ); - findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( - preference -> { - openOpmlExportPathPicker(); - return true; - } - ); - findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( - preference -> { - openHtmlExportPathPicker(); - return true; - }); - findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class)); - return true; - }); findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( preference -> { - if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT && - Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { showChooseDataFolderDialog(); } else { int readPermission = ActivityCompat.checkSelfPermission( activity, Manifest.permission.READ_EXTERNAL_STORAGE); int writePermission = ActivityCompat.checkSelfPermission( activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (readPermission == PackageManager.PERMISSION_GRANTED && - writePermission == PackageManager.PERMISSION_GRANTED) { + if (readPermission == PackageManager.PERMISSION_GRANTED + && writePermission == PackageManager.PERMISSION_GRANTED) { openDirectoryChooser(); } else { requestPermission(); @@ -129,19 +68,18 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { return true; } ); - findPreference(PREF_CHOOSE_DATA_DIR) - .setOnPreferenceClickListener( - preference -> { - if (Build.VERSION.SDK_INT >= 19) { - showChooseDataFolderDialog(); - } else { - Intent intent = new Intent(activity, DirectoryChooserActivity.class); - activity.startActivityForResult(intent, - DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); - } - return true; - } - ); + findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( + preference -> { + if (Build.VERSION.SDK_INT >= 19) { + showChooseDataFolderDialog(); + } else { + Intent intent = new Intent(activity, DirectoryChooserActivity.class); + activity.startActivityForResult(intent, + DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); + } + return true; + } + ); findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( (preference, o) -> { if (o instanceof String) { @@ -158,74 +96,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { return false; } ); - } - - private boolean export(ExportWriter exportWriter) { - return export(exportWriter, null); - } - - private boolean export(ExportWriter exportWriter, final Uri uri) { - Context context = getActivity(); - final ProgressDialog progressDialog = new ProgressDialog(context); - progressDialog.setMessage(context.getString(R.string.exporting_label)); - progressDialog.setIndeterminate(true); - progressDialog.show(); - if (uri == null) { - Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), - context.getString(R.string.provider_authority), output); - showExportSuccessDialog(context.getString(R.string.export_success_sum, output.toString()), fileUri); - }, this::showExportErrorDialog, progressDialog::dismiss); - } else { - Observable<DocumentFile> observable = new DocumentFileExportWorker(exportWriter, context, uri).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - showExportSuccessDialog(context.getString(R.string.export_success_sum, output.getUri()), output.getUri()); - }, this::showExportErrorDialog, progressDialog::dismiss); - } - return true; - } - - private void showExportSuccessDialog(final String message, final Uri streamUri) { - final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_success_title); - alert.setMessage(message); - alert.setPositiveButton(R.string.send_label, (dialog, which) -> { - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); - sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); - sendIntent.setType("text/plain"); - sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = getContext().getPackageManager() - .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + findPreference(PREF_IMPORT_EXPORT).setOnPreferenceClickListener( + preference -> { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_import_export); + return true; } - } - getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); - }); - alert.create().show(); - } - - private void showExportErrorDialog(final Throwable error) { - final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_error_label); - alert.setMessage(error.getMessage()); - alert.show(); + ); } - @SuppressLint("NewApi") public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && - requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + if (resultCode == Activity.RESULT_OK && requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); File path; @@ -255,23 +135,12 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { ab.show(); } } - - if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_OPML_EXPORT_PATH) { - Uri uri = data.getData(); - export(new OpmlWriter(), uri); - } - - if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_HTML_EXPORT_PATH) { - Uri uri = data.getData(); - export(new HtmlWriter(), uri); - } } private void setDataFolderText() { File f = UserPreferences.getDataFolder(null); if (f != null) { - findPreference(PREF_CHOOSE_DATA_DIR) - .setSummary(f.getAbsolutePath()); + findPreference(PREF_CHOOSE_DATA_DIR).setSummary(f.getAbsolutePath()); } } @@ -286,50 +155,6 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); } - private void openOpmlExportPathPicker() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(CONTENT_TYPE_OPML) - .putExtra(Intent.EXTRA_TITLE, DEFAULT_OPML_OUTPUT_NAME); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, CHOOSE_OPML_EXPORT_PATH); - 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 - export(new OpmlWriter()); - } - - private void openHtmlExportPathPicker() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(CONTENT_TYPE_HTML) - .putExtra(Intent.EXTRA_TITLE, DEFAULT_HTML_OUTPUT_NAME); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, CHOOSE_HTML_EXPORT_PATH); - 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 - export(new HtmlWriter()); - } - private void showChooseDataFolderDialog() { ChooseDataFolderDialog.showDialog( getActivity(), new ChooseDataFolderDialog.RunnableWithString() { |