[url_launcher_android] Support intent:// URIs in launchUrl and canLaunchUrl#11913
[url_launcher_android] Support intent:// URIs in launchUrl and canLaunchUrl#11913sorou3h1 wants to merge 1 commit into
Conversation
…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
|
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. |
There was a problem hiding this comment.
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.
| 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)); | ||
| } |
There was a problem hiding this comment.
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.
| 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)); | |
| } |
| 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)); | ||
| } |
There was a problem hiding this comment.
This implementation has two issues:
- It only matches
intent://URIs (case-sensitively). It should support any validintent:URI case-insensitively. - It silently ignores the passed
headerswhen launching anintent: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));|
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. |
Description
Fixes flutter/flutter#188068
url_launcher_androidalways constructsnew Intent(Intent.ACTION_VIEW)for every URL, silently ignoring the action encoded inintent://URIs. This means any call like:…fires
ACTION_VIEWinstead ofACTION_EDIT, breaking the intent.Fix
Detect
intent://URLs in bothcanLaunchUrlandlaunchUrland delegate toIntent.parseUri(url, Intent.URI_INTENT_SCHEME), which is exactly how Android browsers handle this URI scheme. Non-intent://URLs take the existingACTION_VIEWpath unchanged.Tests
Added 4 tests to
UrlLauncherTest:canLaunch_parsesIntentSchemeUri— verifies action, data URI, and package are correctly extractedcanLaunch_returnsFalseForMalformedIntentSchemeUri— verifiesURISyntaxExceptionreturnsfalselaunch_parsesIntentSchemeUri— same as above forlaunchUrllaunch_returnsFalseForMalformedIntentSchemeUriRelated issue
flutter/flutter#188068