Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
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
7 changes: 7 additions & 0 deletions packages/url_launcher/url_launcher/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 5.2.0

* Migrate the plugin to use the V2 Android engine embedding. This shouldn't
affect existing functionality. Plugin authors who use the V2 embedding can now
instantiate the plugin and expect that it correctly responds to app lifecycle
changes.

## 5.1.7

* Define clang module for iOS.
Expand Down
36 changes: 36 additions & 0 deletions packages/url_launcher/url_launcher/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.urllauncher">
<application>
<activity android:name=".WebViewActivity"
<activity android:name="io.flutter.plugins.urllauncher.WebViewActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:exported="false"/>
</application>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
private @Nullable 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}.
*
* <p>Stops any previously started and unstopped calls.
*
* <p>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.
*
* <p>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<String, String> 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<String, String> headersMap) {
final Bundle headersBundle = new Bundle();
for (String key : headersMap.keySet()) {
final String value = headersMap.get(key);
headersBundle.putString(key, value);
}
return headersBundle;
}
}
Original file line number Diff line number Diff line change
@@ -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;
private @Nullable Activity activity;

/**
* Uses the given {@code applicationContext} for launching intents.
*
* <p>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,
}
}
Loading