diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 69e975a49..5c954ad64 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -53,6 +53,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.settings.NewPipeSettings; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredDirectoryHelper; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -687,7 +688,12 @@ public class DownloadDialog extends DialogFragment } private void launchDirectoryPicker(final ActivityResultLauncher launcher) { - launcher.launch(StoredDirectoryHelper.getPicker(context)); + NoFileManagerSafeGuard.launchSafe( + launcher, + StoredDirectoryHelper.getPicker(context), + TAG, + context + ); } private void prepareSelectedDownload() { @@ -766,8 +772,12 @@ public class DownloadDialog extends DialogFragment initialPath = Uri.parse(initialSavePath.getAbsolutePath()); } - requestDownloadSaveAsLauncher.launch(StoredFileHelper.getNewPicker(context, - filenameTmp, mimeTmp, initialPath)); + NoFileManagerSafeGuard.launchSafe( + requestDownloadSaveAsLauncher, + StoredFileHelper.getNewPicker(context, filenameTmp, mimeTmp, initialPath), + TAG, + context + ); return; } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 57e1effbe..008228083 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -55,6 +55,7 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.OnClickGesture @@ -179,15 +180,23 @@ class SubscriptionFragment : BaseStateFragment() { } private fun onImportPreviousSelected() { - requestImportLauncher.launch(StoredFileHelper.getPicker(activity, JSON_MIME_TYPE)) + NoFileManagerSafeGuard.launchSafe( + requestImportLauncher, + StoredFileHelper.getPicker(activity, JSON_MIME_TYPE), + TAG, + requireContext() + ) } private fun onExportSelected() { val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date()) val exportName = "newpipe_subscriptions_$date.json" - requestExportLauncher.launch( - StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null) + NoFileManagerSafeGuard.launchSafe( + requestExportLauncher, + StoredFileHelper.getNewPicker(activity, exportName, JSON_MIME_TYPE, null), + TAG, + requireContext() ) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java index 8dbd7b2c5..4737fa14f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java @@ -30,6 +30,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ServiceHelper; @@ -174,8 +175,14 @@ public class SubscriptionsImportFragment extends BaseFragment { } public void onImportFile() { - // leave */* mime type to support all services with different mime types and file extensions - requestImportFileLauncher.launch(StoredFileHelper.getPicker(activity, "*/*")); + NoFileManagerSafeGuard.launchSafe( + requestImportFileLauncher, + // leave */* mime type to support all services + // with different mime types and file extensions + StoredFileHelper.getPicker(activity, "*/*"), + TAG, + getContext() + ); } private void requestImportFileResult(final ActivityResult result) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 794c78d3e..1c8eb5cd2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -25,6 +25,7 @@ import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; @@ -73,19 +74,28 @@ public class ContentSettingsFragment extends BasePreferenceFragment { final Preference importDataPreference = requirePreference(R.string.import_data); importDataPreference.setOnPreferenceClickListener((Preference p) -> { - requestImportPathLauncher.launch( + NoFileManagerSafeGuard.launchSafe( + requestImportPathLauncher, StoredFileHelper.getPicker(requireContext(), - ZIP_MIME_TYPE, getImportExportDataUri())); + ZIP_MIME_TYPE, getImportExportDataUri()), + TAG, + getContext() + ); + return true; }); final Preference exportDataPreference = requirePreference(R.string.export_data); exportDataPreference.setOnPreferenceClickListener((final Preference p) -> { - - requestExportPathLauncher.launch( + NoFileManagerSafeGuard.launchSafe( + requestExportPathLauncher, StoredFileHelper.getNewPicker(requireContext(), "NewPipeData-" + exportDateFormat.format(new Date()) + ".zip", - ZIP_MIME_TYPE, getImportExportDataUri())); + ZIP_MIME_TYPE, getImportExportDataUri()), + TAG, + getContext() + ); + return true; }); diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index dfd77f049..681aee409 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -21,6 +21,7 @@ import androidx.preference.SwitchPreferenceCompat; import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredDirectoryHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -214,7 +215,12 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { } private void launchDirectoryPicker(final ActivityResultLauncher launcher) { - launcher.launch(StoredDirectoryHelper.getPicker(ctx)); + NoFileManagerSafeGuard.launchSafe( + launcher, + StoredDirectoryHelper.getPicker(ctx), + TAG, + ctx + ); } private void requestDownloadVideoPathResult(final ActivityResult result) { diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/NoFileManagerSafeGuard.java b/app/src/main/java/org/schabi/newpipe/streams/io/NoFileManagerSafeGuard.java new file mode 100644 index 000000000..df43c34c1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/streams/io/NoFileManagerSafeGuard.java @@ -0,0 +1,75 @@ +package org.schabi.newpipe.streams.io; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.os.Build; +import android.util.Log; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.appcompat.app.AlertDialog; + +import org.schabi.newpipe.R; + +/** + * Helper for when no file-manager/activity was found. + */ +public final class NoFileManagerSafeGuard { + private NoFileManagerSafeGuard() { + // No impl + } + + /** + * Shows an alert dialog when no file-manager is found. + * @param context Context + */ + private static void showActivityNotFoundAlert(final Context context) { + if (context == null) { + throw new IllegalArgumentException( + "Unable to open no file manager alert dialog: Context is null"); + } + + final String message; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Android 10+ only allows SAF + message = context.getString(R.string.no_appropriate_file_manager_message_android_10); + } else { + message = context.getString( + R.string.no_appropriate_file_manager_message, + context.getString(R.string.downloads_storage_use_saf_title)); + } + + + new AlertDialog.Builder(context) + .setTitle(R.string.no_app_to_open_intent) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show(); + } + + /** + * Launches the file manager safely. + * + * If no file manager is found (which is normally only the case when the user uninstalled + * the default file manager or the OS lacks one) an alert dialog shows up, asking the user + * to fix the situation. + * + * @param activityResultLauncher see {@link ActivityResultLauncher#launch(Object)} + * @param input see {@link ActivityResultLauncher#launch(Object)} + * @param tag Tag used for logging + * @param context Context + * @param see {@link ActivityResultLauncher#launch(Object)} + */ + public static void launchSafe( + final ActivityResultLauncher activityResultLauncher, + final I input, + final String tag, + final Context context + ) { + try { + activityResultLauncher.launch(input); + } catch (final ActivityNotFoundException aex) { + Log.w(tag, "Unable to launch file/directory picker", aex); + NoFileManagerSafeGuard.showActivityNotFoundAlert(context); + } + } +} diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 2cca3239b..dda2d6dee 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -32,6 +32,7 @@ import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.R; import org.schabi.newpipe.settings.NewPipeSettings; +import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.FilePickerActivityHelper; @@ -46,6 +47,7 @@ import us.shandian.giga.ui.adapter.MissionAdapter; public class MissionsFragment extends Fragment { + private static final String TAG = "MissionsFragment"; private static final int SPAN_SIZE = 2; private SharedPreferences mPrefs; @@ -257,9 +259,13 @@ public class MissionsFragment extends Fragment { initialPath = Uri.parse(initialSavePath.getAbsolutePath()); } - requestDownloadSaveAsLauncher.launch( + NoFileManagerSafeGuard.launchSafe( + requestDownloadSaveAsLauncher, StoredFileHelper.getNewPicker(mContext, mission.storage.getName(), - mission.storage.getType(), initialPath)); + mission.storage.getType(), initialPath), + TAG, + mContext + ); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc75fed06..b4d46a3e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -681,6 +681,8 @@ Recent Chapters No app on your device can open this + No appropriate file manager was found for this action.\nPlease install a file manager or try to disable \'%s\' in the download settings. + No appropriate file manager was found for this action.\nPlease install a Storage Access Framework compatible file manager. This content is not available in your country. This is a SoundCloud Go+ track, at least in your country, so it cannot be streamed or downloaded by NewPipe. This content is private, so it cannot be streamed or downloaded by NewPipe.