From 83a0abddcc3c32dbacc2b206c1e5c20cc682204b Mon Sep 17 00:00:00 2001 From: Fynn Godau Date: Fri, 5 Sep 2025 17:19:32 +0200 Subject: [PATCH] Fix and simplify `openUrlInBrowser` The code was not previously working in case no default browser is set[1] AND NewPipe is set as default handler for the link in question. We improve it by telling the system to choose the target app as if the URI was `http://`, which works even if the user has not set a default browser. [1]: also the case if platform refuses to tell an app what the user's default browser is, which I observed on CalyxOS. --- .../external_communication/ShareUtils.java | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java index 7524e5413..9fe351b4b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java @@ -5,10 +5,9 @@ import static org.schabi.newpipe.MainActivity.DEBUG; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; @@ -23,6 +22,7 @@ import androidx.core.content.FileProvider; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; +import org.schabi.newpipe.RouterActivity; import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.util.image.ImageStrategy; import org.schabi.newpipe.util.image.PicassoHelper; @@ -62,8 +62,9 @@ public final class ShareUtils { } /** - * Open the url with the system default browser. If no browser is set as default, falls back to - * {@link #openAppChooser(Context, Intent, boolean)}. + * Open the url with the system default browser. If no browser is installed, falls back to + * {@link #openAppChooser(Context, Intent, boolean)} (for displaying that no apps are available + * to handle the action, or possible OEM-related edge cases). *

* This function selects the package to open based on which apps respond to the {@code http://} * schema alone, which should exclude special non-browser apps that are can handle the url (e.g. @@ -77,44 +78,26 @@ public final class ShareUtils { * @param url the url to browse **/ public static void openUrlInBrowser(@NonNull final Context context, final String url) { - // Resolve using a generic http://, so we are sure to get a browser and not e.g. the yt app. + // Target a generic http://, so we are sure to get a browser and not e.g. the yt app. // Note that this requires the `http` schema to be added to `` in the manifest. - final ResolveInfo defaultBrowserInfo; final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - defaultBrowserInfo = context.getPackageManager().resolveActivity(browserIntent, - PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)); - } else { - defaultBrowserInfo = context.getPackageManager().resolveActivity(browserIntent, - PackageManager.MATCH_DEFAULT_ONLY); - } final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (defaultBrowserInfo == null) { - // No app installed to open a web URL, but it may be handled by other apps so try - // opening a system chooser for the link in this case (it could be bypassed by the - // system if there is only one app which can open the link or a default app associated - // with the link domain on Android 12 and higher) + // See https://stackoverflow.com/a/58801285 and `setSelector` documentation + intent.setSelector(browserIntent); + try { + context.startActivity(intent); + } catch (final ActivityNotFoundException e) { + // No browser is available. This should, in the end, yield a nice AOSP error message + // indicating that no app is available to handle this action. + // + // Note: there are some situations where modified OEM ROMs have apps that appear + // to be browsers but are actually app choosers. If starting the Activity fails + // related to this, opening the system app chooser is still the correct behavior. + intent.setSelector(null); openAppChooser(context, intent, true); - return; - } - - final String defaultBrowserPackage = defaultBrowserInfo.activityInfo.packageName; - - if (defaultBrowserPackage.equals("android")) { - // No browser set as default (doesn't work on some devices) - openAppChooser(context, intent, true); - } else { - try { - intent.setPackage(defaultBrowserPackage); - context.startActivity(intent); - } catch (final ActivityNotFoundException e) { - // Not a browser but an app chooser because of OEMs changes - intent.setPackage(null); - openAppChooser(context, intent, true); - } } } @@ -190,6 +173,18 @@ public final class ShareUtils { chooserIntent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with)); } + // Avoid opening in NewPipe + // (Implementation note: if the URL is one for which NewPipe itself + // is set as handler on Android >= 12, we actually remove the only eligible app + // for this link, and browsers will not be offered to the user. For that, use + // `openUrlInBrowser`.) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + chooserIntent.putExtra( + Intent.EXTRA_EXCLUDE_COMPONENTS, + new ComponentName[]{new ComponentName(context, RouterActivity.class)} + ); + } + // Migrate any clip data and flags from the original intent. final int permFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION