diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 67b6ed670c91..9893c4f8931d 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.2.2 + +* Re-land embedder v2 support with correct Flutter SDK constraints. + ## 5.2.1 * Revert the migration since the Flutter dependency was too low. diff --git a/packages/url_launcher/url_launcher/android/build.gradle b/packages/url_launcher/url_launcher/android/build.gradle index e2c6ea5af7ae..72142a04114e 100644 --- a/packages/url_launcher/url_launcher/android/build.gradle +++ b/packages/url_launcher/url_launcher/android/build.gradle @@ -44,4 +44,40 @@ android { lintOptions { disable 'InvalidPackage' } + testOptions { + unitTests.includeAndroidResources = true + } +} + +dependencies { + compileOnly 'androidx.annotation:annotation:1.0.0' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:1.10.19' + testImplementation 'androidx.test:core:1.0.0' + testImplementation 'org.robolectric:robolectric:4.3' +} + +// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "2.1.0" + api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" + } + } + } } \ No newline at end of file diff --git a/packages/url_launcher/url_launcher/android/src/main/AndroidManifest.xml b/packages/url_launcher/url_launcher/android/src/main/AndroidManifest.xml index f43e5ba2474d..175c99e594b3 100644 --- a/packages/url_launcher/url_launcher/android/src/main/AndroidManifest.xml +++ b/packages/url_launcher/url_launcher/android/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - diff --git a/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..0b90dfaf32bc --- /dev/null +++ b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java @@ -0,0 +1,113 @@ +package io.flutter.plugins.urllauncher; + +import android.os.Bundle; +import android.util.Log; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugins.urllauncher.UrlLauncher.LaunchStatus; +import java.util.Map; + +/** + * Translates incoming UrlLauncher MethodCalls into well formed Java function calls for {@link + * UrlLauncher}. + */ +final class MethodCallHandlerImpl implements MethodCallHandler { + private static final String TAG = "MethodCallHandlerImpl"; + private final UrlLauncher urlLauncher; + @Nullable private MethodChannel channel; + + /** Forwards all incoming MethodChannel calls to the given {@code urlLauncher}. */ + MethodCallHandlerImpl(UrlLauncher urlLauncher) { + this.urlLauncher = urlLauncher; + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + final String url = call.argument("url"); + switch (call.method) { + case "canLaunch": + onCanLaunch(result, url); + break; + case "launch": + onLaunch(call, result, url); + break; + case "closeWebView": + onCloseWebView(result); + break; + default: + result.notImplemented(); + break; + } + } + + /** + * Registers this instance as a method call handler on the given {@code messenger}. + * + *

Stops any previously started and unstopped calls. + * + *

This should be cleaned with {@link #stopListening} once the messenger is disposed of. + */ + void startListening(BinaryMessenger messenger) { + if (channel != null) { + Log.wtf(TAG, "Setting a method call handler before the last was disposed."); + stopListening(); + } + + channel = new MethodChannel(messenger, "plugins.flutter.io/url_launcher"); + channel.setMethodCallHandler(this); + } + + /** + * Clears this instance from listening to method calls. + * + *

Does nothing if {@link #startListening} hasn't been called, or if we're already stopped. + */ + void stopListening() { + if (channel == null) { + Log.d(TAG, "Tried to stop listening when no MethodChannel had been initialized."); + return; + } + + channel.setMethodCallHandler(null); + channel = null; + } + + private void onCanLaunch(Result result, String url) { + result.success(urlLauncher.canLaunch(url)); + } + + private void onLaunch(MethodCall call, Result result, String url) { + final boolean useWebView = call.argument("useWebView"); + final boolean enableJavaScript = call.argument("enableJavaScript"); + final boolean enableDomStorage = call.argument("enableDomStorage"); + final Map headersMap = call.argument("headers"); + final Bundle headersBundle = extractBundle(headersMap); + + LaunchStatus launchStatus = + urlLauncher.launch(url, headersBundle, useWebView, enableJavaScript, enableDomStorage); + + if (launchStatus == LaunchStatus.NO_ACTIVITY) { + result.error("NO_ACTIVITY", "Launching a URL requires a foreground activity.", null); + } else { + result.success(true); + } + } + + private void onCloseWebView(Result result) { + urlLauncher.closeWebView(); + result.success(null); + } + + private static Bundle extractBundle(Map headersMap) { + final Bundle headersBundle = new Bundle(); + for (String key : headersMap.keySet()) { + final String value = headersMap.get(key); + headersBundle.putString(key, value); + } + return headersBundle; + } +} diff --git a/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java new file mode 100644 index 000000000000..40f2a51f1db2 --- /dev/null +++ b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java @@ -0,0 +1,91 @@ +package io.flutter.plugins.urllauncher; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Browser; +import androidx.annotation.Nullable; + +/** Launches components for URLs. */ +class UrlLauncher { + private final Context applicationContext; + @Nullable private Activity activity; + + /** + * Uses the given {@code applicationContext} for launching intents. + * + *

It may be null initially, but should be set before calling {@link #launch}. + */ + UrlLauncher(Context applicationContext, @Nullable Activity activity) { + this.applicationContext = applicationContext; + this.activity = activity; + } + + void setActivity(@Nullable Activity activity) { + this.activity = activity; + } + + /** Returns whether the given {@code url} resolves into an existing component. */ + boolean canLaunch(String url) { + Intent launchIntent = new Intent(Intent.ACTION_VIEW); + launchIntent.setData(Uri.parse(url)); + ComponentName componentName = + launchIntent.resolveActivity(applicationContext.getPackageManager()); + + return componentName != null + && !"{com.android.fallback/com.android.fallback.Fallback}" + .equals(componentName.toShortString()); + } + + /** + * Attempts to launch the given {@code url}. + * + * @param headersBundle forwarded to the intent as {@code Browser.EXTRA_HEADERS}. + * @param useWebView when true, the URL is launched inside of {@link WebViewActivity}. + * @param enableJavaScript Only used if {@param useWebView} is true. Enables JS in the WebView. + * @param enableDomStorage Only used if {@param useWebView} is true. Enables DOM storage in the + * @return {@link LaunchStatus#NO_ACTIVITY} if there's no available {@code applicationContext}. + * {@link LaunchStatus#OK} otherwise. + */ + LaunchStatus launch( + String url, + Bundle headersBundle, + boolean useWebView, + boolean enableJavaScript, + boolean enableDomStorage) { + if (activity == null) { + return LaunchStatus.NO_ACTIVITY; + } + + Intent launchIntent; + if (useWebView) { + launchIntent = + WebViewActivity.createIntent( + activity, url, enableJavaScript, enableDomStorage, headersBundle); + } else { + launchIntent = + new Intent(Intent.ACTION_VIEW) + .setData(Uri.parse(url)) + .putExtra(Browser.EXTRA_HEADERS, headersBundle); + } + + activity.startActivity(launchIntent); + return LaunchStatus.OK; + } + + /** Closes any activities started with {@link #launch} {@code useWebView=true}. */ + void closeWebView() { + applicationContext.sendBroadcast(new Intent(WebViewActivity.ACTION_CLOSE)); + } + + /** Result of a {@link #launch} call. */ + enum LaunchStatus { + /** The intent was well formed. */ + OK, + /** No activity was found to launch. */ + NO_ACTIVITY, + } +} diff --git a/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java index b2b0eb906952..f322e69c8cdf 100644 --- a/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java +++ b/packages/url_launcher/url_launcher/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java @@ -1,110 +1,82 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - package io.flutter.plugins.urllauncher; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Browser; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.util.Map; -/** UrlLauncherPlugin */ -public class UrlLauncherPlugin implements MethodCallHandler { - private final Registrar mRegistrar; +/** + * Plugin implementation that uses the new {@code io.flutter.embedding} package. + * + *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. + */ +public final class UrlLauncherPlugin implements FlutterPlugin, ActivityAware { + private static final String TAG = "UrlLauncherPlugin"; + @Nullable private MethodCallHandlerImpl methodCallHandler; + @Nullable private UrlLauncher urlLauncher; + /** + * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} + * package. + * + *

Calling this automatically initializes the plugin. However plugins initialized this way + * won't react to changes in activity or context, unlike {@link UrlLauncherPlugin}. + */ public static void registerWith(Registrar registrar) { - MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/url_launcher"); - UrlLauncherPlugin instance = new UrlLauncherPlugin(registrar); - channel.setMethodCallHandler(instance); + MethodCallHandlerImpl handler = + new MethodCallHandlerImpl(new UrlLauncher(registrar.context(), registrar.activity())); + handler.startListening(registrar.messenger()); } - private UrlLauncherPlugin(Registrar registrar) { - this.mRegistrar = registrar; + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + urlLauncher = new UrlLauncher(binding.getApplicationContext(), /*activity=*/ null); + methodCallHandler = new MethodCallHandlerImpl(urlLauncher); + methodCallHandler.startListening(binding.getFlutterEngine().getDartExecutor()); } @Override - public void onMethodCall(MethodCall call, Result result) { - final String url = call.argument("url"); - switch (call.method) { - case "canLaunch": - canLaunch(url, result); - break; - case "launch": - launch(call, result, url); - break; - case "closeWebView": - closeWebView(result); - break; - default: - result.notImplemented(); - break; + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + if (methodCallHandler == null) { + Log.wtf(TAG, "Already detached from the engine."); + return; } - } - private void canLaunch(String url, Result result) { - Intent launchIntent = new Intent(Intent.ACTION_VIEW); - launchIntent.setData(Uri.parse(url)); - ComponentName componentName = - launchIntent.resolveActivity(mRegistrar.context().getPackageManager()); - - boolean canLaunch = - componentName != null - && !"{com.android.fallback/com.android.fallback.Fallback}" - .equals(componentName.toShortString()); - result.success(canLaunch); + methodCallHandler.stopListening(); + methodCallHandler = null; + urlLauncher = null; } - private void launch(MethodCall call, Result result, String url) { - Intent launchIntent; - final boolean useWebView = call.argument("useWebView"); - final boolean enableJavaScript = call.argument("enableJavaScript"); - final boolean enableDomStorage = call.argument("enableDomStorage"); - final Map headersMap = call.argument("headers"); - final Bundle headersBundle = extractBundle(headersMap); - final Context context = mRegistrar.activity(); - - if (context == null) { - result.error("NO_ACTIVITY", "Launching a URL requires a foreground activity.", null); + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + if (methodCallHandler == null) { + Log.wtf(TAG, "urlLauncher was never set."); return; } - if (useWebView) { - launchIntent = - WebViewActivity.createIntent( - context, url, enableJavaScript, enableDomStorage, headersBundle); - } else { - launchIntent = - new Intent(Intent.ACTION_VIEW) - .setData(Uri.parse(url)) - .putExtra(Browser.EXTRA_HEADERS, headersBundle); + urlLauncher.setActivity(binding.getActivity()); + } + + @Override + public void onDetachedFromActivity() { + if (methodCallHandler == null) { + Log.wtf(TAG, "urlLauncher was never set."); + return; } - context.startActivity(launchIntent); - result.success(true); + urlLauncher.setActivity(null); } - private void closeWebView(Result result) { - Intent intent = new Intent(WebViewActivity.ACTION_CLOSE); - mRegistrar.context().sendBroadcast(intent); - result.success(null); + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } - private Bundle extractBundle(Map headersMap) { - final Bundle headersBundle = new Bundle(); - for (String key : headersMap.keySet()) { - final String value = headersMap.get(key); - headersBundle.putString(key, value); - } - return headersBundle; + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); } } diff --git a/packages/url_launcher/url_launcher/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java b/packages/url_launcher/url_launcher/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java new file mode 100644 index 000000000000..63ce46f6d0cb --- /dev/null +++ b/packages/url_launcher/url_launcher/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java @@ -0,0 +1,123 @@ +package io.flutter.plugins.urllauncher; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.test.core.app.ApplicationProvider; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel.Result; +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class MethodCallHandlerImplTest { + private static final String CHANNEL_NAME = "plugins.flutter.io/url_launcher"; + private UrlLauncher urlLauncher; + private MethodCallHandlerImpl methodCallHandler; + + @Before + public void setUp() { + urlLauncher = new UrlLauncher(ApplicationProvider.getApplicationContext(), /*activity=*/ null); + methodCallHandler = new MethodCallHandlerImpl(urlLauncher); + } + + @Test + public void startListening_registersChannel() { + BinaryMessenger messenger = mock(BinaryMessenger.class); + + methodCallHandler.startListening(messenger); + + verify(messenger, times(1)) + .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class)); + } + + @Test + public void startListening_unregistersExistingChannel() { + BinaryMessenger firstMessenger = mock(BinaryMessenger.class); + BinaryMessenger secondMessenger = mock(BinaryMessenger.class); + methodCallHandler.startListening(firstMessenger); + + methodCallHandler.startListening(secondMessenger); + + // Unregisters the first and then registers the second. + verify(firstMessenger, times(1)).setMessageHandler(CHANNEL_NAME, null); + verify(secondMessenger, times(1)) + .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class)); + } + + @Test + public void stopListening_unregistersExistingChannel() { + BinaryMessenger messenger = mock(BinaryMessenger.class); + methodCallHandler.startListening(messenger); + + methodCallHandler.stopListening(); + + verify(messenger, times(1)).setMessageHandler(CHANNEL_NAME, null); + } + + @Test + public void stopListening_doesNothingWhenUnset() { + BinaryMessenger messenger = mock(BinaryMessenger.class); + + methodCallHandler.stopListening(); + + verify(messenger, never()).setMessageHandler(CHANNEL_NAME, null); + } + + @Test + public void onMethodCall_canLaunchReturnsTrue() { + urlLauncher = mock(UrlLauncher.class); + methodCallHandler = new MethodCallHandlerImpl(urlLauncher); + String url = "foo"; + when(urlLauncher.canLaunch(url)).thenReturn(true); + Result result = mock(Result.class); + Map args = new HashMap<>(); + args.put("url", url); + + methodCallHandler.onMethodCall(new MethodCall("canLaunch", args), result); + + verify(result, times(1)).success(true); + } + + @Test + public void onMethodCall_canLaunchReturnsFalse() { + urlLauncher = mock(UrlLauncher.class); + methodCallHandler = new MethodCallHandlerImpl(urlLauncher); + String url = "foo"; + when(urlLauncher.canLaunch(url)).thenReturn(false); + Result result = mock(Result.class); + Map args = new HashMap<>(); + args.put("url", url); + + methodCallHandler.onMethodCall(new MethodCall("canLaunch", args), result); + + verify(result, times(1)).success(false); + } + + @Test + public void onMethodCall_closeWebView() { + urlLauncher = mock(UrlLauncher.class); + methodCallHandler = new MethodCallHandlerImpl(urlLauncher); + String url = "foo"; + when(urlLauncher.canLaunch(url)).thenReturn(true); + Result result = mock(Result.class); + Map args = new HashMap<>(); + args.put("url", url); + + methodCallHandler.onMethodCall(new MethodCall("closeWebView", args), result); + + verify(urlLauncher, times(1)).closeWebView(); + verify(result, times(1)).success(null); + } +} diff --git a/packages/url_launcher/url_launcher/example/android/app/build.gradle b/packages/url_launcher/url_launcher/example/android/app/build.gradle index 325396c57235..7a6cf5df0d33 100644 --- a/packages/url_launcher/url_launcher/example/android/app/build.gradle +++ b/packages/url_launcher/url_launcher/example/android/app/build.gradle @@ -55,6 +55,6 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/packages/url_launcher/url_launcher/example/android/app/src/androidTestDebug/java/io/flutter/plugins/urllauncherexample/EmbeddingV1ActivityTest.java b/packages/url_launcher/url_launcher/example/android/app/src/androidTestDebug/java/io/flutter/plugins/urllauncherexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..bae2957ab81e --- /dev/null +++ b/packages/url_launcher/url_launcher/example/android/app/src/androidTestDebug/java/io/flutter/plugins/urllauncherexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.urllauncherexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/url_launcher/url_launcher/example/android/app/src/androidTestDebug/java/io/flutter/plugins/urllauncherexample/MainActivityTest.java b/packages/url_launcher/url_launcher/example/android/app/src/androidTestDebug/java/io/flutter/plugins/urllauncherexample/MainActivityTest.java new file mode 100644 index 000000000000..4dda10f32621 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/android/app/src/androidTestDebug/java/io/flutter/plugins/urllauncherexample/MainActivityTest.java @@ -0,0 +1,11 @@ +package io.flutter.plugins.urllauncherexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml index 308457dc2c45..d30c15fd9ea8 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml @@ -1,28 +1,39 @@ + package="io.flutter.plugins.urllauncherexample"> - - + + + + + + + + + + + - - - - - - - - - + + diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/EmbeddingV1Activity.java b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..e52ccfc18b47 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/EmbeddingV1Activity.java @@ -0,0 +1,18 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.urllauncherexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java index 87478bfa27df..76f7df752842 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java @@ -1,18 +1,12 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - package io.flutter.plugins.urllauncherexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.urllauncher.UrlLauncherPlugin; public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new UrlLauncherPlugin()); } } diff --git a/packages/url_launcher/url_launcher/example/android/gradle.properties b/packages/url_launcher/url_launcher/example/android/gradle.properties index 7be3d8b46841..a6738207fd15 100644 --- a/packages/url_launcher/url_launcher/example/android/gradle.properties +++ b/packages/url_launcher/url_launcher/example/android/gradle.properties @@ -1,2 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true android.enableR8=true diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index 99aaf27cff78..4d9d01aa9b5e 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -7,5 +7,10 @@ dependencies: url_launcher: path: ../ +dev_dependencies: + e2e: "^0.2.0" + flutter_driver: + sdk: flutter + flutter: uses-material-design: true diff --git a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart new file mode 100644 index 000000000000..e1d75f93b326 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart @@ -0,0 +1,24 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:e2e/e2e.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + test('canLaunch', () async { + expect(await canLaunch('randomstring'), false); + + // Generally all devices should have some default browser. + expect(await canLaunch('http://flutter.dev'), true); + + // Generally all devices should have some default SMS app. + expect(await canLaunch('sms:5555555555'), true); + + // tel: and mailto: links may not be openable on every device. iOS + // simulators notably can't open these link types. + }); +} diff --git a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart new file mode 100644 index 000000000000..ac4ea11482e2 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart @@ -0,0 +1,15 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index fca3557850ab..6f882fbc740a 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL on Android and iOS. Supports web, phone, SMS, and email schemes. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher -version: 5.2.1 +version: 5.2.2 flutter: plugin: @@ -21,4 +21,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0"