[url_launcher] Handling the ActivityNotFoundExeption. (#3125)

Catch ActivityNotFoundException and report an error back to the Dart side.
diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index 995d64c..ab636c6 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 5.7.8
+
+* Fixed a situation where an app would crash if the url_launcher’s `launch` method can’t find an app to open the provided url. It will now throw a clear Dart PlatformException.
+
 ## 5.7.7
 
 * Introduce the Link widget with an implementation for native platforms.
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
index 0b90dfa..2ca6b7c 100644
--- 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
@@ -92,6 +92,11 @@
 
     if (launchStatus == LaunchStatus.NO_ACTIVITY) {
       result.error("NO_ACTIVITY", "Launching a URL requires a foreground activity.", null);
+    } else if (launchStatus == LaunchStatus.ACTIVITY_NOT_FOUND) {
+      result.error(
+          "ACTIVITY_NOT_FOUND",
+          String.format("No Activity found to handle intent { %s }", url),
+          null);
     } else {
       result.success(true);
     }
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
index 40f2a51..44ecc33 100644
--- 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
@@ -1,6 +1,7 @@
 package io.flutter.plugins.urllauncher;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -48,7 +49,8 @@
    * @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.
+   *     {@link LaunchStatus#ACTIVITY_NOT_FOUND} if there's no activity found to handle {@code
+   *     launchIntent}. {@link LaunchStatus#OK} otherwise.
    */
   LaunchStatus launch(
       String url,
@@ -72,7 +74,12 @@
               .putExtra(Browser.EXTRA_HEADERS, headersBundle);
     }
 
-    activity.startActivity(launchIntent);
+    try {
+      activity.startActivity(launchIntent);
+    } catch (ActivityNotFoundException e) {
+      return LaunchStatus.ACTIVITY_NOT_FOUND;
+    }
+
     return LaunchStatus.OK;
   }
 
@@ -87,5 +94,7 @@
     OK,
     /** No activity was found to launch. */
     NO_ACTIVITY,
+    /** No Activity found that can handle given intent. */
+    ACTIVITY_NOT_FOUND,
   }
 }
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
index 63ce46f..e759ded 100644
--- 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
@@ -8,6 +8,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.Bundle;
 import androidx.test.core.app.ApplicationProvider;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
@@ -106,6 +107,95 @@
   }
 
   @Test
+  public void onMethodCall_launchReturnsNoActivityError() {
+    // Setup mock objects
+    urlLauncher = mock(UrlLauncher.class);
+    Result result = mock(Result.class);
+    // Setup expected values
+    String url = "foo";
+    boolean useWebView = false;
+    boolean enableJavaScript = false;
+    boolean enableDomStorage = false;
+    // Setup arguments map send on the method channel
+    Map<String, Object> args = new HashMap<>();
+    args.put("url", url);
+    args.put("useWebView", useWebView);
+    args.put("enableJavaScript", enableJavaScript);
+    args.put("enableDomStorage", enableDomStorage);
+    args.put("headers", new HashMap<>());
+    // Mock the launch method on the urlLauncher class
+    when(urlLauncher.launch(
+            eq(url), any(Bundle.class), eq(useWebView), eq(enableJavaScript), eq(enableDomStorage)))
+        .thenReturn(UrlLauncher.LaunchStatus.NO_ACTIVITY);
+    // Act by calling the "launch" method on the method channel
+    methodCallHandler = new MethodCallHandlerImpl(urlLauncher);
+    methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+    // Verify the results and assert
+    verify(result, times(1))
+        .error("NO_ACTIVITY", "Launching a URL requires a foreground activity.", null);
+  }
+
+  @Test
+  public void onMethodCall_launchReturnsActivityNotFoundError() {
+    // Setup mock objects
+    urlLauncher = mock(UrlLauncher.class);
+    Result result = mock(Result.class);
+    // Setup expected values
+    String url = "foo";
+    boolean useWebView = false;
+    boolean enableJavaScript = false;
+    boolean enableDomStorage = false;
+    // Setup arguments map send on the method channel
+    Map<String, Object> args = new HashMap<>();
+    args.put("url", url);
+    args.put("useWebView", useWebView);
+    args.put("enableJavaScript", enableJavaScript);
+    args.put("enableDomStorage", enableDomStorage);
+    args.put("headers", new HashMap<>());
+    // Mock the launch method on the urlLauncher class
+    when(urlLauncher.launch(
+            eq(url), any(Bundle.class), eq(useWebView), eq(enableJavaScript), eq(enableDomStorage)))
+        .thenReturn(UrlLauncher.LaunchStatus.ACTIVITY_NOT_FOUND);
+    // Act by calling the "launch" method on the method channel
+    methodCallHandler = new MethodCallHandlerImpl(urlLauncher);
+    methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+    // Verify the results and assert
+    verify(result, times(1))
+        .error(
+            "ACTIVITY_NOT_FOUND",
+            String.format("No Activity found to handle intent { %s }", url),
+            null);
+  }
+
+  @Test
+  public void onMethodCall_launchReturnsTrue() {
+    // Setup mock objects
+    urlLauncher = mock(UrlLauncher.class);
+    Result result = mock(Result.class);
+    // Setup expected values
+    String url = "foo";
+    boolean useWebView = false;
+    boolean enableJavaScript = false;
+    boolean enableDomStorage = false;
+    // Setup arguments map send on the method channel
+    Map<String, Object> args = new HashMap<>();
+    args.put("url", url);
+    args.put("useWebView", useWebView);
+    args.put("enableJavaScript", enableJavaScript);
+    args.put("enableDomStorage", enableDomStorage);
+    args.put("headers", new HashMap<>());
+    // Mock the launch method on the urlLauncher class
+    when(urlLauncher.launch(
+            eq(url), any(Bundle.class), eq(useWebView), eq(enableJavaScript), eq(enableDomStorage)))
+        .thenReturn(UrlLauncher.LaunchStatus.OK);
+    // Act by calling the "launch" method on the method channel
+    methodCallHandler = new MethodCallHandlerImpl(urlLauncher);
+    methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
+    // Verify the results and assert
+    verify(result, times(1)).success(true);
+  }
+
+  @Test
   public void onMethodCall_closeWebView() {
     urlLauncher = mock(UrlLauncher.class);
     methodCallHandler = new MethodCallHandlerImpl(urlLauncher);
diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml
index cf837b2..7ffc20c 100644
--- a/packages/url_launcher/url_launcher/pubspec.yaml
+++ b/packages/url_launcher/url_launcher/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for launching a URL on Android and iOS. Supports
   web, phone, SMS, and email schemes.
 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher
-version: 5.7.7
+version: 5.7.8
 
 flutter:
   plugin: