Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
[url_launcher] Re-land v2 embedding support (#2204)
Browse files Browse the repository at this point in the history
This is identical to the original PR, except it correctly increments the Flutter SDK version to the latest stable and fixes some minor Java style nits.
  • Loading branch information
Michael Klimushyn committed Oct 17, 2019
1 parent 3e271bc commit 2951f84
Show file tree
Hide file tree
Showing 18 changed files with 556 additions and 124 deletions.
4 changes: 4 additions & 0 deletions packages/url_launcher/url_launcher/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
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;
@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}.
*
* <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;
@Nullable private 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

0 comments on commit 2951f84

Please sign in to comment.