You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browser.OpenAsync(External) on Android throws FeatureNotSupportedException for verified App Link owner URLs even with documented <queries> fix applied #35651
Browser.OpenAsync(uri, BrowserLaunchMode.External) throws FeatureNotSupportedException ("Specified method is not supported.") on Android when the URL belongs to a verified App Link owner that the calling app cannot see due to Android 11+ package visibility — even when the documented <queries> declaration for http/https is correctly applied.
This is not the same as #20182 / #27744. Those were closed by pointing users at the <queries> declaration, which fixes the "fall through to a generic browser" subset of cases. It does not fix the App Link owner case (Instagram, Facebook, Spotify, X, TikTok, Google Maps when it owns a domain, etc.), which is the larger and more user-visible class of failures.
Steps to Reproduce
Create a File > New .NET MAUI app, target net10.0-android, min SDK 23+.
Add the documented <queries> block to Platforms/Android/AndroidManifest.xml:
Expected: Instagram opens at the target profile (the OS deep-links via verified App Link, the same way Chrome would).
Actual: Microsoft.Maui.ApplicationModel.FeatureNotSupportedException: Specified method is not supported.
at Microsoft.Maui.ApplicationModel.BrowserImplementation.LaunchExternalBrowser(...) in Browser.android.cs:line 90
at Microsoft.Maui.ApplicationModel.BrowserImplementation.OpenAsync(...) in Browser.android.cs:line 23
PlatformUtils.IsIntentSupported(intent) is intent.ResolveActivity(pm), which is filtered by caller-side package visibility. StartActivity, by contrast, dispatches via the system intent resolver and is not subject to caller-side visibility — it can launch invisible activities; the caller just isn't allowed to query about them ahead of time.
So ResolveActivity returns null in two distinct situations:
No handler exists at all.StartActivity would also fail (ActivityNotFoundException).
A handler exists but is invisible to the caller due to package visibility. StartActivity would succeed.
Today MAUI conflates the two and throws even in case 2, blocking a launch that Android can perform.
"If the intent filter includes a <data> element that contains a host, then your app is NOT considered to handle a web intent."
A standard <queries><intent VIEW + scheme=https></intent></queries> declaration matches generic browsers (Chrome, Firefox, Edge — no host restriction on their VIEW filter) but does not match host-bound App Link owners (IG, FB, Spotify — host="instagram.com", etc.).
Empirical confirmation via adb shell dumpsys package queries on a Pixel running stock Android 14 with the queries block applied:
app.example:
com.android.chrome ← visible (browser, no host filter)
com.opera.browser ← visible
com.microsoft.emmx ← visible
com.google.android.apps.maps
... (no com.instagram.android, no com.facebook.katana, etc.)
The verified App Link owner has exclusive ownership of instagram.com once verification succeeds, so Android does not fall back to a visible browser — ResolveActivity returns null, MAUI throws, nothing launches.
Why the Closed Issues Don't Cover This
IBrowser.OpenAsync(<url>, BrowserLaunchMode.External) fails on Android #20182 — closed by the reporter after the <queries> block fixed their case. Their reproduction was a generic public URL that falls through to a browser. None of the comments mention testing with a verified App Link owner. The fix is real but incomplete.
"Yeah looks like we check if the intent is supported for a URL. I guess if its http(s) we should just open the browser and not do anything further."
That code change was never shipped; the issue was closed pointing users at <queries>.
The two closed issues both leave the App Link owner installed and verified case silently broken. That is the case most apps hit in production because it covers nearly every popular consumer app users have installed (IG, FB, Spotify, X, TikTok, WhatsApp click-to-chat, YouTube, Reddit, etc.).
Workarounds (and what they prove)
Switch to BrowserLaunchMode.SystemPreferred — works. That path has no IsIntentSupported pre-check; CustomTabsIntent.LaunchUrl ultimately calls ContextCompat.startActivity with an ACTION_VIEW intent, the system resolver picks the App Link owner, and IG opens. Side effect: non-deep-link URLs open in an in-app Chrome Custom Tab rather than the user's default browser. Acceptable for many apps, but it's a behavior change for those wanting true "external" semantics.
Both workarounds confirm the launch is fundamentally fine — only MAUI's pre-check is wrong.
Proposed Fixes
Option A — Remove the pre-check, rely on Android's ActivityNotFoundException (recommended; PR follows)
staticvoidLaunchExternalBrowser(BrowserLaunchOptionsoptions,AndroidUri?nativeUri){varintent=newIntent(Intent.ActionView,nativeUri);varflags=ActivityFlags.ClearTop|ActivityFlags.NewTask;
#if __ANDROID_24__if(OperatingSystem.IsAndroidVersionAtLeast(24)){if(options.HasFlag(BrowserLaunchFlags.LaunchAdjacent))flags|=ActivityFlags.LaunchAdjacent;}
#endif
intent.SetFlags(flags);try{Application.Context.StartActivity(intent);}catch(ActivityNotFoundExceptionex){thrownewFeatureNotSupportedException("No activity found to handle URI: "+nativeUri,ex);}}
ActivityNotFoundException is the only authoritative signal that no activity can actually handle the intent — because only the system dispatcher knows, and the system dispatcher ignores caller-side visibility. The public contract (FeatureNotSupportedException thrown when no activity is available) is preserved; we just wrap the real Android exception instead of guessing from a visibility-filtered query.
Option B — Skip the pre-check only for web schemes (matches the maintainer's own suggestion)
varscheme=nativeUri?.Scheme;varisWeb=schemeis"http" or "https";if(!isWeb&&!PlatformUtils.IsIntentSupported(intent))thrownewFeatureNotSupportedException();try{Application.Context.StartActivity(intent);}catch(ActivityNotFoundExceptionex){thrownewFeatureNotSupportedException("No activity found to handle URI: "+nativeUri,ex);}
Rationale: for http/https, a browser is essentially always installed and always visible (browsers have host-less VIEW filters → auto-visible per Android's rules). The only thing the pre-check can filter out for web URLs is an App Link owner — which is exactly the case we want to launch. For non-web schemes (custom-scheme apps), the pre-check is kept as cheap correctness insurance against truly unhandled intents.
This matches @jfversluis's own comment on #27744: "if its http(s) we should just open the browser and not do anything further."
Affected
Platform: Android only (iOS/Mac Catalyst/Windows unaffected).
Android version: API 30+ (package visibility is enforced from Android 11).
MAUI version: reproduced on 10.0.51. The affected code in Browser.android.cs has been unchanged since the initial Essentials port, so all currently-supported MAUI versions are likely affected.
Note on duplicate detection
This is intentionally filed as a new issue rather than a reopen of #20182 / #27744 because:
Both closed-out resolutions point to <queries>, which does not address this case (the report would be lost in the closed-issue triage queue).
The root cause and proposed fix are different from what those threads concluded.
The reproduction scenario (verified App Link owner) is not covered in either of their reproductions.
Happy to consolidate / cross-link if the team would prefer one of those reopened instead.
I'm preparing a PR with Option A; happy to switch to Option B if the team prefers.
Description
Browser.OpenAsync(uri, BrowserLaunchMode.External)throwsFeatureNotSupportedException("Specified method is not supported.") on Android when the URL belongs to a verified App Link owner that the calling app cannot see due to Android 11+ package visibility — even when the documented<queries>declaration forhttp/httpsis correctly applied.This is not the same as #20182 / #27744. Those were closed by pointing users at the
<queries>declaration, which fixes the "fall through to a generic browser" subset of cases. It does not fix the App Link owner case (Instagram, Facebook, Spotify, X, TikTok, Google Maps when it owns a domain, etc.), which is the larger and more user-visible class of failures.Steps to Reproduce
net10.0-android, min SDK 23+.<queries>block toPlatforms/Android/AndroidManifest.xml:Expected: Instagram opens at the target profile (the OS deep-links via verified App Link, the same way Chrome would).
Actual:
Microsoft.Maui.ApplicationModel.FeatureNotSupportedException: Specified method is not supported.Root Cause
In
src/Essentials/src/Browser/Browser.android.cs:PlatformUtils.IsIntentSupported(intent)isintent.ResolveActivity(pm), which is filtered by caller-side package visibility.StartActivity, by contrast, dispatches via the system intent resolver and is not subject to caller-side visibility — it can launch invisible activities; the caller just isn't allowed to query about them ahead of time.So
ResolveActivityreturnsnullin two distinct situations:StartActivitywould also fail (ActivityNotFoundException).StartActivitywould succeed.Today MAUI conflates the two and throws even in case 2, blocking a launch that Android can perform.
Why the
<queries>Fix Doesn't Cover ThisPer Android's auto-visibility rules:
A standard
<queries><intent VIEW + scheme=https></intent></queries>declaration matches generic browsers (Chrome, Firefox, Edge — no host restriction on their VIEW filter) but does not match host-bound App Link owners (IG, FB, Spotify —host="instagram.com", etc.).Empirical confirmation via
adb shell dumpsys package querieson a Pixel running stock Android 14 with the queries block applied:Logcat at IG-link launch time:
The verified App Link owner has exclusive ownership of
instagram.comonce verification succeeds, so Android does not fall back to a visible browser —ResolveActivityreturns null, MAUI throws, nothing launches.Why the Closed Issues Don't Cover This
IBrowser.OpenAsync(<url>, BrowserLaunchMode.External) fails on Android #20182 — closed by the reporter after the
<queries>block fixed their case. Their reproduction was a generic public URL that falls through to a browser. None of the comments mention testing with a verified App Link owner. The fix is real but incomplete.[Essentials] Using Browser.OpenAsync with BrowserLaunchMode.External to open universal Google Maps URL throws FeatureNotSupportedException when Google Maps app disabled on Android #27744 — same root cause, surfaced when the Google Maps app is disabled (so the universal URL has no App Link owner left to take it and should fall through to a browser, but doesn't). @jfversluis acknowledged on that thread:
That code change was never shipped; the issue was closed pointing users at
<queries>.The two closed issues both leave the App Link owner installed and verified case silently broken. That is the case most apps hit in production because it covers nearly every popular consumer app users have installed (IG, FB, Spotify, X, TikTok, WhatsApp click-to-chat, YouTube, Reddit, etc.).
Workarounds (and what they prove)
BrowserLaunchMode.SystemPreferred— works. That path has noIsIntentSupportedpre-check;CustomTabsIntent.LaunchUrlultimately callsContextCompat.startActivitywith anACTION_VIEWintent, the system resolver picks the App Link owner, and IG opens. Side effect: non-deep-link URLs open in an in-app Chrome Custom Tab rather than the user's default browser. Acceptable for many apps, but it's a behavior change for those wanting true "external" semantics.Browseron Android — issuenew Intent(Intent.ActionView, uri).SetFlags(NewTask)+StartActivitydirectly, with try/catch forActivityNotFoundException. Confirmed by the workaround snippet in [Essentials] Using Browser.OpenAsync with BrowserLaunchMode.External to open universal Google Maps URL throws FeatureNotSupportedException when Google Maps app disabled on Android #27744.Both workarounds confirm the launch is fundamentally fine — only MAUI's pre-check is wrong.
Proposed Fixes
Option A — Remove the pre-check, rely on Android's
ActivityNotFoundException(recommended; PR follows)ActivityNotFoundExceptionis the only authoritative signal that no activity can actually handle the intent — because only the system dispatcher knows, and the system dispatcher ignores caller-side visibility. The public contract (FeatureNotSupportedExceptionthrown when no activity is available) is preserved; we just wrap the real Android exception instead of guessing from a visibility-filtered query.Option B — Skip the pre-check only for web schemes (matches the maintainer's own suggestion)
Rationale: for
http/https, a browser is essentially always installed and always visible (browsers have host-less VIEW filters → auto-visible per Android's rules). The only thing the pre-check can filter out for web URLs is an App Link owner — which is exactly the case we want to launch. For non-web schemes (custom-scheme apps), the pre-check is kept as cheap correctness insurance against truly unhandled intents.This matches @jfversluis's own comment on #27744: "if its http(s) we should just open the browser and not do anything further."
Affected
10.0.51. The affected code inBrowser.android.cshas been unchanged since the initial Essentials port, so all currently-supported MAUI versions are likely affected.Note on duplicate detection
This is intentionally filed as a new issue rather than a reopen of #20182 / #27744 because:
<queries>, which does not address this case (the report would be lost in the closed-issue triage queue).Happy to consolidate / cross-link if the team would prefer one of those reopened instead.
I'm preparing a PR with Option A; happy to switch to Option B if the team prefers.