Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/url_launcher/url_launcher_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 6.3.22

* Adds support for `externalNonBrowserApplication` on API 30+.

## 6.3.21

* Updates minimum supported SDK version to Flutter 3.35.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Autogenerated from Pigeon (v22.4.1), do not edit directly.
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon

package io.flutter.plugins.urllauncher;
Expand Down Expand Up @@ -298,7 +298,10 @@ public interface UrlLauncherApi {
Boolean canLaunchUrl(@NonNull String url);
/** Opens the URL externally, returning true if successful. */
@NonNull
Boolean launchUrl(@NonNull String url, @NonNull Map<String, String> headers);
Boolean launchUrl(
@NonNull String url,
@NonNull Map<String, String> headers,
@NonNull Boolean requireNonBrowser);
/**
* Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully.
*/
Expand Down Expand Up @@ -367,8 +370,9 @@ static void setUp(
ArrayList<Object> args = (ArrayList<Object>) message;
String urlArg = (String) args.get(0);
Map<String, String> headersArg = (Map<String, String>) args.get(1);
Boolean requireNonBrowserArg = (Boolean) args.get(2);
try {
Boolean output = api.launchUrl(urlArg, headersArg);
Boolean output = api.launchUrl(urlArg, headersArg, requireNonBrowserArg);
wrapped.add(0, output);
} catch (Throwable exception) {
wrapped = wrapError(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Browser;
import android.util.Log;
Expand Down Expand Up @@ -80,14 +81,20 @@ void setActivity(@Nullable Activity activity) {
}

@Override
public @NonNull Boolean launchUrl(@NonNull String url, @NonNull Map<String, String> headers) {
public @NonNull Boolean launchUrl(
@NonNull String url,
@NonNull Map<String, String> headers,
@NonNull Boolean requireNonBrowser) {
ensureActivity();
assert activity != null;

Intent launchIntent =
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(url))
.putExtra(Browser.EXTRA_HEADERS, extractBundle(headers));
if (requireNonBrowser && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
}
try {
activity.startActivity(launchIntent);
} catch (ActivityNotFoundException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
public class UrlLauncherTest {
Expand Down Expand Up @@ -88,7 +89,7 @@ public void launch_throwsForNoCurrentActivity() {
Messages.FlutterError exception =
assertThrows(
Messages.FlutterError.class,
() -> api.launchUrl("https://flutter.dev", new HashMap<>()));
() -> api.launchUrl("https://flutter.dev", new HashMap<>(), false));
assertEquals("NO_ACTIVITY", exception.code);
}

Expand All @@ -100,11 +101,30 @@ public void launch_createsIntentWithPassedUrl() {
api.setActivity(activity);
doThrow(new ActivityNotFoundException()).when(activity).startActivity(any());

api.launchUrl("https://flutter.dev", new HashMap<>());
api.launchUrl("https://flutter.dev", new HashMap<>(), false);

final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(activity).startActivity(intentCaptor.capture());
assertEquals(url, intentCaptor.getValue().getData().toString());
assertEquals(0, intentCaptor.getValue().getFlags() & Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
}

@Config(minSdk = 30)
@Test
public void launch_setsRequireNonBrowserWhenRequested() {
Activity activity = mock(Activity.class);
String url = "https://flutter.dev";
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
api.setActivity(activity);
doThrow(new ActivityNotFoundException()).when(activity).startActivity(any());

api.launchUrl("https://flutter.dev", new HashMap<>(), true);

final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(activity).startActivity(intentCaptor.capture());
assertEquals(
Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER,
intentCaptor.getValue().getFlags() & Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
}

@Test
Expand All @@ -114,7 +134,7 @@ public void launch_returnsFalse() {
api.setActivity(activity);
doThrow(new ActivityNotFoundException()).when(activity).startActivity(any());

boolean result = api.launchUrl("https://flutter.dev", new HashMap<>());
boolean result = api.launchUrl("https://flutter.dev", new HashMap<>(), false);

assertFalse(result);
}
Expand All @@ -125,7 +145,7 @@ public void launch_returnsTrue() {
UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext());
api.setActivity(activity);

boolean result = api.launchUrl("https://flutter.dev", new HashMap<>());
boolean result = api.launchUrl("https://flutter.dev", new HashMap<>(), false);

assertTrue(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ class _MyHomePageState extends State<MyHomePage> {
}
}

Future<void> _launchInNonBrowserExternalApp(String url) async {
if (!await launcher.launchUrl(
url,
const LaunchOptions(
mode: PreferredLaunchMode.externalNonBrowserApplication,
),
)) {
throw Exception('Could not launch $url');
}
}

Future<void> _launchInCustomTab(String url) async {
if (!await launcher.launchUrl(
url,
Expand Down Expand Up @@ -180,18 +191,24 @@ class _MyHomePageState extends State<MyHomePage> {
child: Text(toLaunch),
),
ElevatedButton(
onPressed: _hasCustomTabSupport
? () => setState(() {
_launched = _launchInBrowser(toLaunch);
})
: null,
onPressed: () => setState(() {
_launched = _launchInBrowser(toLaunch);
}),
child: const Text('Launch in browser'),
),
const Padding(padding: EdgeInsets.all(16.0)),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInCustomTab(toLaunch);
_launched = _launchInNonBrowserExternalApp(toLaunch);
}),
child: const Text('Launch in non-browser app'),
),
const Padding(padding: EdgeInsets.all(16.0)),
ElevatedButton(
onPressed: _hasCustomTabSupport
? () => setState(() {
_launched = _launchInCustomTab(toLaunch);
})
: null,
child: const Text('Launch in Android Custom Tab'),
),
const Padding(padding: EdgeInsets.all(16.0)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Autogenerated from Pigeon (v22.4.1), do not edit directly.
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

Expand Down Expand Up @@ -142,7 +142,11 @@ class UrlLauncherApi {
}

/// Opens the URL externally, returning true if successful.
Future<bool> launchUrl(String url, Map<String, String> headers) async {
Future<bool> launchUrl(
String url,
Map<String, String> headers,
bool requireNonBrowser,
) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
Expand All @@ -152,7 +156,8 @@ class UrlLauncherApi {
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[url, headers]) as List<Object?>?;
await pigeonVar_channel.send(<Object?>[url, headers, requireNonBrowser])
as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,16 @@ class UrlLauncherAndroid extends UrlLauncherPlatform {
@override
Future<bool> launchUrl(String url, LaunchOptions options) async {
final bool inApp;
bool requireNonBrowser = false;
switch (options.mode) {
case PreferredLaunchMode.inAppWebView:
case PreferredLaunchMode.inAppBrowserView:
inApp = true;
case PreferredLaunchMode.externalApplication:
inApp = false;
case PreferredLaunchMode.externalNonBrowserApplication:
// TODO(stuartmorgan): Add full support for
// externalNonBrowsingApplication; see
// https://github.com/flutter/flutter/issues/66721.
// Currently it's treated the same as externalApplication.
inApp = false;
requireNonBrowser = true;
case PreferredLaunchMode.platformDefault:
// Intentionally treat any new values as platformDefault; see comment in
// supportsMode.
Expand All @@ -114,6 +113,7 @@ class UrlLauncherAndroid extends UrlLauncherPlatform {
succeeded = await _hostApi.launchUrl(
url,
options.webViewConfiguration.headers,
requireNonBrowser,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ abstract class UrlLauncherApi {
bool canLaunchUrl(String url);

/// Opens the URL externally, returning true if successful.
bool launchUrl(String url, Map<String, String> headers);
bool launchUrl(
String url,
Map<String, String> headers,
bool requireNonBrowser,
);

/// Opens the URL in an in-app Custom Tab or WebView, returning true if it
/// opens successfully.
Expand Down
2 changes: 1 addition & 1 deletion packages/url_launcher/url_launcher_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: url_launcher_android
description: Android implementation of the url_launcher plugin.
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
version: 6.3.21
version: 6.3.22

environment:
sdk: ^3.9.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ void main() {
);
expect(launched, true);
expect(api.usedWebView, false);
expect(api.requiredNonBrowser, false);
expect(api.passedWebViewOptions?.headers, isEmpty);
});

Expand All @@ -254,6 +255,19 @@ void main() {
expect(api.passedWebViewOptions?.headers['key'], 'value');
});

test('passes non-browser flag', () async {
final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api);
final bool launched = await launcher.launchUrl(
'http://example.com/',
const LaunchOptions(
mode: PreferredLaunchMode.externalNonBrowserApplication,
),
);
expect(launched, true);
expect(api.usedWebView, false);
expect(api.requiredNonBrowser, true);
});

test('passes through no-activity exception', () async {
final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api);
await expectLater(
Expand Down Expand Up @@ -484,6 +498,7 @@ class _FakeUrlLauncherApi implements UrlLauncherApi {
BrowserOptions? passedBrowserOptions;
bool? usedWebView;
bool? allowedCustomTab;
bool? requiredNonBrowser;
bool? closed;

/// A domain that will be treated as having no handler, even for http(s).
Expand All @@ -495,13 +510,18 @@ class _FakeUrlLauncherApi implements UrlLauncherApi {
}

@override
Future<bool> launchUrl(String url, Map<String, String> headers) async {
Future<bool> launchUrl(
String url,
Map<String, String> headers,
bool requireNonBrowser,
) async {
passedWebViewOptions = WebViewOptions(
enableJavaScript: false,
enableDomStorage: false,
headers: headers,
);

requiredNonBrowser = requireNonBrowser;
usedWebView = false;
return _launch(url);
}
Expand Down