Skip to content

[url_launcher_android] Support intent:// URIs in launchUrl and canLaunchUrl#11913

Draft
sorou3h1 wants to merge 1 commit into
flutter:mainfrom
sorou3h1:fix/url-launcher-android-intent-scheme
Draft

[url_launcher_android] Support intent:// URIs in launchUrl and canLaunchUrl#11913
sorou3h1 wants to merge 1 commit into
flutter:mainfrom
sorou3h1:fix/url-launcher-android-intent-scheme

Conversation

@sorou3h1

Copy link
Copy Markdown

Description

Fixes flutter/flutter#188068

url_launcher_android always constructs new Intent(Intent.ACTION_VIEW) for every URL, silently ignoring the action encoded in intent:// URIs. This means any call like:

await launchUrl(
  Uri.parse('intent://details?id=com.example.app'
    '#Intent;scheme=bazaar;package=com.example.store;action=android.intent.action.EDIT;end'),
  mode: LaunchMode.externalApplication,
);

…fires ACTION_VIEW instead of ACTION_EDIT, breaking the intent.

Fix

Detect intent:// URLs in both canLaunchUrl and launchUrl and delegate to Intent.parseUri(url, Intent.URI_INTENT_SCHEME), which is exactly how Android browsers handle this URI scheme. Non-intent:// URLs take the existing ACTION_VIEW path unchanged.

Tests

Added 4 tests to UrlLauncherTest:

  • canLaunch_parsesIntentSchemeUri — verifies action, data URI, and package are correctly extracted
  • canLaunch_returnsFalseForMalformedIntentSchemeUri — verifies URISyntaxException returns false
  • launch_parsesIntentSchemeUri — same as above for launchUrl
  • launch_returnsFalseForMalformedIntentSchemeUri

Related issue

flutter/flutter#188068

…nchUrl

`intent://` is a standard Android URI scheme used by browsers and apps
to encode arbitrary intents — including a custom action, data URI, and
target package — inside a URL string. Previously, `launchUrl` and
`canLaunchUrl` both created a new `Intent(Intent.ACTION_VIEW)` regardless
of the URL scheme, silently dropping the encoded action and making
`intent://` URIs with a non-VIEW action fail to behave as expected.

This change detects `intent://` URLs and delegates to
`Intent.parseUri(url, Intent.URI_INTENT_SCHEME)`, which correctly
reconstructs the full intent including action, data URI, and package.
Non-intent URLs are unaffected.

Fixes: flutter/flutter#188068
@google-cla

google-cla Bot commented Jun 16, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for parsing and launching intent:// URIs in canLaunchUrl and launchUrl within the Android implementation of url_launcher, along with corresponding unit tests. The review feedback suggests improving the URI scheme matching to be case-insensitive and to support intent: URIs without double slashes using regionMatches. Additionally, the feedback notes that HTTP headers are currently ignored when launching intent URIs and recommends forwarding them.

Comment on lines +68 to +78
Intent launchIntent;
if (url.startsWith("intent://")) {
try {
launchIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
return false;
}
} else {
launchIntent = new Intent(Intent.ACTION_VIEW);
launchIntent.setData(Uri.parse(url));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using url.startsWith("intent://") only matches URIs starting with the exact lowercase prefix and containing //. However, Android intent URIs can start with intent: (without //) and are case-insensitive per RFC 3986. Using url.regionMatches(true, 0, "intent:", 0, 7) is more robust and correctly supports all valid intent URIs.

Suggested change
Intent launchIntent;
if (url.startsWith("intent://")) {
try {
launchIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
return false;
}
} else {
launchIntent = new Intent(Intent.ACTION_VIEW);
launchIntent.setData(Uri.parse(url));
}
Intent launchIntent;
if (url.regionMatches(true, 0, "intent:", 0, 7)) {
try {
launchIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
return false;
}
} else {
launchIntent = new Intent(Intent.ACTION_VIEW);
launchIntent.setData(Uri.parse(url));
}

Comment on lines +97 to +109
Intent launchIntent;
if (url.startsWith("intent://")) {
try {
launchIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
return false;
}
} else {
launchIntent =
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(url))
.putExtra(Browser.EXTRA_HEADERS, extractBundle(headers));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This implementation has two issues:

  1. It only matches intent:// URIs (case-sensitively). It should support any valid intent: URI case-insensitively.
  2. It silently ignores the passed headers when launching an intent: URI. Even for intent URIs, we should preserve and attach the headers so that if the intent resolves to a browser or an app that handles HTTP headers, they are correctly forwarded.

We can fix both issues by using regionMatches and applying the headers to the resulting intent regardless of the scheme.

    Intent launchIntent;
    if (url.regionMatches(true, 0, "intent:", 0, 7)) {
      try {
        launchIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
      } catch (URISyntaxException e) {
        return false;
      }
    } else {
      launchIntent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url));
    }
    launchIntent.putExtra(Browser.EXTRA_HEADERS, extractBundle(headers));

@stuartmorgan-g

Copy link
Copy Markdown
Collaborator

Thanks for the contribution!

In the future, please do not delete the checklist that is in the PR template; it is there for a reason. This PR is missing required elements described in the checklist, which need to be addressed before it moves forward with review.

I am marking the PR as a Draft. Please re-add and complete the checklist, updating the PR as appropriate, and when that’s complete please feel free to mark the PR as ready for review.

@stuartmorgan-g stuartmorgan-g marked this pull request as draft June 17, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[url_launcher_android] intent:// URIs silently use ACTION_VIEW instead of the encoded action

2 participants