[url_launcher_linux] Add Linux url_launcher plugin (#2857)

Adds url_launcher_linux, the federated implementation of url_launcher.

Not yet endorsed by url_launcher

Part of https://github.com/flutter/flutter/issues/41721
diff --git a/.ci/Dockerfile-LinuxDesktop b/.ci/Dockerfile-LinuxDesktop
index 25924d4..63e4516 100644
--- a/.ci/Dockerfile-LinuxDesktop
+++ b/.ci/Dockerfile-LinuxDesktop
@@ -21,3 +21,11 @@
 RUN sudo apt-get install -y clang cmake ninja-build file pkg-config
 # Install necessary libraries.
 RUN sudo apt-get install -y libgtk-3-dev
+
+# Add repo for Google Chrome and install it, for url_launcher tests.
+RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
+RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list
+RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends google-chrome-stable
+# Make it the default so http: has a handler.
+RUN sudo apt-get install -y xdg-utils
+RUN xdg-settings set default-web-browser google-chrome.desktop
diff --git a/packages/url_launcher/url_launcher_linux/.gitignore b/packages/url_launcher/url_launcher_linux/.gitignore
new file mode 100644
index 0000000..53e92cc
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/.gitignore
@@ -0,0 +1,3 @@
+.packages
+.flutter-plugins
+pubspec.lock
diff --git a/packages/url_launcher/url_launcher_linux/.metadata b/packages/url_launcher/url_launcher_linux/.metadata
new file mode 100644
index 0000000..457a92a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: 4b12050112afd581ddf53df848275fa681f908f3
+  channel: master
+
+project_type: plugin
diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md
new file mode 100644
index 0000000..18ba82a
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md
@@ -0,0 +1,2 @@
+# 0.0.1
+* The initial implementation of url_launcher for Linux
diff --git a/packages/url_launcher/url_launcher_linux/LICENSE b/packages/url_launcher/url_launcher_linux/LICENSE
new file mode 100644
index 0000000..0c91662
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/url_launcher/url_launcher_linux/README.md b/packages/url_launcher/url_launcher_linux/README.md
new file mode 100644
index 0000000..9b16bdf
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/README.md
@@ -0,0 +1,22 @@
+# url_launcher_linux
+
+The Linux implementation of [`url_launcher`][1].
+
+## Usage
+
+### Import the package
+
+This package is an unendorsed Linux implementation of `url_launcher`.
+
+In order to use this now, you'll need to depend on `url_launcher_linux`.
+When this package is endorsed it will be automatically used by the `url_launcher` package and you can switch to that API.
+
+```yaml
+...
+dependencies:
+  ...
+  url_launcher_linux: ^0.0.1
+  ...
+```
+
+[1]: ../
diff --git a/packages/url_launcher/url_launcher_linux/example/.gitignore b/packages/url_launcher/url_launcher_linux/example/.gitignore
new file mode 100644
index 0000000..f3c2053
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Exceptions to above rules.
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/packages/url_launcher/url_launcher_linux/example/.metadata b/packages/url_launcher/url_launcher_linux/example/.metadata
new file mode 100644
index 0000000..99b1a74
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: 4b12050112afd581ddf53df848275fa681f908f3
+  channel: master
+
+project_type: app
diff --git a/packages/url_launcher/url_launcher_linux/example/README.md b/packages/url_launcher/url_launcher_linux/example/README.md
new file mode 100644
index 0000000..28dd90d
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/README.md
@@ -0,0 +1,8 @@
+# url_launcher_example
+
+Demonstrates how to use the url_launcher plugin.
+
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](http://flutter.io/).
diff --git a/packages/url_launcher/url_launcher_linux/example/lib/main.dart b/packages/url_launcher/url_launcher_linux/example/lib/main.dart
new file mode 100644
index 0000000..b5cce74
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/lib/main.dart
@@ -0,0 +1,194 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:async';
+import 'package:flutter/material.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+void main() {
+  runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'URL Launcher',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+      ),
+      home: MyHomePage(title: 'URL Launcher'),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  MyHomePage({Key key, this.title}) : super(key: key);
+  final String title;
+
+  @override
+  _MyHomePageState createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  Future<void> _launched;
+  String _phone = '';
+
+  Future<void> _launchInBrowser(String url) async {
+    if (await canLaunch(url)) {
+      await launch(
+        url,
+        forceSafariVC: false,
+        forceWebView: false,
+        headers: <String, String>{'my_header_key': 'my_header_value'},
+      );
+    } else {
+      throw 'Could not launch $url';
+    }
+  }
+
+  Future<void> _launchInWebViewOrVC(String url) async {
+    if (await canLaunch(url)) {
+      await launch(
+        url,
+        forceSafariVC: true,
+        forceWebView: true,
+        headers: <String, String>{'my_header_key': 'my_header_value'},
+      );
+    } else {
+      throw 'Could not launch $url';
+    }
+  }
+
+  Future<void> _launchInWebViewWithJavaScript(String url) async {
+    if (await canLaunch(url)) {
+      await launch(
+        url,
+        forceSafariVC: true,
+        forceWebView: true,
+        enableJavaScript: true,
+      );
+    } else {
+      throw 'Could not launch $url';
+    }
+  }
+
+  Future<void> _launchInWebViewWithDomStorage(String url) async {
+    if (await canLaunch(url)) {
+      await launch(
+        url,
+        forceSafariVC: true,
+        forceWebView: true,
+        enableDomStorage: true,
+      );
+    } else {
+      throw 'Could not launch $url';
+    }
+  }
+
+  Future<void> _launchUniversalLinkIos(String url) async {
+    if (await canLaunch(url)) {
+      final bool nativeAppLaunchSucceeded = await launch(
+        url,
+        forceSafariVC: false,
+        universalLinksOnly: true,
+      );
+      if (!nativeAppLaunchSucceeded) {
+        await launch(
+          url,
+          forceSafariVC: true,
+        );
+      }
+    }
+  }
+
+  Widget _launchStatus(BuildContext context, AsyncSnapshot<void> snapshot) {
+    if (snapshot.hasError) {
+      return Text('Error: ${snapshot.error}');
+    } else {
+      return const Text('');
+    }
+  }
+
+  Future<void> _makePhoneCall(String url) async {
+    if (await canLaunch(url)) {
+      await launch(url);
+    } else {
+      throw 'Could not launch $url';
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    const String toLaunch = 'https://www.cylog.org/headers/';
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(widget.title),
+      ),
+      body: ListView(
+        children: <Widget>[
+          Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: <Widget>[
+              Padding(
+                padding: const EdgeInsets.all(16.0),
+                child: TextField(
+                    onChanged: (String text) => _phone = text,
+                    decoration: const InputDecoration(
+                        hintText: 'Input the phone number to launch')),
+              ),
+              RaisedButton(
+                onPressed: () => setState(() {
+                  _launched = _makePhoneCall('tel:$_phone');
+                }),
+                child: const Text('Make phone call'),
+              ),
+              const Padding(
+                padding: EdgeInsets.all(16.0),
+                child: Text(toLaunch),
+              ),
+              RaisedButton(
+                onPressed: () => setState(() {
+                  _launched = _launchInBrowser(toLaunch);
+                }),
+                child: const Text('Launch in browser'),
+              ),
+              const Padding(padding: EdgeInsets.all(16.0)),
+              RaisedButton(
+                onPressed: () => setState(() {
+                  _launched = _launchInWebViewOrVC(toLaunch);
+                }),
+                child: const Text('Launch in app'),
+              ),
+              RaisedButton(
+                onPressed: () => setState(() {
+                  _launched = _launchInWebViewWithJavaScript(toLaunch);
+                }),
+                child: const Text('Launch in app(JavaScript ON)'),
+              ),
+              RaisedButton(
+                onPressed: () => setState(() {
+                  _launched = _launchInWebViewWithDomStorage(toLaunch);
+                }),
+                child: const Text('Launch in app(DOM storage ON)'),
+              ),
+              const Padding(padding: EdgeInsets.all(16.0)),
+              RaisedButton(
+                onPressed: () => setState(() {
+                  _launched = _launchUniversalLinkIos(toLaunch);
+                }),
+                child: const Text(
+                    'Launch a universal link in a native app, fallback to Safari.(Youtube)'),
+              ),
+              const Padding(padding: EdgeInsets.all(16.0)),
+              FutureBuilder<void>(future: _launched, builder: _launchStatus),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/.gitignore b/packages/url_launcher/url_launcher_linux/example/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt
new file mode 100644
index 0000000..0236a88
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "example")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+# Application build
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..94f43ff
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt
@@ -0,0 +1,86 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      linux-x64 ${CMAKE_BUILD_TYPE}
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..026851f
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+#include <url_launcher_linux/url_launcher_plugin.h>
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+  url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..9bf7478
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..1fc8ed3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,16 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  url_launcher_linux
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/main.cc b/packages/url_launcher/url_launcher_linux/example/linux/main.cc
new file mode 100644
index 0000000..e7c5c54
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc b/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc
new file mode 100644
index 0000000..c2357f1
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc
@@ -0,0 +1,44 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+  GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+  gtk_widget_show(GTK_WIDGET(header_bar));
+  gtk_header_bar_set_title(header_bar, "example");
+  gtk_header_bar_set_show_close_button(header_bar, TRUE);
+  gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr));
+}
diff --git a/packages/url_launcher/url_launcher_linux/example/linux/my_application.h b/packages/url_launcher/url_launcher_linux/example/linux/my_application.h
new file mode 100644
index 0000000..72271d5
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
new file mode 100644
index 0000000..a4d08f3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
@@ -0,0 +1,18 @@
+name: url_launcher_example
+description: Demonstrates how to use the url_launcher plugin.
+
+dependencies:
+  flutter:
+    sdk: flutter
+  url_launcher: any
+  url_launcher_linux:
+    path: ../
+
+dev_dependencies:
+  e2e: "^0.2.0"
+  flutter_driver:
+    sdk: flutter
+  pedantic: ^1.8.0
+
+flutter:
+  uses-material-design: true
diff --git a/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart
new file mode 100644
index 0000000..516835c
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart
@@ -0,0 +1,23 @@
+// 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);
+
+    // Desktop will not necessarily support sms:.
+
+    // 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_linux/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart
new file mode 100644
index 0000000..1bcd0d3
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/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<void> main() async {
+  final FlutterDriver driver = await FlutterDriver.connect();
+  final String result =
+      await driver.requestData(null, timeout: const Duration(minutes: 1));
+  await driver.close();
+  exit(result == 'pass' ? 0 : 1);
+}
diff --git a/packages/url_launcher/url_launcher_linux/ios/.gitignore b/packages/url_launcher/url_launcher_linux/ios/.gitignore
new file mode 100644
index 0000000..aa479fd
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/ios/.gitignore
@@ -0,0 +1,37 @@
+.idea/
+.vagrant/
+.sconsign.dblite
+.svn/
+
+.DS_Store
+*.swp
+profile
+
+DerivedData/
+build/
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+
+.generated/
+
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+!default.pbxuser
+!default.mode1v3
+!default.mode2v3
+!default.perspectivev3
+
+xcuserdata
+
+*.moved-aside
+
+*.pyc
+*sync/
+Icon?
+.tags*
+
+/Flutter/Generated.xcconfig
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec b/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec
new file mode 100644
index 0000000..1359fd4
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec
@@ -0,0 +1,22 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint url_launcher_linux.podspec' to validate before publishing.
+#
+Pod::Spec.new do |s|
+  s.name             = 'url_launcher_linux'
+  s.version          = '0.0.1'
+  s.summary          = 'url_launcher_linux iOS stub'
+  s.description      = <<-DESC
+  No-op implementation of the Linux url_launcher plugin to avoid build issues on iOS
+                       DESC
+  s.homepage         = 'https://github.com/flutter/plugins'
+  s.license          = { :type => 'BSD', :file => '../LICENSE' }
+  s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
+  s.source           = { :http => 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux' }
+  s.dependency 'Flutter'
+  s.platform = :ios, '8.0'
+
+  # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
+  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
+  s.swift_version = '5.0'
+end
diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart
new file mode 100644
index 0000000..18f7af1
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart
@@ -0,0 +1,3 @@
+// The url_launcher_platform_interface defaults to MethodChannelUrlLauncher
+// as its instance, which is all the Linux implementation needs. This file
+// is here to silence warnings when publishing to pub.
diff --git a/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt
new file mode 100644
index 0000000..1403d0c
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.10)
+set(PROJECT_NAME "url_launcher_linux")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
+
+add_library(${PLUGIN_NAME} SHARED
+  "url_launcher_plugin.cc"
+)
+apply_standard_settings(${PLUGIN_NAME})
+set_target_properties(${PLUGIN_NAME} PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
+target_include_directories(${PLUGIN_NAME} INTERFACE
+  "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
+target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
diff --git a/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h b/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h
new file mode 100644
index 0000000..efcfe62
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h
@@ -0,0 +1,31 @@
+// Copyright 2020 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.
+
+#ifndef PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_
+#define PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_
+
+// A plugin to launch URLs.
+
+#include <flutter_linux/flutter_linux.h>
+
+G_BEGIN_DECLS
+
+#ifdef FLUTTER_PLUGIN_IMPL
+#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
+#else
+#define FLUTTER_PLUGIN_EXPORT
+#endif
+
+G_DECLARE_FINAL_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, FL,
+                     URL_LAUNCHER_PLUGIN, GObject)
+
+FLUTTER_PLUGIN_EXPORT FlUrlLauncherPlugin* fl_url_launcher_plugin_new(
+    FlPluginRegistrar* registrar);
+
+FLUTTER_PLUGIN_EXPORT void url_launcher_plugin_register_with_registrar(
+    FlPluginRegistrar* registrar);
+
+G_END_DECLS
+
+#endif  // PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_
diff --git a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
new file mode 100644
index 0000000..592bb96
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
@@ -0,0 +1,148 @@
+// Copyright 2020 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.
+
+#include "include/url_launcher_linux/url_launcher_plugin.h"
+
+#include <flutter_linux/flutter_linux.h>
+#include <gtk/gtk.h>
+
+// See url_launcher_channel.dart for documentation.
+const char kChannelName[] = "plugins.flutter.io/url_launcher";
+const char kBadArgumentsError[] = "Bad Arguments";
+const char kLaunchError[] = "Launch Error";
+const char kCanLaunchMethod[] = "canLaunch";
+const char kLaunchMethod[] = "launch";
+const char kUrlKey[] = "url";
+
+struct _FlUrlLauncherPlugin {
+  GObject parent_instance;
+
+  FlPluginRegistrar* registrar;
+
+  // Connection to Flutter engine.
+  FlMethodChannel* channel;
+};
+
+G_DEFINE_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, g_object_get_type())
+
+// Gets the URL from the arguments or generates an error.
+static gchar* get_url(FlValue* args, GError** error) {
+  if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
+    g_set_error(error, 0, 0, "Argument map missing or malformed");
+    return nullptr;
+  }
+  FlValue* url_value = fl_value_lookup_string(args, kUrlKey);
+  if (url_value == nullptr) {
+    g_set_error(error, 0, 0, "Missing URL");
+    return nullptr;
+  }
+
+  return g_strdup(fl_value_get_string(url_value));
+}
+
+// Called to check if a URL can be launched.
+static FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) {
+  g_autoptr(GError) error = nullptr;
+  g_autofree gchar* url = get_url(args, &error);
+  if (url == nullptr) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, error->message, nullptr));
+  }
+
+  gboolean is_launchable = FALSE;
+  g_autofree gchar* scheme = g_uri_parse_scheme(url);
+  if (scheme != nullptr) {
+    g_autoptr(GAppInfo) app_info =
+        g_app_info_get_default_for_uri_scheme(scheme);
+    is_launchable = app_info != nullptr;
+  }
+
+  g_autoptr(FlValue) result = fl_value_new_bool(is_launchable);
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
+}
+
+// Called when a URL should launch.
+static FlMethodResponse* launch(FlUrlLauncherPlugin* self, FlValue* args) {
+  g_autoptr(GError) error = nullptr;
+  g_autofree gchar* url = get_url(args, &error);
+  if (url == nullptr) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, error->message, nullptr));
+  }
+
+  FlView* view = fl_plugin_registrar_get_view(self->registrar);
+  gboolean launched;
+  if (view != nullptr) {
+    GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
+    launched = gtk_show_uri_on_window(window, url, GDK_CURRENT_TIME, &error);
+  } else {
+    launched = g_app_info_launch_default_for_uri(url, nullptr, &error);
+  }
+  if (!launched) {
+    g_autofree gchar* message =
+        g_strdup_printf("Failed to launch URL: %s", error->message);
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kLaunchError, message, nullptr));
+  }
+
+  g_autoptr(FlValue) result = fl_value_new_bool(TRUE);
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
+}
+
+// Called when a method call is received from Flutter.
+static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
+                           gpointer user_data) {
+  FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data);
+
+  const gchar* method = fl_method_call_get_name(method_call);
+  FlValue* args = fl_method_call_get_args(method_call);
+
+  g_autoptr(FlMethodResponse) response = nullptr;
+  if (strcmp(method, kCanLaunchMethod) == 0)
+    response = can_launch(self, args);
+  else if (strcmp(method, kLaunchMethod) == 0)
+    response = launch(self, args);
+  else
+    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
+
+  g_autoptr(GError) error = nullptr;
+  if (!fl_method_call_respond(method_call, response, &error))
+    g_warning("Failed to send method call response: %s", error->message);
+}
+
+static void fl_url_launcher_plugin_dispose(GObject* object) {
+  FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(object);
+
+  g_clear_object(&self->registrar);
+  g_clear_object(&self->channel);
+
+  G_OBJECT_CLASS(fl_url_launcher_plugin_parent_class)->dispose(object);
+}
+
+static void fl_url_launcher_plugin_class_init(FlUrlLauncherPluginClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_url_launcher_plugin_dispose;
+}
+
+FlUrlLauncherPlugin* fl_url_launcher_plugin_new(FlPluginRegistrar* registrar) {
+  FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(
+      g_object_new(fl_url_launcher_plugin_get_type(), nullptr));
+
+  self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar));
+
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  self->channel =
+      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
+                            kChannelName, FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(self->channel, method_call_cb,
+                                            g_object_ref(self), g_object_unref);
+
+  return self;
+}
+
+static void fl_url_launcher_plugin_init(FlUrlLauncherPlugin* self) {}
+
+void url_launcher_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
+  FlUrlLauncherPlugin* plugin = fl_url_launcher_plugin_new(registrar);
+  g_object_unref(plugin);
+}
diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml
new file mode 100644
index 0000000..2662176
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml
@@ -0,0 +1,19 @@
+name: url_launcher_linux
+description: Linux implementation of the url_launcher plugin.
+version: 0.0.1
+homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux
+
+flutter:
+  plugin:
+    platforms:
+      linux:
+        pluginClass: UrlLauncherPlugin
+
+environment:
+  sdk: ">=2.1.0 <3.0.0"
+  flutter: ">=1.12.8 <2.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+