Isolate extension WebView profiles and follow host proxy settings

This commit is contained in:
Levi committed 2026-09-14 00:30:52 +08:00
1 parent cee5f3336e
commit a0e84a75b1
556 files changed
+71097 -31

No files matched your search

@@ -0,0 +1,57 @@
group 'com.pichillilorenzo.flutter_inappwebview_android'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.1'
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
// Conditional for compatibility with AGP <4.2.
if (project.android.hasProperty("namespace")) {
namespace 'com.pichillilorenzo.flutter_inappwebview_android'
}
compileSdk 34
defaultConfig {
minSdkVersion 19
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
consumerProguardFiles 'proguard-rules.pro'
}
lintOptions {
disable 'InvalidPackage'
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dependencies {
implementation 'androidx.webkit:webkit:1.12.0'
implementation 'androidx.browser:browser:1.8.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
}
}
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
@@ -0,0 +1,19 @@
# WebView
-keepattributes *JavascriptInterface*
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
public void *(android.webkit.webView, jav.lang.String);
}
-keepclassmembers class com.pichillilorenzo.flutter_inappwebview_android$JavaScriptBridgeInterface {
<fields>;
<methods>;
public *;
private *;
}
-keep class com.pichillilorenzo.flutter_inappwebview_android.** { *; }
-dontwarn android.window.BackEvent
@@ -0,0 +1 @@
rootProject.name = 'flutter_inappwebview_android'
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pichillilorenzo.flutter_inappwebview_android">
<application>
<activity
android:theme="@style/AppTheme"
android:exported="false"
android:name="com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" />
<activity
android:theme="@style/ThemeTransparent"
android:exported="false"
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ChromeCustomTabsActivity" />
<activity
android:theme="@style/ThemeTransparent"
android:exported="false"
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.TrustedWebActivity" />
<activity
android:theme="@style/ThemeTransparent"
android:exported="false"
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ChromeCustomTabsActivitySingleInstance"
android:launchMode="singleInstance"/>
<activity
android:theme="@style/ThemeTransparent"
android:exported="false"
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.TrustedWebActivitySingleInstance"
android:launchMode="singleInstance"/>
<receiver
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ActionBroadcastReceiver"
android:enabled="true"
android:exported="false" />
<meta-data
android:name="io.flutter.embedded_views_preview"
android:value="true" />
</application>
<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
</manifest>
@@ -0,0 +1,33 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.net.Uri;
import androidx.webkit.ProfileStore;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.WebViewFeature;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
// Internal host bridge: profile creation and proxy completion precede navigation.
final class GopeedProfiles {
static void prepare(MethodCall call, MethodChannel.Result result) {
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) ||
!WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
result.error("UNAVAILABLE", "Android WebView does not support isolated profiles and proxy override", null);
return;
}
String id = call.argument("gopeedProfileId");
String proxy = call.argument("proxyUrl");
try {
java.util.UUID.fromString(id);
Uri uri = Uri.parse(proxy);
if (!"http".equals(uri.getScheme()) || !"127.0.0.1".equals(uri.getHost()) || uri.getPort() <= 0)
throw new IllegalArgumentException("Invalid host proxy configuration");
ProfileStore.getInstance().getOrCreateProfile(id);
ProxyConfig config = new ProxyConfig.Builder().addProxyRule(proxy).removeImplicitRules().build();
ProxyController.getInstance().setProxyOverride(config, Runnable::run, () -> result.success(true));
} catch (RuntimeException e) {
result.error("UNAVAILABLE", "Unable to configure WebView profile and proxy", null);
}
}
}
@@ -0,0 +1,11 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import androidx.annotation.NonNull;
import java.util.Map;
public interface ISettings<T> {
@NonNull ISettings<T> parse(@NonNull Map<String, Object> settings);
@NonNull Map<String, Object> toMap();
@NonNull Map<String, Object> getRealSettings(@NonNull T obj);
}
@@ -0,0 +1,9 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import androidx.core.content.FileProvider;
public class InAppWebViewFileProvider extends FileProvider {
public static final String fileProviderAuthorityExtension = "flutter_inappwebview_android.fileprovider";
}
@@ -0,0 +1,224 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ChromeSafariBrowserManager;
import com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.NoHistoryCustomTabsActivityCallbacks;
import com.pichillilorenzo.flutter_inappwebview_android.credential_database.CredentialDatabaseHandler;
import com.pichillilorenzo.flutter_inappwebview_android.headless_in_app_webview.HeadlessInAppWebViewManager;
import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserManager;
import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobManager;
import com.pichillilorenzo.flutter_inappwebview_android.process_global_config.ProcessGlobalConfigManager;
import com.pichillilorenzo.flutter_inappwebview_android.proxy.ProxyManager;
import com.pichillilorenzo.flutter_inappwebview_android.service_worker.ServiceWorkerManager;
import com.pichillilorenzo.flutter_inappwebview_android.tracing.TracingControllerManager;
import com.pichillilorenzo.flutter_inappwebview_android.webview.FlutterWebViewFactory;
import com.pichillilorenzo.flutter_inappwebview_android.webview.InAppWebViewManager;
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.BinaryMessenger;
import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.embedding.android.FlutterView;
public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
protected static final String LOG_TAG = "InAppWebViewFlutterPL";
@Nullable
public PlatformUtil platformUtil;
@Nullable
public InAppBrowserManager inAppBrowserManager;
@Nullable
public HeadlessInAppWebViewManager headlessInAppWebViewManager;
@Nullable
public ChromeSafariBrowserManager chromeSafariBrowserManager;
@Nullable
public NoHistoryCustomTabsActivityCallbacks noHistoryCustomTabsActivityCallbacks;
@Nullable
public InAppWebViewManager inAppWebViewManager;
@Nullable
public MyCookieManager myCookieManager;
@Nullable
public CredentialDatabaseHandler credentialDatabaseHandler;
@Nullable
public MyWebStorage myWebStorage;
@Nullable
public ServiceWorkerManager serviceWorkerManager;
@Nullable
public WebViewFeatureManager webViewFeatureManager;
@Nullable
public ProxyManager proxyManager;
@Nullable
public PrintJobManager printJobManager;
@Nullable
public TracingControllerManager tracingControllerManager;
@Nullable
public ProcessGlobalConfigManager processGlobalConfigManager;
public FlutterWebViewFactory flutterWebViewFactory;
public Context applicationContext;
public BinaryMessenger messenger;
public FlutterPlugin.FlutterAssets flutterAssets;
@Nullable
public ActivityPluginBinding activityPluginBinding;
@Nullable
public Activity activity;
public FlutterView flutterView;
public InAppWebViewFlutterPlugin() {}
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
this.flutterAssets = binding.getFlutterAssets();
// Shared.activity could be null or not.
// It depends on who is called first between onAttachedToEngine event and onAttachedToActivity event.
//
// See https://github.com/pichillilorenzo/flutter_inappwebview/issues/390#issuecomment-647039084
onAttachedToEngine(
binding.getApplicationContext(), binding.getBinaryMessenger(), this.activity, binding.getPlatformViewRegistry(), null);
}
private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger, Activity activity, PlatformViewRegistry platformViewRegistry, FlutterView flutterView) {
this.applicationContext = applicationContext;
this.activity = activity;
this.messenger = messenger;
this.flutterView = flutterView;
inAppBrowserManager = new InAppBrowserManager(this);
headlessInAppWebViewManager = new HeadlessInAppWebViewManager(this);
chromeSafariBrowserManager = new ChromeSafariBrowserManager(this);
noHistoryCustomTabsActivityCallbacks = new NoHistoryCustomTabsActivityCallbacks(this);
flutterWebViewFactory = new FlutterWebViewFactory(this);
platformViewRegistry.registerViewFactory(
FlutterWebViewFactory.VIEW_TYPE_ID, flutterWebViewFactory);
platformUtil = new PlatformUtil(this);
inAppWebViewManager = new InAppWebViewManager(this);
myCookieManager = new MyCookieManager(this);
myWebStorage = new MyWebStorage(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
serviceWorkerManager = new ServiceWorkerManager(this);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
credentialDatabaseHandler = new CredentialDatabaseHandler(this);
}
webViewFeatureManager = new WebViewFeatureManager(this);
proxyManager = new ProxyManager(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
printJobManager = new PrintJobManager(this);
}
tracingControllerManager = new TracingControllerManager(this);
processGlobalConfigManager = new ProcessGlobalConfigManager(this);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
if (platformUtil != null) {
platformUtil.dispose();
platformUtil = null;
}
if (inAppBrowserManager != null) {
inAppBrowserManager.dispose();
inAppBrowserManager = null;
}
if (headlessInAppWebViewManager != null) {
headlessInAppWebViewManager.dispose();
headlessInAppWebViewManager = null;
}
if (chromeSafariBrowserManager != null) {
chromeSafariBrowserManager.dispose();
chromeSafariBrowserManager = null;
}
if (noHistoryCustomTabsActivityCallbacks != null) {
noHistoryCustomTabsActivityCallbacks.dispose();
noHistoryCustomTabsActivityCallbacks = null;
}
if (myCookieManager != null) {
myCookieManager.dispose();
myCookieManager = null;
}
if (myWebStorage != null) {
myWebStorage.dispose();
myWebStorage = null;
}
if (credentialDatabaseHandler != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
credentialDatabaseHandler.dispose();
credentialDatabaseHandler = null;
}
if (inAppWebViewManager != null) {
inAppWebViewManager.dispose();
inAppWebViewManager = null;
}
if (serviceWorkerManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
serviceWorkerManager.dispose();
serviceWorkerManager = null;
}
if (webViewFeatureManager != null) {
webViewFeatureManager.dispose();
webViewFeatureManager = null;
}
if (proxyManager != null) {
proxyManager.dispose();
proxyManager = null;
}
if (printJobManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
printJobManager.dispose();
printJobManager = null;
}
if (tracingControllerManager != null) {
tracingControllerManager.dispose();
tracingControllerManager = null;
}
if (processGlobalConfigManager != null) {
processGlobalConfigManager.dispose();
processGlobalConfigManager = null;
}
}
@Override
public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
this.activityPluginBinding = activityPluginBinding;
this.activity = activityPluginBinding.getActivity();
if (noHistoryCustomTabsActivityCallbacks != null) {
this.activity.getApplication().registerActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
}
}
@Override
public void onDetachedFromActivityForConfigChanges() {
if (activity != null && noHistoryCustomTabsActivityCallbacks != null) {
this.activity.getApplication().unregisterActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
}
activityPluginBinding = null;
activity = null;
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
this.activityPluginBinding = activityPluginBinding;
this.activity = activityPluginBinding.getActivity();
if (noHistoryCustomTabsActivityCallbacks != null) {
this.activity.getApplication().registerActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
}
}
@Override
public void onDetachedFromActivity() {
if (activity != null && noHistoryCustomTabsActivityCallbacks != null) {
this.activity.getApplication().unregisterActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
}
activityPluginBinding = null;
activity = null;
}
}
@@ -0,0 +1,452 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.os.Build;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.ValueCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.CookieManagerCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class MyCookieManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "MyCookieManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager";
@Nullable
public static CookieManager cookieManager;
private CookieManager selectedCookieManager;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public MyCookieManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
public static void init() {
if (cookieManager == null) {
cookieManager = getCookieManager();
}
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals("gopeed.prepareProfile")) {
GopeedProfiles.prepare(call, result);
return;
}
String profileID = call.argument("gopeedProfileId");
if (profileID != null && !profileID.isEmpty()) {
if (!androidx.webkit.WebViewFeature.isFeatureSupported(androidx.webkit.WebViewFeature.MULTI_PROFILE) ||
androidx.webkit.ProfileStore.getInstance().getProfile(profileID) == null) {
result.error("UNAVAILABLE", "WebView profile is not initialized", null);
return;
}
selectedCookieManager = androidx.webkit.ProfileStore.getInstance().getProfile(profileID).getCookieManager();
}
init();
try {
switch (call.method) {
case "setCookie":
{
String url = (String) call.argument("url");
String name = (String) call.argument("name");
String value = (String) call.argument("value");
String domain = (String) call.argument("domain");
String path = (String) call.argument("path");
String expiresDateString = (String) call.argument("expiresDate");
Long expiresDate = (expiresDateString != null ? new Long(expiresDateString) : null);
Integer maxAge = (Integer) call.argument("maxAge");
Boolean isSecure = (Boolean) call.argument("isSecure");
Boolean isHttpOnly = (Boolean) call.argument("isHttpOnly");
String sameSite = (String) call.argument("sameSite");
setCookie(url,
name,
value,
domain,
path,
expiresDate,
maxAge,
isSecure,
isHttpOnly,
sameSite,
result);
}
break;
case "getCookies":
result.success(getCookies((String) call.argument("url")));
break;
case "deleteCookie":
{
String url = (String) call.argument("url");
String name = (String) call.argument("name");
String domain = (String) call.argument("domain");
String path = (String) call.argument("path");
deleteCookie(url, name, domain, path, result);
}
break;
case "deleteCookies":
{
String url = (String) call.argument("url");
String domain = (String) call.argument("domain");
String path = (String) call.argument("path");
deleteCookies(url, domain, path, result);
}
break;
case "deleteAllCookies":
deleteAllCookies(result);
break;
case "removeSessionCookies":
removeSessionCookies(result);
break;
default:
result.notImplemented();
}
} finally { selectedCookieManager = null; }
}
/**
* Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily
* to make sure it's done on a background thread as needed.
*
* https://github.com/facebook/react-native/blob/1903f6680d9750e244d97c3cd4a9f755a9a47c61/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java#L132
*/
static private @Nullable CookieManager getCookieManager() {
if (cookieManager == null) {
try {
cookieManager = CookieManager.getInstance();
} catch (IllegalArgumentException ex) {
// https://bugs.chromium.org/p/chromium/issues/detail?id=559720
return null;
} catch (Exception exception) {
String message = exception.getMessage();
// We cannot catch MissingWebViewPackageException as it is in a private / system API
// class. This validates the exception's message to ensure we are only handling this
// specific exception.
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewFactory.java#348
if (message != null
&& exception
.getClass()
.getCanonicalName()
.equals("android.webkit.WebViewFactory.MissingWebViewPackageException")) {
return null;
} else {
throw exception;
}
}
}
return cookieManager;
}
public void setCookie(String url,
String name,
String value,
String domain,
String path,
Long expiresDate,
Integer maxAge,
Boolean isSecure,
Boolean isHttpOnly,
String sameSite,
final MethodChannel.Result result) {
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
if (cookieManager == null) {
result.success(false);
return;
}
String cookieValue = name + "=" + value + "; Path=" + path;
if (domain != null)
cookieValue += "; Domain=" + domain;
if (expiresDate != null)
cookieValue += "; Expires=" + getCookieExpirationDate(expiresDate);
if (maxAge != null)
cookieValue += "; Max-Age=" + maxAge.toString();
if (isSecure != null && isSecure)
cookieValue += "; Secure";
if (isHttpOnly != null && isHttpOnly)
cookieValue += "; HttpOnly";
if (sameSite != null)
cookieValue += "; SameSite=" + sameSite;
cookieValue += ";";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setCookie(url, cookieValue, new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean successful) {
result.success(successful);
}
});
cookieManager.flush();
}
else if (plugin != null) {
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
cookieSyncMngr.startSync();
cookieManager.setCookie(url, cookieValue);
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
result.success(true);
} else {
cookieManager.setCookie(url, cookieValue);
result.success(true);
}
}
public List<Map<String, Object>> getCookies(final String url) {
final List<Map<String, Object>> cookieListMap = new ArrayList<>();
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
if (cookieManager == null) return cookieListMap;
List<String> cookies = new ArrayList<>();
if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_COOKIE_INFO)) {
cookies = CookieManagerCompat.getCookieInfo(cookieManager, url);
} else {
String cookiesString = cookieManager.getCookie(url);
if (cookiesString != null) {
cookies = Arrays.asList(cookiesString.split(";"));
}
}
for (String cookie : cookies) {
String[] cookieParams = cookie.split(";");
if (cookieParams.length == 0) continue;
String[] nameValue = cookieParams[0].split("=", 2);
String name = nameValue[0].trim();
String value = (nameValue.length > 1) ? nameValue[1].trim() : "";
Map<String, Object> cookieMap = new HashMap<>();
cookieMap.put("name", name);
cookieMap.put("value", value);
cookieMap.put("expiresDate", null);
cookieMap.put("isSessionOnly", null);
cookieMap.put("domain", null);
cookieMap.put("sameSite", null);
cookieMap.put("isSecure", null);
cookieMap.put("isHttpOnly", null);
cookieMap.put("path", null);
if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_COOKIE_INFO)) {
cookieMap.put("isSecure", false);
cookieMap.put("isHttpOnly", false);
for (int i = 1; i < cookieParams.length; i++) {
String[] cookieParamNameValue = cookieParams[i].split("=", 2);
String cookieParamName = cookieParamNameValue[0].trim();
String cookieParamValue = (cookieParamNameValue.length > 1) ? cookieParamNameValue[1].trim() : "";
if (cookieParamName.equalsIgnoreCase("Expires")) {
try {
final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US);
Date expiryDate = sdf.parse(cookieParamValue);
if (expiryDate != null) {
cookieMap.put("expiresDate", expiryDate.getTime());
}
} catch (ParseException e) {
Log.e(LOG_TAG, "", e);
}
} else if (cookieParamName.equalsIgnoreCase("Max-Age")) {
try {
long maxAge = Long.parseLong(cookieParamValue);
cookieMap.put("expiresDate", System.currentTimeMillis() + maxAge);
} catch (NumberFormatException e) {
Log.e(LOG_TAG, "", e);
}
} else if (cookieParamName.equalsIgnoreCase("Domain")) {
cookieMap.put("domain", cookieParamValue);
} else if (cookieParamName.equalsIgnoreCase("SameSite")) {
cookieMap.put("sameSite", cookieParamValue);
} else if (cookieParamName.equalsIgnoreCase("Secure")) {
cookieMap.put("isSecure", true);
} else if (cookieParamName.equalsIgnoreCase("HttpOnly")) {
cookieMap.put("isHttpOnly", true);
} else if (cookieParamName.equalsIgnoreCase("Path")) {
cookieMap.put("path", cookieParamValue);
}
}
}
cookieListMap.add(cookieMap);
}
return cookieListMap;
}
public void deleteCookie(String url, String name, String domain, String path, final MethodChannel.Result result) {
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
if (cookieManager == null) {
result.success(false);
return;
}
String cookieValue = name + "=; Path=" + path + "; Max-Age=-1";
if (domain != null)
cookieValue += "; Domain=" + domain;
cookieValue += ";";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setCookie(url, cookieValue, new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean successful) {
result.success(successful);
}
});
cookieManager.flush();
}
else if (plugin != null) {
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
cookieSyncMngr.startSync();
cookieManager.setCookie(url, cookieValue);
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
result.success(true);
} else {
cookieManager.setCookie(url, cookieValue);
result.success(true);
}
}
public void deleteCookies(String url, String domain, String path, final MethodChannel.Result result) {
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
if (cookieManager == null) {
result.success(false);
return;
}
CookieSyncManager cookieSyncMngr = null;
String cookiesString = cookieManager.getCookie(url);
if (cookiesString != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && plugin != null) {
cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
cookieSyncMngr.startSync();
}
String[] cookies = cookiesString.split(";");
for (String cookie : cookies) {
String[] nameValue = cookie.split("=", 2);
String name = nameValue[0].trim();
String cookieValue = name + "=; Path=" + path + "; Max-Age=-1";
if (domain != null)
cookieValue += "; Domain=" + domain;
cookieValue += ";";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
cookieManager.setCookie(url, cookieValue, null);
else
cookieManager.setCookie(url, cookieValue);
}
if (cookieSyncMngr != null) {
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
cookieManager.flush();
}
result.success(true);
}
public void deleteAllCookies(final MethodChannel.Result result) {
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
if (cookieManager == null) {
result.success(false);
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.removeAllCookies(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean successful) {
result.success(successful);
}
});
cookieManager.flush();
}
else if (plugin != null) {
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
cookieSyncMngr.startSync();
cookieManager.removeAllCookie();
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
result.success(true);
} else {
cookieManager.removeAllCookie();
result.success(true);
}
}
public void removeSessionCookies(final MethodChannel.Result result) {
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
if (cookieManager == null) {
result.success(false);
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.removeSessionCookies(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean successful) {
result.success(successful);
}
});
cookieManager.flush();
}
else if (plugin != null) {
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
cookieSyncMngr.startSync();
cookieManager.removeSessionCookie();
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
result.success(true);
} else {
cookieManager.removeSessionCookie();
result.success(true);
}
}
public static String getCookieExpirationDate(Long timestamp) {
final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
return sdf.format(new Date(timestamp));
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}
@@ -0,0 +1,138 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.webkit.ValueCallback;
import android.webkit.WebStorage;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class MyWebStorage extends ChannelDelegateImpl {
protected static final String LOG_TAG = "MyWebStorage";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager";
@Nullable
public static WebStorage webStorageManager;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public MyWebStorage(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
public static void init() {
if (webStorageManager == null) {
webStorageManager = WebStorage.getInstance();
}
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
init();
switch (call.method) {
case "getOrigins":
getOrigins(result);
break;
case "deleteAllData":
if (webStorageManager != null) {
webStorageManager.deleteAllData();
result.success(true);
} else {
result.success(false);
}
break;
case "deleteOrigin":
{
if (webStorageManager != null) {
String origin = (String) call.argument("origin");
webStorageManager.deleteOrigin(origin);
result.success(true);
} else {
result.success(false);
}
}
break;
case "getQuotaForOrigin":
{
String origin = (String) call.argument("origin");
getQuotaForOrigin(origin, result);
}
break;
case "getUsageForOrigin":
{
String origin = (String) call.argument("origin");
getUsageForOrigin(origin, result);
}
break;
default:
result.notImplemented();
}
}
public void getOrigins(final MethodChannel.Result result) {
if (webStorageManager == null) {
result.success(new ArrayList<>());
return;
}
webStorageManager.getOrigins(new ValueCallback<Map>() {
@Override
public void onReceiveValue(Map value) {
List<Map<String, Object>> origins = new ArrayList<>();
for(Object key : value.keySet()) {
WebStorage.Origin originObj = (WebStorage.Origin) value.get(key);
Map<String, Object> originInfo = new HashMap<>();
originInfo.put("origin", originObj.getOrigin());
originInfo.put("quota", originObj.getQuota());
originInfo.put("usage", originObj.getUsage());
origins.add(originInfo);
}
result.success(origins);
}
});
}
public void getQuotaForOrigin(String origin, final MethodChannel.Result result) {
if (webStorageManager == null) {
result.success(0);
return;
}
webStorageManager.getQuotaForOrigin(origin, new ValueCallback<Long>() {
@Override
public void onReceiveValue(Long value) {
result.success(value);
}
});
}
public void getUsageForOrigin(String origin, final MethodChannel.Result result) {
if (webStorageManager == null) {
result.success(0);
return;
}
webStorageManager.getUsageForOrigin(origin, new ValueCallback<Long>() {
@Override
public void onReceiveValue(Long value) {
result.success(value);
}
});
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}
@@ -0,0 +1,73 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class PlatformUtil extends ChannelDelegateImpl {
protected static final String LOG_TAG = "PlatformUtil";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public PlatformUtil(final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "getSystemVersion":
result.success(String.valueOf(Build.VERSION.SDK_INT));
break;
case "formatDate":
long date = (long) call.argument("date");
String format = (String) call.argument("format");
Locale locale = PlatformUtil.getLocaleFromString((String) call.argument("locale"));
String timezone = (String) call.argument("timezone");
if (timezone == null) {
timezone = "UTC";
}
result.success(PlatformUtil.formatDate(date, format, locale, TimeZone.getTimeZone(timezone)));
break;
default:
result.notImplemented();
}
}
public static Locale getLocaleFromString(@Nullable String locale) {
if (locale == null) {
return Locale.US;
}
String[] localeSplit = locale.split("_");
String language = localeSplit[0];
String country = localeSplit.length > 1 ? localeSplit[1] : "";
String variant = localeSplit.length > 2 ? localeSplit[2] : "";
return new Locale(language, country, variant);
}
public static String formatDate(long date, String format, Locale locale, TimeZone timezone) {
final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
sdf.setTimeZone(timezone);
return sdf.format(new Date(date));
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}
@@ -0,0 +1,386 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.BitmapFactory;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview_android.types.Size2D;
import com.pichillilorenzo.flutter_inappwebview_android.types.SyncBaseCallbackResultImpl;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import javax.net.ssl.SSLHandshakeException;
import io.flutter.plugin.common.MethodChannel;
public class Util {
static final String LOG_TAG = "Util";
public static final String ANDROID_ASSET_URL = "file:///android_asset/";
private Util() {}
public static String getUrlAsset(InAppWebViewFlutterPlugin plugin, String assetFilePath) throws IOException {
String key = plugin.flutterAssets.getAssetFilePathByName(assetFilePath);
InputStream is = null;
IOException e = null;
try {
is = getFileAsset(plugin, assetFilePath);
} catch (IOException ex) {
e = ex;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ex) {
e = ex;
}
}
}
if (e != null) {
throw e;
}
return ANDROID_ASSET_URL + key;
}
public static InputStream getFileAsset(InAppWebViewFlutterPlugin plugin, String assetFilePath) throws IOException {
String key = plugin.flutterAssets.getAssetFilePathByName(assetFilePath);
AssetManager mg = plugin.applicationContext.getResources().getAssets();
return mg.open(key);
}
public static <T> T invokeMethodAndWaitResult(final @NonNull MethodChannel channel,
final @NonNull String method, final @Nullable Object arguments,
final @NonNull SyncBaseCallbackResultImpl<T> callback) throws InterruptedException {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
channel.invokeMethod(method, arguments, callback);
}
});
callback.latch.await();
return callback.result;
}
@Nullable
public static PrivateKeyAndCertificates loadPrivateKeyAndCertificate(@NonNull InAppWebViewFlutterPlugin plugin,
@NonNull String certificatePath,
@Nullable String certificatePassword,
@NonNull String keyStoreType) {
PrivateKeyAndCertificates privateKeyAndCertificates = null;
InputStream certificateFileStream = null;
try {
certificateFileStream = getFileAsset(plugin, certificatePath);
} catch (IOException ignored) {}
try {
if (certificateFileStream == null) {
certificateFileStream = new FileInputStream(certificatePath);
}
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(certificateFileStream, (certificatePassword != null ? certificatePassword : "").toCharArray());
Enumeration<String> aliases = keyStore.aliases();
String alias = aliases.nextElement();
Key key = keyStore.getKey(alias, (certificatePassword != null ? certificatePassword : "").toCharArray());
if (key instanceof PrivateKey) {
PrivateKey privateKey = (PrivateKey)key;
Certificate cert = keyStore.getCertificate(alias);
X509Certificate[] certificates = new X509Certificate[1];
certificates[0] = (X509Certificate)cert;
privateKeyAndCertificates = new PrivateKeyAndCertificates(privateKey, certificates);
}
certificateFileStream.close();
} catch (Exception e) {
Log.e(LOG_TAG, "", e);
} finally {
if (certificateFileStream != null) {
try {
certificateFileStream.close();
} catch (IOException ex) {
Log.e(LOG_TAG, "", ex);
}
}
}
return privateKeyAndCertificates;
}
public static class PrivateKeyAndCertificates {
public X509Certificate[] certificates;
public PrivateKey privateKey;
public PrivateKeyAndCertificates(PrivateKey privateKey, X509Certificate[] certificates) {
this.privateKey = privateKey;
this.certificates = certificates;
}
}
@Nullable
public static HttpURLConnection makeHttpRequest(String urlString, String method, @Nullable Map<String, String> headers) {
HttpURLConnection urlConnection = null;
try {
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod(method);
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
urlConnection.setRequestProperty(header.getKey(), header.getValue());
}
}
urlConnection.setConnectTimeout(15000); // 15 seconds
urlConnection.setReadTimeout(15000); // 15 seconds
urlConnection.setDoInput(true);
urlConnection.setInstanceFollowRedirects(true);
if ("GET".equalsIgnoreCase(method)) {
urlConnection.setDoOutput(false);
}
urlConnection.connect();
return urlConnection;
}
catch (Exception e) {
if (!(e instanceof SSLHandshakeException)) {
Log.e(LOG_TAG, "", e);
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
/**
* SslCertificate class does not has a public getter for the underlying
* X509Certificate, we can only do this by hack. This only works for Android 4.0+
* https://groups.google.com/forum/#!topic/android-developers/eAPJ6b7mrmg
*/
public static X509Certificate getX509CertFromSslCertHack(SslCertificate sslCert) {
X509Certificate x509Certificate = null;
Bundle bundle = SslCertificate.saveState(sslCert);
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
} catch (CertificateException e) {
x509Certificate = null;
}
}
return x509Certificate;
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String JSONStringify(@Nullable Object value) {
if (value == null) {
return "null";
}
if (value instanceof Map) {
return new JSONObject((Map<String, Object>) value).toString();
} else if (value instanceof List) {
return new JSONArray((List<Object>) value).toString();
} else if (value instanceof String) {
return JSONObject.quote((String) value);
} else {
return JSONObject.wrap(value).toString();
}
}
public static boolean objEquals(@Nullable Object a, @Nullable Object b) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return Objects.equals(a, b);
}
return (a == b) || (a != null && a.equals(b));
}
public static String replaceAll(String s, String oldString, String newString) {
return TextUtils.join(newString, s.split(Pattern.quote(oldString)));
}
public static void log(String tag, String message) {
// Split by line, then ensure each line can fit into Log's maximum length.
for (int i = 0, length = message.length(); i < length; i++) {
int newline = message.indexOf('\n', i);
newline = newline != -1 ? newline : length;
do {
int end = Math.min(newline, i + 4000);
Log.d(tag, message.substring(i, end));
i = end;
} while (i < newline);
}
}
public static float getPixelDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
public static Size2D getFullscreenSize(Context context) {
Size2D fullscreenSize = new Size2D(-1, -1);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (wm != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
final WindowMetrics metrics = wm.getCurrentWindowMetrics();
// Gets all excluding insets
final WindowInsets windowInsets = metrics.getWindowInsets();
Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
| WindowInsets.Type.displayCutout());
int insetsWidth = insets.right + insets.left;
int insetsHeight = insets.top + insets.bottom;
final Rect bounds = metrics.getBounds();
fullscreenSize.setWidth(bounds.width() - insetsWidth);
fullscreenSize.setHeight(bounds.height() - insetsHeight);
} else {
DisplayMetrics displayMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(displayMetrics);
fullscreenSize.setWidth(displayMetrics.widthPixels);
fullscreenSize.setHeight(displayMetrics.heightPixels);
}
}
return fullscreenSize;
}
public static boolean isClass(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static boolean isIPv6(String address) {
try {
Inet6Address.getByName(address);
} catch (UnknownHostException e) {
return false;
}
return true;
}
public static String normalizeIPv6(String address) throws Exception {
if (!Util.isIPv6(address)) {
throw new Exception("Invalid address: " + address);
}
return InetAddress.getByName(address).getCanonicalHostName();
}
public static <T> T getOrDefault(Map<String, Object> map, String key, T defaultValue) {
return map.containsKey(key) ? (T) map.get(key) : defaultValue;
}
@Nullable
public static byte[] readAllBytes(@Nullable InputStream inputStream) {
if (inputStream == null) {
return null;
}
final int bufLen = 4 * 0x400; // 4KB
byte[] buf = new byte[bufLen];
int readLen;
IOException exception = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] data = null;
try {
while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
outputStream.write(buf, 0, readLen);
data = outputStream.toByteArray();
} catch (IOException e) {
exception = e;
} finally {
try {
inputStream.close();
} catch (IOException e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && exception != null) {
exception.addSuppressed(e);
}
}
try {
outputStream.close();
} catch (IOException e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && exception != null) {
exception.addSuppressed(e);
}
}
}
return data;
}
@Nullable
public static <O> Object invokeMethodIfExists(final O o, final String methodName, Object... args) {
Method[] methods = o.getClass().getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
try {
return method.invoke(o, args);
} catch (IllegalAccessException e) {
return null;
} catch (InvocationTargetException e) {
return null;
}
}
}
return null;
}
public static Drawable drawableFromBytes(Context context, byte[] data) {
return new BitmapDrawable(context.getResources(), BitmapFactory.decodeByteArray(data, 0, data.length));
}
}
@@ -0,0 +1,47 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class WebViewFeatureManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "WebViewFeatureManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webviewfeature";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public WebViewFeatureManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "isFeatureSupported":
String feature = (String) call.argument("feature");
result.success(WebViewFeature.isFeatureSupported(feature));
break;
case "isStartupFeatureSupported":
if (plugin != null && plugin.activity != null) {
String startupFeature = (String) call.argument("startupFeature");
result.success(WebViewFeature.isStartupFeatureSupported(plugin.activity, startupFeature));
}
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}
@@ -0,0 +1,47 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.browser.customtabs.CustomTabsIntent;
public class ActionBroadcastReceiver extends BroadcastReceiver {
protected static final String LOG_TAG = "ActionBroadcastReceiver";
public static final String KEY_ACTION_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_ID";
public static final String KEY_ACTION_VIEW_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_VIEW_ID";
public static final String KEY_ACTION_MANAGER_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_MANAGER_ID";
public static final String KEY_URL_TITLE = "android.intent.extra.SUBJECT";
@Override
public void onReceive(Context context, Intent intent) {
int clickedId = intent.getIntExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS_CLICKED_ID, -1);
String url = intent.getDataString();
if (url != null) {
Bundle b = intent.getExtras();
String viewId = b.getString(KEY_ACTION_VIEW_ID);
String managerId = b.getString(KEY_ACTION_MANAGER_ID);
if (managerId != null) {
ChromeSafariBrowserManager chromeSafariBrowserManager = ChromeSafariBrowserManager.shared.get(managerId);
if (chromeSafariBrowserManager != null) {
if (clickedId == -1) {
int id = b.getInt(KEY_ACTION_ID);
String title = b.getString(KEY_URL_TITLE);
ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onItemActionPerform(id, url, title);
}
} else {
ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url);
}
}
}
}
}
}
}
@@ -0,0 +1,462 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.customtabs.EngagementSignalsCallback;
import com.pichillilorenzo.flutter_inappwebview_android.R;
import com.pichillilorenzo.flutter_inappwebview_android.types.AndroidResource;
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsActionButton;
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsMenuItem;
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsSecondaryToolbar;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
public class ChromeCustomTabsActivity extends Activity implements Disposable {
protected static final String LOG_TAG = "CustomTabsActivity";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_chromesafaribrowser_";
public String id;
@Nullable
public CustomTabsIntent.Builder builder;
public ChromeCustomTabsSettings customSettings = new ChromeCustomTabsSettings();
public CustomTabActivityHelper customTabActivityHelper = new CustomTabActivityHelper();
@Nullable
public CustomTabsSession customTabsSession;
public final static int CHROME_CUSTOM_TAB_REQUEST_CODE = 100;
public final static int NO_HISTORY_CHROME_CUSTOM_TAB_REQUEST_CODE = 101;
protected boolean onOpened = false;
protected boolean onCompletedInitialLoad = false;
protected boolean isBindSuccess = false;
@Nullable
public ChromeSafariBrowserManager manager;
@Nullable
public String initialUrl;
@Nullable
public List<String> initialOtherLikelyURLs;
@Nullable
public Map<String, String> initialHeaders;
@Nullable
public String initialReferrer;
public List<CustomTabsMenuItem> menuItems = new ArrayList<>();
@Nullable
public CustomTabsActionButton actionButton;
@Nullable
public CustomTabsSecondaryToolbar secondaryToolbar;
@Nullable
public ChromeCustomTabsChannelDelegate channelDelegate;
@CallSuper
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chrome_custom_tabs_layout);
Bundle b = getIntent().getExtras();
if (b == null) return;
id = b.getString("id");
String managerId = b.getString("managerId");
manager = ChromeSafariBrowserManager.shared.get(managerId);
if (manager == null || manager.plugin == null || manager.plugin.messenger == null) return;
manager.browsers.put(id, this);
MethodChannel channel = new MethodChannel(manager.plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
channelDelegate = new ChromeCustomTabsChannelDelegate(this, channel);
initialUrl = b.getString("url");
initialHeaders = (Map<String, String>) b.getSerializable("headers");
initialReferrer = b.getString("referrer");
initialOtherLikelyURLs = b.getStringArrayList("otherLikelyURLs");
customSettings = new ChromeCustomTabsSettings();
customSettings.parse((HashMap<String, Object>) b.getSerializable("settings"));
actionButton = CustomTabsActionButton.fromMap((Map<String, Object>) b.getSerializable("actionButton"));
secondaryToolbar = CustomTabsSecondaryToolbar.fromMap((Map<String, Object>) b.getSerializable("secondaryToolbar"));
List<Map<String, Object>> menuItemList = (List<Map<String, Object>>) b.getSerializable("menuItemList");
for (Map<String, Object> menuItem : menuItemList) {
menuItems.add(CustomTabsMenuItem.fromMap(menuItem));
}
if (customSettings.noHistory && manager.plugin.noHistoryCustomTabsActivityCallbacks != null) {
manager.plugin.noHistoryCustomTabsActivityCallbacks.noHistoryBrowserIDs.put(id, id);
}
final ChromeCustomTabsActivity chromeCustomTabsActivity = this;
customTabActivityHelper.setConnectionCallback(new CustomTabActivityHelper.ConnectionCallback() {
@Override
public void onCustomTabsConnected() {
customTabsConnected();
if (channelDelegate != null) {
channelDelegate.onServiceConnected();
}
}
@Override
public void onCustomTabsDisconnected() {
chromeCustomTabsActivity.close();
dispose();
}
});
customTabActivityHelper.setCustomTabsCallback(new CustomTabsCallback() {
@Override
public void onNavigationEvent(int navigationEvent, Bundle extras) {
if (navigationEvent == TAB_SHOWN && !onOpened) {
onOpened = true;
if (channelDelegate != null) {
channelDelegate.onOpened();
}
}
if (navigationEvent == NAVIGATION_FINISHED && !onCompletedInitialLoad) {
onCompletedInitialLoad = true;
if (channelDelegate != null) {
channelDelegate.onCompletedInitialLoad();
}
}
if (channelDelegate != null) {
channelDelegate.onNavigationEvent(navigationEvent);
}
}
@Override
public void extraCallback(@NonNull String callbackName, Bundle args) {
}
@Override
public void onMessageChannelReady(Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onMessageChannelReady();
}
}
@Override
public void onPostMessage(@NonNull String message, Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onPostMessage(message);
}
}
@Override
public void onRelationshipValidationResult(@CustomTabsService.Relation int relation,
@NonNull Uri requestedOrigin,
boolean result, Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onRelationshipValidationResult(relation, requestedOrigin, result);
}
}
});
}
public void launchUrl(@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable String referrer,
@Nullable List<String> otherLikelyURLs) {
launchUrlWithSession(customTabsSession, url, headers, referrer, otherLikelyURLs);
}
public void launchUrlWithSession(@Nullable CustomTabsSession session,
@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable String referrer,
@Nullable List<String> otherLikelyURLs) {
mayLaunchUrl(url, otherLikelyURLs);
builder = new CustomTabsIntent.Builder(session);
prepareCustomTabs();
CustomTabsIntent customTabsIntent = builder.build();
prepareCustomTabsIntent(customTabsIntent);
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), headers,
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
public boolean mayLaunchUrl(@Nullable String url, @Nullable List<String> otherLikelyURLs) {
Uri uri = url != null ? Uri.parse(url) : null;
List<Bundle> bundleOtherLikelyURLs = new ArrayList<>();
if (otherLikelyURLs != null) {
Bundle bundleOtherLikelyURL = new Bundle();
for (String otherLikelyURL : otherLikelyURLs) {
bundleOtherLikelyURL.putString(CustomTabsService.KEY_URL, otherLikelyURL);
}
}
return customTabActivityHelper.mayLaunchUrl(uri, null, bundleOtherLikelyURLs);
}
@CallSuper
public void customTabsConnected() {
customTabsSession = customTabActivityHelper.getSession();
if (customTabsSession != null) {
try {
Bundle bundle = new Bundle();
if (customTabsSession.isEngagementSignalsApiAvailable(bundle)) {
customTabsSession.setEngagementSignalsCallback(new EngagementSignalsCallback() {
@Override
public void onVerticalScrollEvent(boolean isDirectionUp, @NonNull Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onVerticalScrollEvent(isDirectionUp);
}
}
@Override
public void onGreatestScrollPercentageIncreased(int scrollPercentage, @NonNull Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onGreatestScrollPercentageIncreased(scrollPercentage);
}
}
@Override
public void onSessionEnded(boolean didUserInteract, @NonNull Bundle extras) {
if (channelDelegate != null) {
channelDelegate.onSessionEnded(didUserInteract);
}
}
}, bundle);
}
} catch (Throwable e) {
Log.d(LOG_TAG, "Custom Tabs Engagement Signals API not supported", e);
}
}
// avoid webpage reopen if isBindSuccess is false: onServiceConnected->launchUrl
if (isBindSuccess && initialUrl != null) {
launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
}
}
private void prepareCustomTabs() {
if (builder == null) {
return;
}
if (customSettings.addDefaultShareMenuItem != null) {
builder.setShareState(customSettings.addDefaultShareMenuItem ?
CustomTabsIntent.SHARE_STATE_ON : CustomTabsIntent.SHARE_STATE_OFF);
} else {
builder.setShareState(customSettings.shareState);
}
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) {
defaultColorSchemeBuilder.setToolbarColor(Color.parseColor(customSettings.toolbarBackgroundColor));
}
if (customSettings.navigationBarColor != null && !customSettings.navigationBarColor.isEmpty()) {
defaultColorSchemeBuilder.setNavigationBarColor(Color.parseColor(customSettings.navigationBarColor));
}
if (customSettings.navigationBarDividerColor != null && !customSettings.navigationBarDividerColor.isEmpty()) {
defaultColorSchemeBuilder.setNavigationBarDividerColor(Color.parseColor(customSettings.navigationBarDividerColor));
}
if (customSettings.secondaryToolbarColor != null && !customSettings.secondaryToolbarColor.isEmpty()) {
defaultColorSchemeBuilder.setSecondaryToolbarColor(Color.parseColor(customSettings.secondaryToolbarColor));
}
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder.build());
builder.setShowTitle(customSettings.showTitle);
builder.setUrlBarHidingEnabled(customSettings.enableUrlBarHiding);
builder.setInstantAppsEnabled(customSettings.instantAppsEnabled);
if (customSettings.startAnimations.size() == 2) {
builder.setStartAnimations(this,
customSettings.startAnimations.get(0).getIdentifier(this),
customSettings.startAnimations.get(1).getIdentifier(this));
}
if (customSettings.exitAnimations.size() == 2) {
builder.setExitAnimations(this,
customSettings.exitAnimations.get(0).getIdentifier(this),
customSettings.exitAnimations.get(1).getIdentifier(this));
}
for (CustomTabsMenuItem menuItem : menuItems) {
builder.addMenuItem(menuItem.getLabel(),
createPendingIntent(menuItem.getId()));
}
if (actionButton != null) {
byte[] data = actionButton.getIcon();
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inMutable = true;
Bitmap bmp = BitmapFactory.decodeByteArray(
data, 0, data.length, bitmapOptions
);
builder.setActionButton(bmp, actionButton.getDescription(),
createPendingIntent(actionButton.getId()),
actionButton.isShouldTint());
}
if (secondaryToolbar != null) {
AndroidResource layout = secondaryToolbar.getLayout();
RemoteViews remoteViews = new RemoteViews(layout.getDefPackage(), layout.getIdentifier(this));
int[] clickableIDs = new int[secondaryToolbar.getClickableIDs().size()];
for (int i = 0, length = secondaryToolbar.getClickableIDs().size(); i < length; i++) {
AndroidResource clickableID = secondaryToolbar.getClickableIDs().get(i);
clickableIDs[i] = clickableID.getIdentifier(this);
}
builder.setSecondaryToolbarViews(remoteViews, clickableIDs, getSecondaryToolbarOnClickPendingIntent());
}
}
public PendingIntent getSecondaryToolbarOnClickPendingIntent() {
Intent broadcastIntent = new Intent(this, ActionBroadcastReceiver.class);
Bundle extras = new Bundle();
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null);
broadcastIntent.putExtras(extras);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return PendingIntent.getBroadcast(
this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
} else {
return PendingIntent.getBroadcast(
this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
private void prepareCustomTabsIntent(CustomTabsIntent customTabsIntent) {
if (customSettings.packageName != null)
customTabsIntent.intent.setPackage(customSettings.packageName);
else
customTabsIntent.intent.setPackage(CustomTabsHelper.getPackageNameToUse(this));
if (customSettings.keepAliveEnabled)
CustomTabsHelper.addKeepAliveExtra(this, customTabsIntent.intent);
if (customSettings.alwaysUseBrowserUI)
CustomTabsIntent.setAlwaysUseBrowserUI(customTabsIntent.intent);
}
public void updateActionButton(@NonNull byte[] icon, @NonNull String description) {
if (customTabsSession == null || actionButton == null) {
return;
}
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inMutable = true;
Bitmap bmp = BitmapFactory.decodeByteArray(
icon, 0, icon.length, bitmapOptions
);
customTabsSession.setActionButton(bmp, description);
actionButton.setIcon(icon);
actionButton.setDescription(description);
}
public void updateSecondaryToolbar(CustomTabsSecondaryToolbar secondaryToolbar) {
if (customTabsSession == null) {
return;
}
AndroidResource layout = secondaryToolbar.getLayout();
RemoteViews remoteViews = new RemoteViews(layout.getDefPackage(), layout.getIdentifier(this));
int[] clickableIDs = new int[secondaryToolbar.getClickableIDs().size()];
for (int i = 0, length = secondaryToolbar.getClickableIDs().size(); i < length; i++) {
AndroidResource clickableID = secondaryToolbar.getClickableIDs().get(i);
clickableIDs[i] = clickableID.getIdentifier(this);
}
customTabsSession.setSecondaryToolbarViews(remoteViews, clickableIDs, getSecondaryToolbarOnClickPendingIntent());
this.secondaryToolbar = secondaryToolbar;
}
@Override
protected void onStart() {
super.onStart();
isBindSuccess = customTabActivityHelper.bindCustomTabsService(this);
if (!isBindSuccess && initialUrl != null) {
// chrome process not running, start tab directly
launchUrlWithSession(null, initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
}
}
@Override
protected void onStop() {
super.onStop();
customTabActivityHelper.unbindCustomTabsService(this);
isBindSuccess = false;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) {
close();
dispose();
}
}
private PendingIntent createPendingIntent(int actionSourceId) {
Intent actionIntent = new Intent(this, ActionBroadcastReceiver.class);
Bundle extras = new Bundle();
extras.putInt(ActionBroadcastReceiver.KEY_ACTION_ID, actionSourceId);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null);
actionIntent.putExtras(extras);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return PendingIntent.getBroadcast(
this, actionSourceId, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
} else {
return PendingIntent.getBroadcast(
this, actionSourceId, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
@Override
public void dispose() {
onStop();
onDestroy();
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
if (manager != null) {
if (manager.browsers.containsKey(id)) {
manager.browsers.put(id, null);
}
}
manager = null;
}
public void close() {
onStop();
onDestroy();
customTabsSession = null;
finish();
if (channelDelegate != null) {
channelDelegate.onClosed();
}
}
}
@@ -0,0 +1,7 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
public class ChromeCustomTabsActivitySingleInstance extends ChromeCustomTabsActivity {
protected static final String LOG_TAG = "ChromeCustomTabsActivitySingleInstance";
}
@@ -0,0 +1,251 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsService;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsSecondaryToolbar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
@Nullable
private ChromeCustomTabsActivity chromeCustomTabsActivity;
public ChromeCustomTabsChannelDelegate(@NonNull ChromeCustomTabsActivity chromeCustomTabsActivity, @NonNull MethodChannel channel) {
super(channel);
this.chromeCustomTabsActivity = chromeCustomTabsActivity;
}
@Override
public void onMethodCall(@NonNull final MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "launchUrl":
if (chromeCustomTabsActivity != null) {
String url = (String) call.argument("url");
if (url != null) {
Map<String, String> headers = (Map<String, String>) call.argument("headers");
String referrer = (String) call.argument("referrer");
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
chromeCustomTabsActivity.launchUrl(url, headers, referrer, otherLikelyURLs);
result.success(true);
} else {
result.success(false);
}
} else {
result.success(false);
}
break;
case "mayLaunchUrl":
if (chromeCustomTabsActivity != null) {
String url = (String) call.argument("url");
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
result.success(chromeCustomTabsActivity.mayLaunchUrl(url, otherLikelyURLs));
} else {
result.success(false);
}
break;
case "updateActionButton":
if (chromeCustomTabsActivity != null) {
byte[] icon = (byte[]) call.argument("icon");
String description = (String) call.argument("description");
chromeCustomTabsActivity.updateActionButton(icon, description);
result.success(true);
} else {
result.success(false);
}
break;
case "validateRelationship":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
Integer relation = (Integer) call.argument("relation");
String origin = (String) call.argument("origin");
result.success(chromeCustomTabsActivity.customTabsSession.validateRelationship(relation, Uri.parse(origin), null));
} else {
result.success(false);
}
break;
case "updateSecondaryToolbar":
if (chromeCustomTabsActivity != null) {
CustomTabsSecondaryToolbar secondaryToolbar = CustomTabsSecondaryToolbar.fromMap((Map<String, Object>) call.argument("secondaryToolbar"));
chromeCustomTabsActivity.updateSecondaryToolbar(secondaryToolbar);
result.success(true);
} else {
result.success(false);
}
break;
case "requestPostMessageChannel":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
String sourceOrigin = (String) call.argument("sourceOrigin");
String targetOrigin = (String) call.argument("targetOrigin");
result.success(chromeCustomTabsActivity.customTabsSession.requestPostMessageChannel(Uri.parse(sourceOrigin),
targetOrigin != null ? Uri.parse(targetOrigin) : null, new Bundle()));
} else {
result.success(false);
}
break;
case "postMessage":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
String message = (String) call.argument("message");
result.success(chromeCustomTabsActivity.customTabsSession.postMessage(message, new Bundle()));
} else {
result.success(CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR);
}
break;
case "isEngagementSignalsApiAvailable":
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
try {
result.success(chromeCustomTabsActivity.customTabsSession.isEngagementSignalsApiAvailable(new Bundle()));
} catch (Throwable e) {
result.success(false);
}
} else {
result.success(false);
}
break;
case "close":
if (chromeCustomTabsActivity != null) {
chromeCustomTabsActivity.onStop();
chromeCustomTabsActivity.onDestroy();
chromeCustomTabsActivity.close();
if (chromeCustomTabsActivity.manager != null && chromeCustomTabsActivity.manager.plugin != null &&
chromeCustomTabsActivity.manager.plugin.activity != null) {
Activity activity = chromeCustomTabsActivity.manager.plugin.activity;
// https://stackoverflow.com/a/41596629/4637638
Intent myIntent = new Intent(activity, activity.getClass());
myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(myIntent);
}
chromeCustomTabsActivity.dispose();
result.success(true);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
public void onServiceConnected() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onServiceConnected", obj);
}
public void onOpened() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onOpened", obj);
}
public void onCompletedInitialLoad() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onCompletedInitialLoad", obj);
}
public void onNavigationEvent(int navigationEvent) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("navigationEvent", navigationEvent);
channel.invokeMethod("onNavigationEvent", obj);
}
public void onClosed() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onClosed", obj);
}
public void onItemActionPerform(int id, String url, String title) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("id", id);
obj.put("url", url);
obj.put("title", title);
channel.invokeMethod("onItemActionPerform", obj);
}
public void onSecondaryItemActionPerform(String name, String url) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("name", name);
obj.put("url", url);
channel.invokeMethod("onSecondaryItemActionPerform", obj);
}
public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, boolean result) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("relation", relation);
obj.put("requestedOrigin", requestedOrigin.toString());
obj.put("result", result);
channel.invokeMethod("onRelationshipValidationResult", obj);
}
public void onMessageChannelReady() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onMessageChannelReady", obj);
}
public void onPostMessage(@NonNull String message) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("message", message);
channel.invokeMethod("onPostMessage", obj);
}
public void onVerticalScrollEvent(boolean isDirectionUp) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("isDirectionUp", isDirectionUp);
channel.invokeMethod("onVerticalScrollEvent", obj);
}
public void onGreatestScrollPercentageIncreased(int scrollPercentage) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("scrollPercentage", scrollPercentage);
channel.invokeMethod("onGreatestScrollPercentageIncreased", obj);
}
public void onSessionEnded(boolean didUserInteract) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("didUserInteract", didUserInteract);
channel.invokeMethod("onSessionEnded", obj);
}
@Override
public void dispose() {
super.dispose();
chromeCustomTabsActivity = null;
}
}
@@ -0,0 +1,187 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.trusted.ScreenOrientation;
import androidx.browser.trusted.TrustedWebActivityDisplayMode;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import com.pichillilorenzo.flutter_inappwebview_android.types.AndroidResource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActivity> {
final static String LOG_TAG = "ChromeCustomTabsSettings";
@Deprecated
public Boolean addDefaultShareMenuItem;
public Integer shareState = CustomTabsIntent.SHARE_STATE_DEFAULT;
public Boolean showTitle = true;
@Nullable
public String toolbarBackgroundColor;
@Nullable
public String navigationBarColor;
@Nullable
public String navigationBarDividerColor;
@Nullable
public String secondaryToolbarColor;
public Boolean enableUrlBarHiding = false;
public Boolean instantAppsEnabled = false;
public String packageName;
public Boolean keepAliveEnabled = false;
public Boolean isSingleInstance = false;
public Boolean noHistory = false;
public Boolean isTrustedWebActivity = false;
public List<String> additionalTrustedOrigins = new ArrayList<>();
public TrustedWebActivityDisplayMode displayMode = null;
public Integer screenOrientation = ScreenOrientation.DEFAULT;
public List<AndroidResource> startAnimations = new ArrayList<>();
public List<AndroidResource> exitAnimations = new ArrayList<>();
public Boolean alwaysUseBrowserUI = false;
@NonNull
@Override
public ChromeCustomTabsSettings parse(@NonNull Map<String, Object> options) {
for (Map.Entry<String, Object> pair : options.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "addDefaultShareMenuItem":
addDefaultShareMenuItem = (Boolean) value;
break;
case "shareState":
shareState = (Integer) value;
break;
case "showTitle":
showTitle = (Boolean) value;
break;
case "toolbarBackgroundColor":
toolbarBackgroundColor = (String) value;
break;
case "navigationBarColor":
navigationBarColor = (String) value;
break;
case "navigationBarDividerColor":
navigationBarDividerColor = (String) value;
break;
case "secondaryToolbarColor":
secondaryToolbarColor = (String) value;
break;
case "enableUrlBarHiding":
enableUrlBarHiding = (Boolean) value;
break;
case "instantAppsEnabled":
instantAppsEnabled = (Boolean) value;
break;
case "packageName":
packageName = (String) value;
break;
case "keepAliveEnabled":
keepAliveEnabled = (Boolean) value;
break;
case "isSingleInstance":
isSingleInstance = (Boolean) value;
break;
case "noHistory":
noHistory = (Boolean) value;
break;
case "isTrustedWebActivity":
isTrustedWebActivity = (Boolean) value;
break;
case "additionalTrustedOrigins":
additionalTrustedOrigins = (List<String>) value;
break;
case "displayMode":
Map<String, Object> displayModeMap = (Map<String, Object>) value;
String displayModeType = (String) displayModeMap.get("type");
if (displayModeType != null) {
switch (displayModeType) {
case "IMMERSIVE_MODE":
boolean isSticky = (boolean) displayModeMap.get("isSticky");
int layoutInDisplayCutoutMode = (int) displayModeMap.get("displayCutoutMode");
displayMode = new TrustedWebActivityDisplayMode.ImmersiveMode(isSticky, layoutInDisplayCutoutMode);
break;
case "DEFAULT_MODE":
displayMode = new TrustedWebActivityDisplayMode.DefaultMode();
break;
}
}
break;
case "screenOrientation":
screenOrientation = (Integer) value;
break;
case "startAnimations":
List<Map<String, Object>> startAnimationsList = (List<Map<String, Object>>) value;
for (Map<String, Object> startAnimation : startAnimationsList) {
AndroidResource androidResource = AndroidResource.fromMap(startAnimation);
if (androidResource != null) {
startAnimations.add(AndroidResource.fromMap(startAnimation));
}
}
break;
case "exitAnimations":
List<Map<String, Object>> exitAnimationsList = (List<Map<String, Object>>) value;
for (Map<String, Object> exitAnimation : exitAnimationsList) {
AndroidResource androidResource = AndroidResource.fromMap(exitAnimation);
if (androidResource != null) {
exitAnimations.add(AndroidResource.fromMap(exitAnimation));
}
}
break;
case "alwaysUseBrowserUI":
alwaysUseBrowserUI = (Boolean) value;
break;
}
}
return this;
}
@NonNull
@Override
public Map<String, Object> toMap() {
Map<String, Object> options = new HashMap<>();
options.put("addDefaultShareMenuItem", addDefaultShareMenuItem);
options.put("showTitle", showTitle);
options.put("toolbarBackgroundColor", toolbarBackgroundColor);
options.put("navigationBarColor", navigationBarColor);
options.put("navigationBarDividerColor", navigationBarDividerColor);
options.put("secondaryToolbarColor", secondaryToolbarColor);
options.put("enableUrlBarHiding", enableUrlBarHiding);
options.put("instantAppsEnabled", instantAppsEnabled);
options.put("packageName", packageName);
options.put("keepAliveEnabled", keepAliveEnabled);
options.put("isSingleInstance", isSingleInstance);
options.put("noHistory", noHistory);
options.put("isTrustedWebActivity", isTrustedWebActivity);
options.put("additionalTrustedOrigins", additionalTrustedOrigins);
options.put("screenOrientation", screenOrientation);
options.put("alwaysUseBrowserUI", alwaysUseBrowserUI);
return options;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull ChromeCustomTabsActivity chromeCustomTabsActivity) {
Map<String, Object> realOptions = toMap();
if (chromeCustomTabsActivity != null) {
Intent intent = chromeCustomTabsActivity.getIntent();
if (intent != null) {
realOptions.put("packageName", intent.getPackage());
realOptions.put("keepAliveEnabled", intent.hasExtra(CustomTabsHelper.EXTRA_CUSTOM_TABS_KEEP_ALIVE));
}
}
return realOptions;
}
}
@@ -0,0 +1,139 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsIntent;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.Util;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ChromeSafariBrowserManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "ChromeBrowserManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_chromesafaribrowser";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public String id;
public static final Map<String, ChromeSafariBrowserManager> shared = new HashMap<>();
public final Map<String, ChromeCustomTabsActivity> browsers = new HashMap<>();
public ChromeSafariBrowserManager(final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.id = UUID.randomUUID().toString();
this.plugin = plugin;
shared.put(this.id, this);
}
@Override
public void onMethodCall(final MethodCall call, final MethodChannel.Result result) {
final String viewId = (String) call.argument("id");
switch (call.method) {
case "open":
if (plugin != null && plugin.activity != null) {
String url = (String) call.argument("url");
HashMap<String, Object> headers = (HashMap<String, Object>) call.argument("headers");
String referrer = (String) call.argument("referrer");
ArrayList<String> otherLikelyURLs = (ArrayList<String>) call.argument("otherLikelyURLs");
HashMap<String, Object> settings = (HashMap<String, Object>) call.argument("settings");
HashMap<String, Object> actionButton = (HashMap<String, Object>) call.argument("actionButton");
HashMap<String, Object> secondaryToolbar = (HashMap<String, Object>) call.argument("secondaryToolbar");
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
open(plugin.activity, viewId, url, headers, referrer, otherLikelyURLs, settings, actionButton, secondaryToolbar, menuItemList, result);
} else {
result.success(false);
}
break;
case "isAvailable":
if (plugin != null && plugin.activity != null) {
result.success(CustomTabActivityHelper.isAvailable(plugin.activity));
} else {
result.success(false);
}
break;
case "getMaxToolbarItems":
result.success(CustomTabsIntent.getMaxToolbarItems());
break;
case "getPackageName":
if (plugin != null && plugin.activity != null) {
ArrayList<String> packages = (ArrayList<String>) call.argument("packages");
Boolean ignoreDefault = (Boolean) call.argument("ignoreDefault");
result.success(CustomTabsClient.getPackageName(plugin.activity, packages, ignoreDefault));
} else {
result.success(null);
}
break;
default:
result.notImplemented();
}
}
public void open(Activity activity, String viewId, @Nullable String url, @Nullable HashMap<String, Object> headers,
@Nullable String referrer, @Nullable ArrayList<String> otherLikelyURLs,
HashMap<String, Object> settings, HashMap<String, Object> actionButton,
HashMap<String, Object> secondaryToolbar,
List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) {
Intent intent = null;
Bundle extras = new Bundle();
extras.putString("url", url);
extras.putString("id", viewId);
extras.putString("managerId", this.id);
extras.putSerializable("headers", headers);
extras.putString("referrer", referrer);
extras.putSerializable("otherLikelyURLs", otherLikelyURLs);
extras.putSerializable("settings", settings);
extras.putSerializable("actionButton", (Serializable) actionButton);
extras.putSerializable("secondaryToolbar", (Serializable) secondaryToolbar);
extras.putSerializable("menuItemList", (Serializable) menuItemList);
Boolean isSingleInstance = Util.<Boolean>getOrDefault(settings, "isSingleInstance", false);
Boolean isTrustedWebActivity = Util.<Boolean>getOrDefault(settings, "isTrustedWebActivity", false);
if (CustomTabActivityHelper.isAvailable(activity)) {
intent = new Intent(activity, !isSingleInstance ?
(!isTrustedWebActivity ? ChromeCustomTabsActivity.class : TrustedWebActivity.class) :
(!isTrustedWebActivity ? ChromeCustomTabsActivitySingleInstance.class : TrustedWebActivitySingleInstance.class));
intent.putExtras(extras);
Boolean noHistory = Util.<Boolean>getOrDefault(settings, "noHistory", false);
if (noHistory) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
}
activity.startActivity(intent);
result.success(true);
return;
}
result.error(LOG_TAG, "ChromeCustomTabs is not available!", null);
}
@Override
public void dispose() {
super.dispose();
Collection<ChromeCustomTabsActivity> browserList = browsers.values();
for (ChromeCustomTabsActivity browser : browserList) {
if (browser != null) {
browser.close();
browser.dispose();
}
}
browsers.clear();
shared.remove(this.id);
plugin = null;
}
}
@@ -0,0 +1,177 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.trusted.TrustedWebActivityIntent;
import java.util.List;
import java.util.Map;
/**
* This is a helper class to manage the connection to the Custom Tabs Service.
*/
public class CustomTabActivityHelper implements ServiceConnectionCallback {
private CustomTabsSession mCustomTabsSession;
private CustomTabsClient mClient;
private CustomTabsServiceConnection mConnection;
private ConnectionCallback mConnectionCallback;
private CustomTabsCallback mCustomTabsCallback;
/**
* Opens the URL on a Custom Tab if possible.
*
* @param activity The host activity.
* @param intent a intent to be used if Custom Tabs is available.
* @param uri the Uri to be opened.
*/
public static void openCustomTab(Activity activity,
Intent intent,
Uri uri,
@Nullable Map<String, String> headers,
@Nullable Uri referrer,
int requestCode) {
intent.setData(uri);
if (headers != null) {
Bundle bundleHeaders = new Bundle();
for (Map.Entry<String, String> header : headers.entrySet()) {
bundleHeaders.putString(header.getKey(), header.getValue());
}
intent.putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
}
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
activity.startActivityForResult(intent, requestCode);
}
public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent,
Uri uri,
@Nullable Map<String, String> headers,
@Nullable Uri referrer,
int requestCode) {
CustomTabActivityHelper.openCustomTab(activity, customTabsIntent.intent, uri,
headers, referrer, requestCode);
}
public static void openTrustedWebActivity(Activity activity,
TrustedWebActivityIntent trustedWebActivityIntent,
Uri uri,
@Nullable Map<String, String> headers,
@Nullable Uri referrer,
int requestCode) {
CustomTabActivityHelper.openCustomTab(activity, trustedWebActivityIntent.getIntent(), uri,
headers, referrer, requestCode);
}
public static boolean isAvailable(Activity activity) {
return CustomTabsHelper.getPackageNameToUse(activity) != null;
}
/**
* Unbinds the Activity from the Custom Tabs Service.
* @param activity the activity that is connected to the service.
*/
public void unbindCustomTabsService(Activity activity) {
if (mConnection == null) return;
activity.unbindService(mConnection);
mClient = null;
mCustomTabsSession = null;
mConnection = null;
}
/**
* Creates or retrieves an exiting CustomTabsSession.
*
* @return a CustomTabsSession.
*/
@Nullable
public CustomTabsSession getSession() {
if (mClient == null) {
mCustomTabsSession = null;
} else if (mCustomTabsSession == null) {
mCustomTabsSession = mClient.newSession(mCustomTabsCallback);
}
return mCustomTabsSession;
}
/**
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
* @param connectionCallback
*/
public void setConnectionCallback(ConnectionCallback connectionCallback) {
this.mConnectionCallback = connectionCallback;
}
public void setCustomTabsCallback(CustomTabsCallback customTabsCallback) {
this.mCustomTabsCallback = customTabsCallback;
}
/**
* Binds the Activity to the Custom Tabs Service.
* @param activity the activity to be binded to the service.
*/
public boolean bindCustomTabsService(Activity activity) {
if (mClient != null) return true;
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
if (packageName == null) return false;
mConnection = new ServiceConnection(this);
return CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
}
/**
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
* @return true if call to mayLaunchUrl was accepted.
*/
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
if (mClient == null) return false;
CustomTabsSession session = getSession();
if (session == null) return false;
return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
}
@Override
public void onServiceConnected(CustomTabsClient client) {
mClient = client;
mClient.warmup(0L);
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
}
@Override
public void onServiceDisconnected() {
mClient = null;
mCustomTabsSession = null;
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
}
/**
* A Callback for when the service is connected or disconnected. Use those callbacks to
* handle UI changes when the service is connected or disconnected.
*/
public interface ConnectionCallback {
/**
* Called when the service is connected.
*/
void onCustomTabsConnected();
/**
* Called when the service is disconnected.
*/
void onCustomTabsDisconnected();
}
}
@@ -0,0 +1,134 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.browser.customtabs.CustomTabsService;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for Custom Tabs.
*/
public class CustomTabsHelper {
protected static final String TAG = "CustomTabsHelper";
static final String STABLE_PACKAGE = "com.android.chrome";
static final String BETA_PACKAGE = "com.chrome.beta";
static final String DEV_PACKAGE = "com.chrome.dev";
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
protected static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
"android.support.customtabs.extra.KEEP_ALIVE";
private static String sPackageNameToUse;
private CustomTabsHelper() {}
public static void addKeepAliveExtra(Context context, Intent intent) {
Intent keepAliveIntent = new Intent().setClassName(
context.getPackageName(), KeepAliveService.class.getCanonicalName());
intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent);
}
/**
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
* the one chosen by the user if there is one, otherwise makes a best effort to return a
* valid package name.
*
* This is <strong>not</strong> threadsafe.
*
* @param context {@link Context} to use for accessing {@link PackageManager}.
* @return The package name recommended to use for connecting to custom tabs related components.
*/
public static String getPackageNameToUse(Context context) {
if (sPackageNameToUse != null) return sPackageNameToUse;
PackageManager pm = context.getPackageManager();
// Get default VIEW intent handler.
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
activityIntent.addCategory(Intent.CATEGORY_BROWSABLE);
ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
String defaultViewHandlerPackageName = null;
if (defaultViewHandlerInfo != null) {
defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
}
// Get all apps that can handle VIEW intents.
int flags = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags |= PackageManager.MATCH_ALL;
}
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, flags);
List<String> packagesSupportingCustomTabs = new ArrayList<>();
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs.add(info.activityInfo.packageName);
}
}
// Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
// and service calls.
if (packagesSupportingCustomTabs.isEmpty()) {
sPackageNameToUse = null;
} else if (packagesSupportingCustomTabs.size() == 1) {
sPackageNameToUse = packagesSupportingCustomTabs.get(0);
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)
&& !hasSpecializedHandlerIntents(context, activityIntent)
&& packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
sPackageNameToUse = defaultViewHandlerPackageName;
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
sPackageNameToUse = STABLE_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
sPackageNameToUse = BETA_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
sPackageNameToUse = DEV_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
sPackageNameToUse = LOCAL_PACKAGE;
}
return sPackageNameToUse;
}
/**
* Used to check whether there is a specialized handler for a given intent.
* @param intent The intent to check with.
* @return Whether there is a specialized handler for the given intent.
*/
private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {
try {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> handlers = pm.queryIntentActivities(
intent,
PackageManager.GET_RESOLVED_FILTER);
if (handlers == null || handlers.size() == 0) {
return false;
}
for (ResolveInfo resolveInfo : handlers) {
IntentFilter filter = resolveInfo.filter;
if (filter == null) continue;
if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;
if (resolveInfo.activityInfo == null) continue;
return true;
}
} catch (RuntimeException e) {
Log.e(TAG, "Runtime exception while getting specialized handlers");
}
return false;
}
/**
* @return All possible chrome package names that provide custom tabs feature.
*/
public static String[] getPackages() {
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
}
}
@@ -0,0 +1,18 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
/**
* Empty service used by the custom tab to bind to, raising the application's importance.
*/
public class KeepAliveService extends Service {
private static final Binder sBinder = new Binder();
@Override
public IBinder onBind(Intent intent) {
return sBinder;
}
}
@@ -0,0 +1,77 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import io.flutter.embedding.android.FlutterActivity;
public class NoHistoryCustomTabsActivityCallbacks implements Disposable {
@Nullable
public InAppWebViewFlutterPlugin plugin;
public final Map<String, String> noHistoryBrowserIDs = new HashMap<>();
public NoHistoryCustomTabsActivityCallbacks(@NonNull final InAppWebViewFlutterPlugin plugin) {
this.plugin = plugin;
}
public Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
if (activity instanceof FlutterActivity && plugin != null && plugin.chromeSafariBrowserManager != null) {
Collection<String> browserIds = noHistoryBrowserIDs.values();
for (String browserId : browserIds) {
if (browserId != null) {
noHistoryBrowserIDs.put(browserId, null);
ChromeCustomTabsActivity browser = plugin.chromeSafariBrowserManager.browsers.get(browserId);
if (browser != null) {
browser.close();
browser.dispose();
}
}
}
}
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
};
@Override
public void dispose() {
noHistoryBrowserIDs.clear();
plugin = null;
}
}
@@ -0,0 +1,32 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.content.ComponentName;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import java.lang.ref.WeakReference;
/**
* Implementation for the CustomTabsServiceConnection that avoids leaking the
* ServiceConnectionCallback
*/
public class ServiceConnection extends CustomTabsServiceConnection {
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
mConnectionCallback = new WeakReference<>(connectionCallback);
}
@Override
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceConnected(client);
}
@Override
public void onServiceDisconnected(ComponentName name) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceDisconnected();
}
}
@@ -0,0 +1,19 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import androidx.browser.customtabs.CustomTabsClient;
/**
* Callback for events when connecting and disconnecting from Custom Tabs Service.
*/
public interface ServiceConnectionCallback {
/**
* Called when the service is connected.
* @param client a CustomTabsClient
*/
void onServiceConnected(CustomTabsClient client);
/**
* Called when the service is disconnected.
*/
void onServiceDisconnected();
}
@@ -0,0 +1,84 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.trusted.TrustedWebActivityIntent;
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
import java.util.List;
import java.util.Map;
public class TrustedWebActivity extends ChromeCustomTabsActivity {
protected static final String LOG_TAG = "TrustedWebActivity";
public TrustedWebActivityIntentBuilder builder;
@Override
public void launchUrl(@NonNull String url,
@Nullable Map<String, String> headers,
@Nullable String referrer,
@Nullable List<String> otherLikelyURLs) {
if (customTabsSession == null) {
return;
}
Uri uri = Uri.parse(url);
mayLaunchUrl(url, otherLikelyURLs);
builder = new TrustedWebActivityIntentBuilder(uri);
prepareCustomTabs();
TrustedWebActivityIntent trustedWebActivityIntent = builder.build(customTabsSession);
prepareCustomTabsIntent(trustedWebActivityIntent);
CustomTabActivityHelper.openTrustedWebActivity(this, trustedWebActivityIntent, uri, headers,
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
}
private void prepareCustomTabs() {
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) {
defaultColorSchemeBuilder.setToolbarColor(Color.parseColor(customSettings.toolbarBackgroundColor));
}
if (customSettings.navigationBarColor != null && !customSettings.navigationBarColor.isEmpty()) {
defaultColorSchemeBuilder.setNavigationBarColor(Color.parseColor(customSettings.navigationBarColor));
}
if (customSettings.navigationBarDividerColor != null && !customSettings.navigationBarDividerColor.isEmpty()) {
defaultColorSchemeBuilder.setNavigationBarDividerColor(Color.parseColor(customSettings.navigationBarDividerColor));
}
if (customSettings.secondaryToolbarColor != null && !customSettings.secondaryToolbarColor.isEmpty()) {
defaultColorSchemeBuilder.setSecondaryToolbarColor(Color.parseColor(customSettings.secondaryToolbarColor));
}
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder.build());
if (customSettings.additionalTrustedOrigins != null && !customSettings.additionalTrustedOrigins.isEmpty()) {
builder.setAdditionalTrustedOrigins(customSettings.additionalTrustedOrigins);
}
if (customSettings.displayMode != null) {
builder.setDisplayMode(customSettings.displayMode);
}
builder.setScreenOrientation(customSettings.screenOrientation);
}
private void prepareCustomTabsIntent(TrustedWebActivityIntent trustedWebActivityIntent) {
Intent intent = trustedWebActivityIntent.getIntent();
if (customSettings.packageName != null)
intent.setPackage(customSettings.packageName);
else
intent.setPackage(CustomTabsHelper.getPackageNameToUse(this));
if (customSettings.keepAliveEnabled)
CustomTabsHelper.addKeepAliveExtra(this, intent);
if (customSettings.alwaysUseBrowserUI)
CustomTabsIntent.setAlwaysUseBrowserUI(intent);
}
}
@@ -0,0 +1,7 @@
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
public class TrustedWebActivitySingleInstance extends TrustedWebActivity {
protected static final String LOG_TAG = "TrustedWebActivitySingleInstance";
}
@@ -0,0 +1,59 @@
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
import androidx.annotation.NonNull;
public class ContentBlocker {
@NonNull
private ContentBlockerTrigger trigger;
@NonNull
private ContentBlockerAction action;
public ContentBlocker (@NonNull ContentBlockerTrigger trigger, @NonNull ContentBlockerAction action) {
this.trigger = trigger;
this.action = action;
}
@NonNull
public ContentBlockerTrigger getTrigger() {
return trigger;
}
public void setTrigger(@NonNull ContentBlockerTrigger trigger) {
this.trigger = trigger;
}
@NonNull
public ContentBlockerAction getAction() {
return action;
}
public void setAction(@NonNull ContentBlockerAction action) {
this.action = action;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ContentBlocker that = (ContentBlocker) o;
if (!trigger.equals(that.trigger)) return false;
return action.equals(that.action);
}
@Override
public int hashCode() {
int result = trigger.hashCode();
result = 31 * result + action.hashCode();
return result;
}
@Override
public String toString() {
return "ContentBlocker{" +
"trigger=" + trigger +
", action=" + action +
'}';
}
}
@@ -0,0 +1,71 @@
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public class ContentBlockerAction {
@NonNull
private ContentBlockerActionType type;
@Nullable
private String selector;
ContentBlockerAction(@NonNull ContentBlockerActionType type, @Nullable String selector) {
this.type = type;
if (this.type.equals(ContentBlockerActionType.CSS_DISPLAY_NONE)) {
assert(selector != null);
}
this.selector = selector;
}
public static ContentBlockerAction fromMap(Map<String, Object> map) {
ContentBlockerActionType type = ContentBlockerActionType.fromValue((String) map.get("type"));
String selector = (String) map.get("selector");
return new ContentBlockerAction(type, selector);
}
@NonNull
public ContentBlockerActionType getType() {
return type;
}
public void setType(@NonNull ContentBlockerActionType type) {
this.type = type;
}
public String getSelector() {
return selector;
}
public void setSelector(String selector) {
this.selector = selector;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ContentBlockerAction that = (ContentBlockerAction) o;
if (type != that.type) return false;
return selector != null ? selector.equals(that.selector) : that.selector == null;
}
@Override
public int hashCode() {
int result = type.hashCode();
result = 31 * result + (selector != null ? selector.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ContentBlockerAction{" +
"type=" + type +
", selector='" + selector + '\'' +
'}';
}
}
@@ -0,0 +1,30 @@
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
public enum ContentBlockerActionType {
BLOCK ("block"),
CSS_DISPLAY_NONE ("css-display-none"),
MAKE_HTTPS ("make-https");
private final String value;
private ContentBlockerActionType(String value) {
this.value = value;
}
public boolean equalsValue(String otherValue) {
return value.equals(otherValue);
}
public static ContentBlockerActionType fromValue(String value) {
for( ContentBlockerActionType type : ContentBlockerActionType.values()) {
if(value.equals(type.value))
return type;
}
throw new IllegalArgumentException("No enum constant: " + value);
}
@Override
public String toString() {
return this.value;
}
}
@@ -0,0 +1,336 @@
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
import android.os.Build;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebResourceResponse;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.Util;
import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS;
import com.pichillilorenzo.flutter_inappwebview_android.types.WebResourceRequestExt;
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import javax.net.ssl.SSLHandshakeException;
public class ContentBlockerHandler {
protected static final String LOG_TAG = "ContentBlockerHandler";
protected List<ContentBlocker> ruleList = new ArrayList<>();
public ContentBlockerHandler() {}
public ContentBlockerHandler(List<ContentBlocker> ruleList) {
this.ruleList = ruleList;
}
public List<ContentBlocker> getRuleList() {
return this.ruleList;
}
public void setRuleList(List<ContentBlocker> newRuleList) {
this.ruleList = newRuleList;
}
@Nullable
public WebResourceResponse checkUrl(final InAppWebView webView, WebResourceRequestExt request,
ContentBlockerTriggerResourceType responseResourceType)
throws URISyntaxException, InterruptedException, MalformedURLException {
if (webView.customSettings.contentBlockers == null)
return null;
String url = request.getUrl();
URI u;
try {
u = new URI(url);
} catch (URISyntaxException e) {
String[] urlSplit = url.split(":");
String scheme = urlSplit[0];
URL tempUrl = new URL(url.replace(scheme, "https"));
u = new URI(scheme, tempUrl.getUserInfo(), tempUrl.getHost(), tempUrl.getPort(), tempUrl.getPath(), tempUrl.getQuery(), tempUrl.getRef());
}
String host = u.getHost();
int port = u.getPort();
String scheme = u.getScheme();
// thread safe copy list
List<ContentBlocker> ruleListCopy = new CopyOnWriteArrayList<ContentBlocker>(ruleList);
for (ContentBlocker contentBlocker : ruleListCopy) {
ContentBlockerTrigger trigger = contentBlocker.getTrigger();
List<ContentBlockerTriggerResourceType> resourceTypes = trigger.getResourceType();
if (resourceTypes.contains(ContentBlockerTriggerResourceType.IMAGE) && !resourceTypes.contains(ContentBlockerTriggerResourceType.SVG_DOCUMENT)) {
resourceTypes.add(ContentBlockerTriggerResourceType.SVG_DOCUMENT);
}
ContentBlockerAction action = contentBlocker.getAction();
Matcher m = trigger.getUrlFilterPatternCompiled().matcher(url);
if (m.matches()) {
if (!resourceTypes.isEmpty() && !resourceTypes.contains(responseResourceType)) {
return null;
}
if (!trigger.getIfDomain().isEmpty()) {
boolean matchFound = false;
for (String domain : trigger.getIfDomain()) {
if ((domain.startsWith("*") && host.endsWith(domain.replace("*", ""))) || domain.equals(host)) {
matchFound = true;
break;
}
}
if (!matchFound)
return null;
}
if (!trigger.getUnlessDomain().isEmpty()) {
for (String domain : trigger.getUnlessDomain())
if ((domain.startsWith("*") && host.endsWith(domain.replace("*", ""))) || domain.equals(host))
return null;
}
final String[] webViewUrl = new String[1];
if (!trigger.getLoadType().isEmpty() || !trigger.getIfTopUrl().isEmpty() || !trigger.getUnlessTopUrl().isEmpty()) {
final CountDownLatch latch = new CountDownLatch(1);
Handler handler = new Handler(webView.getWebViewLooper());
handler.post(new Runnable() {
@Override
public void run() {
webViewUrl[0] = webView.getUrl();
latch.countDown();
}
});
latch.await();
}
if (webViewUrl[0] != null) {
if (!trigger.getLoadType().isEmpty()) {
URI cUrl = new URI(webViewUrl[0]);
String cHost = cUrl.getHost();
int cPort = cUrl.getPort();
String cScheme = cUrl.getScheme();
if ( (trigger.getLoadType().contains("first-party") && cHost != null && !(cScheme.equals(scheme) && cHost.equals(host) && cPort == port)) ||
(trigger.getLoadType().contains("third-party") && cHost != null && cHost.equals(host)) )
return null;
}
if (!trigger.getIfTopUrl().isEmpty()) {
boolean matchFound = false;
for (String topUrl : trigger.getIfTopUrl()) {
if (webViewUrl[0].startsWith(topUrl)) {
matchFound = true;
break;
}
}
if (!matchFound)
return null;
}
if (!trigger.getUnlessTopUrl().isEmpty()) {
for (String topUrl : trigger.getUnlessTopUrl())
if (webViewUrl[0].startsWith(topUrl))
return null;
}
}
switch (action.getType()) {
case BLOCK:
return new WebResourceResponse("", "", null);
case CSS_DISPLAY_NONE:
final String cssSelector = action.getSelector();
final String jsScript = "(function(d) { " +
" function hide () { " +
" if (d.body != null && !d.getElementById('" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style')) { " +
" var c = d.createElement('style'); " +
" c.id = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style'; " +
" c.innerHTML = '" + cssSelector + " { display: none !important; }'; " +
" d.body.appendChild(c); " +
" }" +
" d.querySelectorAll('" + cssSelector + "').forEach(function (item, index) { " +
" item.setAttribute('style', 'display: none !important;'); " +
" }); " +
" }; " +
" hide(); " +
" d.addEventListener('DOMContentLoaded', function(event) { hide(); }); " +
"})(document);";
final Handler handler = new Handler(webView.getWebViewLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(jsScript, null);
} else {
webView.loadUrl("javascript:" + jsScript);
}
}
}, 800);
break;
case MAKE_HTTPS:
if (scheme.equals("http") && (port == -1 || port == 80)) {
String urlHttps = url.replace("http://", "https://");
HttpURLConnection urlConnection = Util.makeHttpRequest(urlHttps, request.getMethod(), request.getHeaders());
if (urlConnection != null) {
try {
byte[] dataBytes = Util.readAllBytes(urlConnection.getInputStream());
if (dataBytes == null) {
return null;
}
InputStream dataStream = new ByteArrayInputStream(dataBytes);
String encoding = urlConnection.getContentEncoding();
String contentType = urlConnection.getContentType();
if (contentType == null) {
contentType = "text/plain";
} else {
String[] contentTypeSplit = contentType.split(";");
contentType = contentTypeSplit[0].trim();
if (encoding == null) {
encoding = (contentTypeSplit.length > 1 && contentTypeSplit[1].contains("charset="))
? contentTypeSplit[1].replace("charset=", "").trim()
: "utf-8";
}
}
String reasonPhrase = urlConnection.getResponseMessage();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && reasonPhrase != null) {
Map<String, String> responseHeaders = new HashMap<>();
for (Map.Entry<String, List<String>> responseHeader : urlConnection.getHeaderFields().entrySet()) {
responseHeaders.put(responseHeader.getKey(), TextUtils.join(",", responseHeader.getValue()));
}
return new WebResourceResponse(contentType,
encoding,
urlConnection.getResponseCode(),
reasonPhrase,
responseHeaders,
dataStream);
} else {
return new WebResourceResponse(contentType,
encoding,
dataStream);
}
} catch (Exception e) {
if (!(e instanceof SSLHandshakeException)) {
Log.e(LOG_TAG, "", e);
}
} finally {
urlConnection.disconnect();
}
}
// Request mRequest = new Request.Builder().url(urlHttps).build();
// Response response = null;
//
// try {
// response = Util.getBasicOkHttpClient().newCall(mRequest).execute();
// byte[] dataBytes = response.body().bytes();
// InputStream dataStream = new ByteArrayInputStream(dataBytes);
//
// String[] contentTypeSplit = response.header("content-type", "text/plain").split(";");
//
// String contentType = contentTypeSplit[0].trim();
// String encoding = (contentTypeSplit.length > 1 && contentTypeSplit[1].contains("charset="))
// ? contentTypeSplit[1].replace("charset=", "").trim()
// : "utf-8";
//
// response.body().close();
// response.close();
//
// return new WebResourceResponse(contentType, encoding, dataStream);
//
// } catch (Exception e) {
// if (response != null) {
// response.body().close();
// response.close();
// }
// if (!(e instanceof SSLHandshakeException)) {
// Log.e(LOG_TAG, "", e);
// }
// }
}
break;
}
}
}
return null;
}
@Nullable
public WebResourceResponse checkUrl(final InAppWebView webView, WebResourceRequestExt request) throws URISyntaxException, InterruptedException, MalformedURLException {
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(request);
return checkUrl(webView, request, responseResourceType);
}
@Nullable
public WebResourceResponse checkUrl(final InAppWebView webView, WebResourceRequestExt request, String contentType) throws URISyntaxException, InterruptedException, MalformedURLException {
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromContentType(contentType);
return checkUrl(webView, request, responseResourceType);
}
public ContentBlockerTriggerResourceType getResourceTypeFromUrl(WebResourceRequestExt request) {
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
String url = request.getUrl();
if (url.startsWith("http://") || url.startsWith("https://")) {
// make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL.
HttpURLConnection urlConnection = Util.makeHttpRequest(url, "HEAD", request.getHeaders());
if (urlConnection != null) {
try {
String contentType = urlConnection.getContentType();
if (contentType != null) {
String[] contentTypeSplit = contentType.split(";");
contentType = contentTypeSplit[0].trim();
responseResourceType = getResourceTypeFromContentType(contentType);
}
} catch (Exception e) {
Log.e(LOG_TAG, "", e);
} finally {
urlConnection.disconnect();
}
}
}
return responseResourceType;
}
public ContentBlockerTriggerResourceType getResourceTypeFromContentType(String contentType) {
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
if (contentType.equals("text/css")) {
responseResourceType = ContentBlockerTriggerResourceType.STYLE_SHEET;
} else if (contentType.equals("image/svg+xml")) {
responseResourceType = ContentBlockerTriggerResourceType.SVG_DOCUMENT;
} else if (contentType.startsWith("image/")) {
responseResourceType = ContentBlockerTriggerResourceType.IMAGE;
} else if (contentType.startsWith("font/")) {
responseResourceType = ContentBlockerTriggerResourceType.FONT;
} else if (contentType.startsWith("audio/") || contentType.startsWith("video/") || contentType.equals("application/ogg")) {
responseResourceType = ContentBlockerTriggerResourceType.MEDIA;
} else if (contentType.endsWith("javascript")) {
responseResourceType = ContentBlockerTriggerResourceType.SCRIPT;
} else if (contentType.startsWith("text/")) {
responseResourceType = ContentBlockerTriggerResourceType.DOCUMENT;
}
return responseResourceType;
}
}
@@ -0,0 +1,185 @@
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class ContentBlockerTrigger {
@NonNull
private String urlFilter;
private Pattern urlFilterPatternCompiled;
private Boolean urlFilterIsCaseSensitive;
private List<ContentBlockerTriggerResourceType> resourceType = new ArrayList<>();
private List<String> ifDomain = new ArrayList<>();
private List<String> unlessDomain = new ArrayList<>();
private List<String> loadType = new ArrayList<>();
private List<String> ifTopUrl = new ArrayList<>();
private List<String> unlessTopUrl = new ArrayList<>();
public ContentBlockerTrigger(@NonNull String urlFilter, @Nullable Boolean urlFilterIsCaseSensitive, @Nullable List<ContentBlockerTriggerResourceType> resourceType,
@Nullable List<String> ifDomain, @Nullable List<String> unlessDomain, @Nullable List<String> loadType,
@Nullable List<String> ifTopUrl, @Nullable List<String> unlessTopUrl) {
this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive != null ? urlFilterIsCaseSensitive : false;
this.urlFilter = urlFilter;
this.urlFilterPatternCompiled = Pattern.compile(this.urlFilter, this.urlFilterIsCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
this.resourceType = resourceType != null ? resourceType : this.resourceType;
this.ifDomain = ifDomain != null ? ifDomain : this.ifDomain;
this.unlessDomain = unlessDomain != null ? unlessDomain : this.unlessDomain;
if ((!(this.ifDomain.isEmpty() || this.unlessDomain.isEmpty()) != false))
throw new AssertionError();
this.loadType = loadType != null ? loadType : this.loadType;
if ((this.loadType.size() > 2)) throw new AssertionError();
this.ifTopUrl = ifTopUrl != null ? ifTopUrl : this.ifTopUrl;
this.unlessTopUrl = unlessTopUrl != null ? unlessTopUrl : this.unlessTopUrl;
if ((!(this.ifTopUrl.isEmpty() || this.unlessTopUrl.isEmpty()) != false))
throw new AssertionError();
}
public static ContentBlockerTrigger fromMap(Map<String, Object> map) {
String urlFilter = (String) map.get("url-filter");
Boolean urlFilterIsCaseSensitive = (Boolean) map.get("url-filter-is-case-sensitive");
List<String> resourceTypeStringList = (List<String>) map.get("resource-type");
List<ContentBlockerTriggerResourceType> resourceType = new ArrayList<>();
if (resourceTypeStringList != null) {
for (String type : resourceTypeStringList) {
resourceType.add(ContentBlockerTriggerResourceType.fromValue(type));
}
} else {
resourceType.addAll(Arrays.asList(ContentBlockerTriggerResourceType.values()));
}
List<String> ifDomain = (List<String>) map.get("if-domain");
List<String> unlessDomain = (List<String>) map.get("unless-domain");
List<String> loadType = (List<String>) map.get("load-type");
List<String> ifTopUrl = (List<String>) map.get("if-top-url");
List<String> unlessTopUrl = (List<String>) map.get("unless-top-url");
return new ContentBlockerTrigger(urlFilter, urlFilterIsCaseSensitive, resourceType, ifDomain, unlessDomain, loadType, ifTopUrl, unlessTopUrl);
}
@NonNull
public String getUrlFilter() {
return urlFilter;
}
public void setUrlFilter(@NonNull String urlFilter) {
this.urlFilter = urlFilter;
}
public Pattern getUrlFilterPatternCompiled() {
return urlFilterPatternCompiled;
}
public void setUrlFilterPatternCompiled(Pattern urlFilterPatternCompiled) {
this.urlFilterPatternCompiled = urlFilterPatternCompiled;
}
public Boolean getUrlFilterIsCaseSensitive() {
return urlFilterIsCaseSensitive;
}
public void setUrlFilterIsCaseSensitive(Boolean urlFilterIsCaseSensitive) {
this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive;
}
public List<ContentBlockerTriggerResourceType> getResourceType() {
return resourceType;
}
public void setResourceType(List<ContentBlockerTriggerResourceType> resourceType) {
this.resourceType = resourceType;
}
public List<String> getIfDomain() {
return ifDomain;
}
public void setIfDomain(List<String> ifDomain) {
this.ifDomain = ifDomain;
}
public List<String> getUnlessDomain() {
return unlessDomain;
}
public void setUnlessDomain(List<String> unlessDomain) {
this.unlessDomain = unlessDomain;
}
public List<String> getLoadType() {
return loadType;
}
public void setLoadType(List<String> loadType) {
this.loadType = loadType;
}
public List<String> getIfTopUrl() {
return ifTopUrl;
}
public void setIfTopUrl(List<String> ifTopUrl) {
this.ifTopUrl = ifTopUrl;
}
public List<String> getUnlessTopUrl() {
return unlessTopUrl;
}
public void setUnlessTopUrl(List<String> unlessTopUrl) {
this.unlessTopUrl = unlessTopUrl;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ContentBlockerTrigger that = (ContentBlockerTrigger) o;
if (!urlFilter.equals(that.urlFilter)) return false;
if (!urlFilterPatternCompiled.equals(that.urlFilterPatternCompiled)) return false;
if (!urlFilterIsCaseSensitive.equals(that.urlFilterIsCaseSensitive)) return false;
if (!resourceType.equals(that.resourceType)) return false;
if (!ifDomain.equals(that.ifDomain)) return false;
if (!unlessDomain.equals(that.unlessDomain)) return false;
if (!loadType.equals(that.loadType)) return false;
if (!ifTopUrl.equals(that.ifTopUrl)) return false;
return unlessTopUrl.equals(that.unlessTopUrl);
}
@Override
public int hashCode() {
int result = urlFilter.hashCode();
result = 31 * result + urlFilterPatternCompiled.hashCode();
result = 31 * result + urlFilterIsCaseSensitive.hashCode();
result = 31 * result + resourceType.hashCode();
result = 31 * result + ifDomain.hashCode();
result = 31 * result + unlessDomain.hashCode();
result = 31 * result + loadType.hashCode();
result = 31 * result + ifTopUrl.hashCode();
result = 31 * result + unlessTopUrl.hashCode();
return result;
}
@Override
public String toString() {
return "ContentBlockerTrigger{" +
"urlFilter='" + urlFilter + '\'' +
", urlFilterPatternCompiled=" + urlFilterPatternCompiled +
", urlFilterIsCaseSensitive=" + urlFilterIsCaseSensitive +
", resourceType=" + resourceType +
", ifDomain=" + ifDomain +
", unlessDomain=" + unlessDomain +
", loadType=" + loadType +
", ifTopUrl=" + ifTopUrl +
", unlessTopUrl=" + unlessTopUrl +
'}';
}
}
@@ -0,0 +1,36 @@
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
public enum ContentBlockerTriggerResourceType {
DOCUMENT ("document"),
IMAGE ("image"),
STYLE_SHEET ("style-sheet"),
SCRIPT ("script"),
FONT ("font"),
SVG_DOCUMENT ("svg-document"),
MEDIA ("media"),
POPUP ("popup"),
RAW ("raw");
private final String value;
private ContentBlockerTriggerResourceType(String value) {
this.value = value;
}
public boolean equalsValue(String otherValue) {
return value.equals(otherValue);
}
public static ContentBlockerTriggerResourceType fromValue(String value) {
for( ContentBlockerTriggerResourceType type : ContentBlockerTriggerResourceType.values()) {
if(value.equals(type.value))
return type;
}
throw new IllegalArgumentException("No enum constant: " + value);
}
@Override
public String toString() {
return this.value;
}
}
@@ -0,0 +1,95 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.content.Context;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLCredential;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLProtectionSpace;
import java.util.ArrayList;
import java.util.List;
public class CredentialDatabase {
private static CredentialDatabase instance;
static final String LOG_TAG = "CredentialDatabase";
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 2;
public static final String DATABASE_NAME = "CredentialDatabase.db";
public URLProtectionSpaceDao protectionSpaceDao;
public URLCredentialDao credentialDao;
public CredentialDatabaseHelper db;
private CredentialDatabase() {}
private CredentialDatabase(CredentialDatabaseHelper db, URLProtectionSpaceDao protectionSpaceDao, URLCredentialDao credentialDao) {
this.db = db;
this.protectionSpaceDao = protectionSpaceDao;
this.credentialDao = credentialDao;
}
public static CredentialDatabase getInstance(Context context) {
if (instance != null)
return instance;
CredentialDatabaseHelper db = new CredentialDatabaseHelper(context);
instance = new CredentialDatabase(db, new URLProtectionSpaceDao(db), new URLCredentialDao(db));
return instance;
}
public List<URLCredential> getHttpAuthCredentials(String host, String protocol, String realm, Integer port) {
List<URLCredential> credentials = new ArrayList<>();
URLProtectionSpace protectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
if (protectionSpace != null) {
credentials = credentialDao.getAllByProtectionSpaceId(protectionSpace.getId());
}
return credentials;
}
public void clearAllAuthCredentials() {
db.clearAllTables(db.getWritableDatabase());
}
public void removeHttpAuthCredentials(String host, String protocol, String realm, Integer port) {
URLProtectionSpace URLProtectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
if (URLProtectionSpace != null) {
protectionSpaceDao.delete(URLProtectionSpace);
}
}
public void removeHttpAuthCredential(String host, String protocol, String realm, Integer port, String username, String password) {
URLProtectionSpace protectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
if (protectionSpace != null) {
URLCredential credential = credentialDao.find(username, password, protectionSpace.getId());
credentialDao.delete(credential);
}
}
public void setHttpAuthCredential(String host, String protocol, String realm, Integer port, String username, String password) {
URLProtectionSpace protectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
Long protectionSpaceId;
if (protectionSpace == null) {
protectionSpaceId = protectionSpaceDao.insert(new URLProtectionSpace(null, host, protocol, realm, port));
} else {
protectionSpaceId = protectionSpace.getId();
}
URLCredential credential = credentialDao.find(username, password, protectionSpaceId);
if (credential != null) {
boolean needUpdate = false;
if (!credential.getUsername().equals(username)) {
credential.setUsername(username);
needUpdate = true;
}
if (!credential.getPassword().equals(password)) {
credential.setPassword(password);
needUpdate = true;
}
if (needUpdate)
credentialDao.update(credential);
} else {
credential = new URLCredential(null, username, password, protectionSpaceId);
credential.setId(credentialDao.insert(credential));
}
}
}
@@ -0,0 +1,157 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.os.Build;
import android.webkit.WebViewDatabase;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLCredential;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLProtectionSpace;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@RequiresApi(api = Build.VERSION_CODES.O)
public class CredentialDatabaseHandler extends ChannelDelegateImpl {
protected static final String LOG_TAG = "CredentialDatabaseHandler";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database";
@Nullable
public static CredentialDatabase credentialDatabase;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public CredentialDatabaseHandler(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
public static void init(@NonNull InAppWebViewFlutterPlugin plugin) {
if (credentialDatabase == null) {
credentialDatabase = CredentialDatabase.getInstance(plugin.applicationContext);
}
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (plugin != null) {
init(plugin);
}
switch (call.method) {
case "getAllAuthCredentials":
{
List<Map<String, Object>> allCredentials = new ArrayList<>();
if (credentialDatabase != null) {
List<URLProtectionSpace> protectionSpaces = credentialDatabase.protectionSpaceDao.getAll();
for (URLProtectionSpace protectionSpace : protectionSpaces) {
List<Map<String, Object>> credentials = new ArrayList<>();
for (URLCredential credential : credentialDatabase.credentialDao.getAllByProtectionSpaceId(protectionSpace.getId())) {
credentials.add(credential.toMap());
}
Map<String, Object> obj = new HashMap<>();
obj.put("protectionSpace", protectionSpace.toMap());
obj.put("credentials", credentials);
allCredentials.add(obj);
}
}
result.success(allCredentials);
}
break;
case "getHttpAuthCredentials":
{
List<Map<String, Object>> credentials = new ArrayList<>();
if (credentialDatabase != null) {
String host = (String) call.argument("host");
String protocol = (String) call.argument("protocol");
String realm = (String) call.argument("realm");
Integer port = (Integer) call.argument("port");
for (URLCredential credential : credentialDatabase.getHttpAuthCredentials(host, protocol, realm, port)) {
credentials.add(credential.toMap());
}
}
result.success(credentials);
}
break;
case "setHttpAuthCredential":
{
if (credentialDatabase != null) {
String host = (String) call.argument("host");
String protocol = (String) call.argument("protocol");
String realm = (String) call.argument("realm");
Integer port = (Integer) call.argument("port");
String username = (String) call.argument("username");
String password = (String) call.argument("password");
credentialDatabase.setHttpAuthCredential(host, protocol, realm, port, username, password);
result.success(true);
} else {
result.success(false);
}
}
break;
case "removeHttpAuthCredential":
{
if (credentialDatabase != null) {
String host = (String) call.argument("host");
String protocol = (String) call.argument("protocol");
String realm = (String) call.argument("realm");
Integer port = (Integer) call.argument("port");
String username = (String) call.argument("username");
String password = (String) call.argument("password");
credentialDatabase.removeHttpAuthCredential(host, protocol, realm, port, username, password);
result.success(true);
} else {
result.success(false);
}
}
break;
case "removeHttpAuthCredentials":
{
if (credentialDatabase != null) {
String host = (String) call.argument("host");
String protocol = (String) call.argument("protocol");
String realm = (String) call.argument("realm");
Integer port = (Integer) call.argument("port");
credentialDatabase.removeHttpAuthCredentials(host, protocol, realm, port);
result.success(true);
} else {
result.success(false);
}
}
break;
case "clearAllAuthCredentials":
if (credentialDatabase != null) {
credentialDatabase.clearAllAuthCredentials();
if (plugin != null && plugin.applicationContext != null) {
WebViewDatabase.getInstance(plugin.applicationContext).clearHttpAuthUsernamePassword();
}
result.success(true);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
plugin = null;
credentialDatabase = null;
}
}
@@ -0,0 +1,66 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class CredentialDatabaseHelper extends SQLiteOpenHelper {
private static final String SQL_CREATE_PROTECTION_SPACE_TABLE =
"CREATE TABLE " + URLProtectionSpaceContract.FeedEntry.TABLE_NAME + " (" +
URLProtectionSpaceContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST + " TEXT NOT NULL," +
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL + " TEXT," +
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM + " TEXT," +
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT + " INTEGER," +
"UNIQUE(" + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST + ", " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL + ", " +
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM + ", " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT +
")" +
");";
private static final String SQL_CREATE_CREDENTIAL_TABLE =
"CREATE TABLE " + URLCredentialContract.FeedEntry.TABLE_NAME + " (" +
URLCredentialContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME + " TEXT NOT NULL," +
URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD + " TEXT NOT NULL," +
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " INTEGER NOT NULL," +
"UNIQUE(" + URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME + ", " + URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD + ", " +
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID +
")," +
"FOREIGN KEY (" + URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + ") REFERENCES " +
URLProtectionSpaceContract.FeedEntry.TABLE_NAME + " (" + URLProtectionSpaceContract.FeedEntry._ID + ") ON DELETE CASCADE" +
");";
private static final String SQL_DELETE_PROTECTION_SPACE_TABLE =
"DROP TABLE IF EXISTS " + URLProtectionSpaceContract.FeedEntry.TABLE_NAME;
private static final String SQL_DELETE_CREDENTIAL_TABLE =
"DROP TABLE IF EXISTS " + URLCredentialContract.FeedEntry.TABLE_NAME;
public CredentialDatabaseHelper(Context context) {
super(context, CredentialDatabase.DATABASE_NAME, null, CredentialDatabase.DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_PROTECTION_SPACE_TABLE);
db.execSQL(SQL_CREATE_CREDENTIAL_TABLE);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_PROTECTION_SPACE_TABLE);
db.execSQL(SQL_DELETE_CREDENTIAL_TABLE);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
public void clearAllTables(SQLiteDatabase db) {
db.execSQL(SQL_DELETE_PROTECTION_SPACE_TABLE);
db.execSQL(SQL_DELETE_CREDENTIAL_TABLE);
onCreate(db);
}
}
@@ -0,0 +1,15 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.provider.BaseColumns;
public class URLCredentialContract {
private URLCredentialContract() {}
/* Inner class that defines the table contents */
public static class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "credential";
public static final String COLUMN_NAME_USERNAME = "username";
public static final String COLUMN_NAME_PASSWORD = "password";
public static final String COLUMN_NAME_PROTECTION_SPACE_ID = "protection_space_id";
}
}
@@ -0,0 +1,106 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.content.ContentValues;
import android.database.Cursor;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLCredential;
import java.util.ArrayList;
import java.util.List;
public class URLCredentialDao {
CredentialDatabaseHelper credentialDatabaseHelper;
String[] projection = {
URLCredentialContract.FeedEntry._ID,
URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME,
URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD,
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID
};
public URLCredentialDao(CredentialDatabaseHelper credentialDatabaseHelper) {
this.credentialDatabaseHelper = credentialDatabaseHelper;
}
public List<URLCredential> getAllByProtectionSpaceId(Long protectionSpaceId) {
String selection = URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " = ?";
String[] selectionArgs = {protectionSpaceId.toString()};
Cursor cursor = credentialDatabaseHelper.getReadableDatabase().query(
URLCredentialContract.FeedEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
);
List<URLCredential> URLCredentials = new ArrayList<>();
while (cursor.moveToNext()) {
Long id = cursor.getLong(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry._ID));
String username = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME));
String password = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD));
URLCredentials.add(new URLCredential(id, username, password, protectionSpaceId));
}
cursor.close();
return URLCredentials;
}
public URLCredential find(String username, String password, Long protectionSpaceId) {
String selection = URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME + " = ? AND " +
URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD + " = ? AND " +
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " = ?";
String[] selectionArgs = {username, password, protectionSpaceId.toString()};
Cursor cursor = credentialDatabaseHelper.getReadableDatabase().query(
URLCredentialContract.FeedEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
);
URLCredential URLCredential = null;
if (cursor.moveToNext()) {
Long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry._ID));
String rowUsername = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME));
String rowPassword = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD));
URLCredential = new URLCredential(rowId, rowUsername, rowPassword, protectionSpaceId);
}
cursor.close();
return URLCredential;
}
public long insert(URLCredential urlCredential) {
ContentValues credentialValues = new ContentValues();
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME, urlCredential.getUsername());
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD, urlCredential.getPassword());
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID, urlCredential.getProtectionSpaceId());
return credentialDatabaseHelper.getWritableDatabase().insert(URLCredentialContract.FeedEntry.TABLE_NAME, null, credentialValues);
}
public long update(URLCredential urlCredential) {
ContentValues credentialValues = new ContentValues();
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME, urlCredential.getUsername());
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD, urlCredential.getPassword());
String whereClause = URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " = ?";
String[] whereArgs = {urlCredential.getProtectionSpaceId().toString()};
return credentialDatabaseHelper.getWritableDatabase().update(URLCredentialContract.FeedEntry.TABLE_NAME, credentialValues, whereClause, whereArgs);
}
public long delete(URLCredential urlCredential) {
String whereClause = URLCredentialContract.FeedEntry._ID + " = ?";
String[] whereArgs = {urlCredential.getId().toString()};
return credentialDatabaseHelper.getWritableDatabase().delete(URLCredentialContract.FeedEntry.TABLE_NAME, whereClause, whereArgs);
}
}
@@ -0,0 +1,16 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.provider.BaseColumns;
public class URLProtectionSpaceContract {
private URLProtectionSpaceContract() {}
/* Inner class that defines the table contents */
public static class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "protection_space";
public static final String COLUMN_NAME_HOST = "host";
public static final String COLUMN_NAME_PROTOCOL = "protocol";
public static final String COLUMN_NAME_REALM = "realm";
public static final String COLUMN_NAME_PORT = "port";
}
}
@@ -0,0 +1,100 @@
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLProtectionSpace;
import java.util.ArrayList;
import java.util.List;
public class URLProtectionSpaceDao {
CredentialDatabaseHelper credentialDatabaseHelper;
String[] projection = {
URLProtectionSpaceContract.FeedEntry._ID,
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST,
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL,
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM,
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT
};
public URLProtectionSpaceDao(CredentialDatabaseHelper credentialDatabaseHelper) {
this.credentialDatabaseHelper = credentialDatabaseHelper;
}
public List<URLProtectionSpace> getAll() {
SQLiteDatabase readableDatabase = credentialDatabaseHelper.getReadableDatabase();
Cursor cursor = readableDatabase.query(
URLProtectionSpaceContract.FeedEntry.TABLE_NAME,
projection,
null,
null,
null,
null,
null
);
List<URLProtectionSpace> URLProtectionSpaces = new ArrayList<>();
while (cursor.moveToNext()) {
Long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry._ID));
String rowHost = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST));
String rowProtocol = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL));
String rowRealm = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM));
Integer rowPort = cursor.getInt(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT));
URLProtectionSpaces.add(new URLProtectionSpace(rowId, rowHost, rowProtocol, rowRealm, rowPort));
}
cursor.close();
return URLProtectionSpaces;
}
public URLProtectionSpace find(String host, String protocol, String realm, Integer port) {
SQLiteDatabase readableDatabase = credentialDatabaseHelper.getReadableDatabase();
String selection = URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST + " = ? AND " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL + " = ? AND " +
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM + " = ? AND " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT + " = ?";
String[] selectionArgs = {host, protocol, realm, port.toString()};
Cursor cursor = readableDatabase.query(
URLProtectionSpaceContract.FeedEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null
);
URLProtectionSpace URLProtectionSpace = null;
if (cursor.moveToNext()) {
Long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry._ID));
String rowHost = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST));
String rowProtocol = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL));
String rowRealm = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM));
Integer rowPort = cursor.getInt(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT));
URLProtectionSpace = new URLProtectionSpace(rowId, rowHost, rowProtocol, rowRealm, rowPort);
}
cursor.close();
return URLProtectionSpace;
}
public long insert(URLProtectionSpace URLProtectionSpace) {
ContentValues protectionSpaceValues = new ContentValues();
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST, URLProtectionSpace.getHost());
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL, URLProtectionSpace.getProtocol());
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM, URLProtectionSpace.getRealm());
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT, URLProtectionSpace.getPort());
return credentialDatabaseHelper.getWritableDatabase().insert(URLProtectionSpaceContract.FeedEntry.TABLE_NAME, null, protectionSpaceValues);
};
public long delete(URLProtectionSpace URLProtectionSpace) {
String whereClause = URLProtectionSpaceContract.FeedEntry._ID + " = ?";
String[] whereArgs = {URLProtectionSpace.getId().toString()};
return credentialDatabaseHelper.getWritableDatabase().delete(URLProtectionSpaceContract.FeedEntry.TABLE_NAME, whereClause, whereArgs);
}
}
@@ -0,0 +1,94 @@
package com.pichillilorenzo.flutter_inappwebview_android.find_interaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.FindSession;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class FindInteractionChannelDelegate extends ChannelDelegateImpl {
@Nullable
private FindInteractionController findInteractionController;
public FindInteractionChannelDelegate(@NonNull FindInteractionController findInteractionController, @NonNull MethodChannel channel) {
super(channel);
this.findInteractionController = findInteractionController;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "findAll":
if (findInteractionController != null) {
String find = (String) call.argument("find");
findInteractionController.findAll(find);
}
result.success(true);
break;
case "findNext":
if (findInteractionController != null) {
Boolean forward = (Boolean) call.argument("forward");
findInteractionController.findNext(forward);
}
result.success(true);
break;
case "clearMatches":
if (findInteractionController != null) {
findInteractionController.clearMatches();
}
result.success(true);
break;
case "setSearchText":
if (findInteractionController != null) {
findInteractionController.searchText = (String) call.argument("searchText");
result.success(true);
} else {
result.success(false);
}
break;
case "getSearchText":
if (findInteractionController != null) {
result.success(findInteractionController.searchText);
} else {
result.success(false);
}
break;
case "getActiveFindSession":
if (findInteractionController != null && findInteractionController.activeFindSession != null) {
result.success(findInteractionController.activeFindSession.toMap());
} else {
result.success(null);
}
break;
default:
result.notImplemented();
}
}
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
MethodChannel channel = getChannel();
if (channel == null) return;
if (isDoneCounting && findInteractionController != null && findInteractionController.webView != null) {
findInteractionController.activeFindSession = new FindSession(numberOfMatches, activeMatchOrdinal);
}
Map<String, Object> obj = new HashMap<>();
obj.put("activeMatchOrdinal", activeMatchOrdinal);
obj.put("numberOfMatches", numberOfMatches);
obj.put("isDoneCounting", isDoneCounting);
channel.invokeMethod("onFindResultReceived", obj);
}
@Override
public void dispose() {
super.dispose();
findInteractionController = null;
}
}
@@ -0,0 +1,73 @@
package com.pichillilorenzo.flutter_inappwebview_android.find_interaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview_android.types.FindSession;
import com.pichillilorenzo.flutter_inappwebview_android.webview.InAppWebViewInterface;
import io.flutter.plugin.common.MethodChannel;
public class FindInteractionController implements Disposable {
static final String LOG_TAG = "FindInteractionController";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
@Nullable
public InAppWebViewInterface webView;
@Nullable
public FindSession activeFindSession;
@Nullable
public FindInteractionChannelDelegate channelDelegate;
@Nullable
public FindInteractionSettings settings;
@Nullable
public String searchText;
public FindInteractionController(@NonNull InAppWebViewInterface webView, @NonNull InAppWebViewFlutterPlugin plugin,
@NonNull Object id, @Nullable FindInteractionSettings settings) {
this.webView = webView;
this.settings = settings;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
this.channelDelegate = new FindInteractionChannelDelegate(this, channel);
}
public void prepare() {
}
public void findAll(@Nullable String find) {
if (find == null) {
find = searchText;
} else {
// updated searchText
searchText = find;
}
if (webView != null && find != null) {
webView.findAllAsync(find);
}
}
public void findNext(boolean forward) {
if (webView != null) {
webView.findNext(forward);
}
}
public void clearMatches() {
if (webView != null) {
webView.clearMatches();
}
}
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
webView = null;
activeFindSession = null;
searchText = null;
}
}
@@ -0,0 +1,45 @@
package com.pichillilorenzo.flutter_inappwebview_android.find_interaction;
import androidx.annotation.NonNull;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import java.util.HashMap;
import java.util.Map;
public class FindInteractionSettings implements ISettings<FindInteractionController> {
public static final String LOG_TAG = "FindInteractionSettings";
@NonNull
@Override
public FindInteractionSettings parse(@NonNull Map<String, Object> settings) {
// for (Map.Entry<String, Object> pair : settings.entrySet()) {
// String key = pair.getKey();
// Object value = pair.getValue();
// if (value == null) {
// continue;
// }
//
// switch (key) {
//
// }
// }
return this;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull FindInteractionController findInteractionController) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}
@@ -0,0 +1,159 @@
package com.pichillilorenzo.flutter_inappwebview_android.headless_in_app_webview;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.Util;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview_android.types.Size2D;
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.FlutterWebView;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
public class HeadlessInAppWebView implements Disposable {
protected static final String LOG_TAG = "HeadlessInAppWebView";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_headless_inappwebview_";
@NonNull
public final String id;
@Nullable
public HeadlessWebViewChannelDelegate channelDelegate;
@Nullable
public FlutterWebView flutterWebView;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public HeadlessInAppWebView(@NonNull final InAppWebViewFlutterPlugin plugin, @NonNull String id, @NonNull FlutterWebView flutterWebView) {
this.id = id;
this.plugin = plugin;
this.flutterWebView = flutterWebView;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
this.channelDelegate = new HeadlessWebViewChannelDelegate(this, channel);
}
public void onWebViewCreated() {
if (channelDelegate != null) {
channelDelegate.onWebViewCreated();
}
}
public void prepare(Map<String, Object> params) {
if (flutterWebView != null) {
View view = flutterWebView.getView();
if (view != null) {
final Map<String, Object> initialSize = (Map<String, Object>) params.get("initialSize");
Size2D size = Size2D.fromMap(initialSize);
if (size == null) {
size = new Size2D(-1, -1);
}
setSize(size);
view.setVisibility(View.INVISIBLE);
}
}
if (plugin != null && plugin.activity != null) {
// Add the headless WebView to the view hierarchy.
// This way is also possible to take screenshots.
ViewGroup contentView = (ViewGroup) plugin.activity.findViewById(android.R.id.content);
if (contentView != null) {
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
if (mainView != null && flutterWebView != null) {
View view = flutterWebView.getView();
if (view != null) {
mainView.addView(view, 0);
}
}
}
}
}
public void setSize(@NonNull Size2D size) {
if (flutterWebView != null && flutterWebView.webView != null) {
View view = flutterWebView.getView();
if (view != null) {
float scale = Util.getPixelDensity(view.getContext());
Size2D fullscreenSize = Util.getFullscreenSize(view.getContext());
int width = (int) (size.getWidth() == -1 ? fullscreenSize.getWidth() : (size.getWidth() * scale));
int height = (int) (size.getWidth() == -1 ? fullscreenSize.getHeight() : (size.getHeight() * scale));
view.setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
}
}
@Nullable
public Size2D getSize() {
if (flutterWebView != null && flutterWebView.webView != null) {
View view = flutterWebView.getView();
if (view != null) {
float scale = Util.getPixelDensity(view.getContext());
Size2D fullscreenSize = Util.getFullscreenSize(view.getContext());
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
return new Size2D(
fullscreenSize.getWidth() == layoutParams.width ? layoutParams.width : (layoutParams.width / scale),
fullscreenSize.getHeight() == layoutParams.height ? layoutParams.height : (layoutParams.height / scale)
);
}
}
return null;
}
@Nullable
public FlutterWebView disposeAndGetFlutterWebView() {
FlutterWebView newFlutterWebView = flutterWebView;
if (flutterWebView != null) {
View view = flutterWebView.getView();
if (view != null) {
// restore WebView layout params and visibility
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setVisibility(View.VISIBLE);
// remove from parent
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
}
// set to null to avoid to be disposed before calling "dispose()"
flutterWebView = null;
dispose();
}
return newFlutterWebView;
}
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
if (plugin != null) {
HeadlessInAppWebViewManager headlessInAppWebViewManager = plugin.headlessInAppWebViewManager;
if (headlessInAppWebViewManager != null && headlessInAppWebViewManager.webViews.containsKey(id)) {
headlessInAppWebViewManager.webViews.put(id, null);
}
Activity activity = plugin.activity;
if (activity != null) {
ViewGroup contentView = plugin.activity.findViewById(android.R.id.content);
if (contentView != null) {
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
if (mainView != null && flutterWebView != null) {
View view = flutterWebView.getView();
if (view != null) {
mainView.removeView(flutterWebView.getView());
}
}
}
}
}
if (flutterWebView != null) {
flutterWebView.dispose();
}
flutterWebView = null;
plugin = null;
}
}
@@ -0,0 +1,98 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package com.pichillilorenzo.flutter_inappwebview_android.headless_in_app_webview;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.FlutterWebView;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
public class HeadlessInAppWebViewManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "HeadlessInAppWebViewManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview";
public final Map<String, HeadlessInAppWebView> webViews = new HashMap<>();
@Nullable
public InAppWebViewFlutterPlugin plugin;
public HeadlessInAppWebViewManager(final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
@Override
public void onMethodCall(final MethodCall call, @NonNull final Result result) {
final String id = (String) call.argument("id");
switch (call.method) {
case "run":
{
HashMap<String, Object> params = (HashMap<String, Object>) call.argument("params");
run(id, params);
}
result.success(true);
break;
default:
result.notImplemented();
}
}
public void run(String id, HashMap<String, Object> params) {
if (plugin == null || (plugin.activity == null && plugin.applicationContext == null)) return;
Context context = plugin.activity;
if (context == null) {
context = plugin.applicationContext;
}
FlutterWebView flutterWebView = new FlutterWebView(plugin, context, id, params);
HeadlessInAppWebView headlessInAppWebView = new HeadlessInAppWebView(plugin, id, flutterWebView);
webViews.put(id, headlessInAppWebView);
headlessInAppWebView.prepare(params);
headlessInAppWebView.onWebViewCreated();
flutterWebView.makeInitialLoad(params);
}
@Override
public void dispose() {
super.dispose();
Collection<HeadlessInAppWebView> headlessInAppWebViews = webViews.values();
for (HeadlessInAppWebView headlessInAppWebView : headlessInAppWebViews) {
if (headlessInAppWebView != null) {
headlessInAppWebView.dispose();
}
}
webViews.clear();
plugin = null;
}
}
@@ -0,0 +1,71 @@
package com.pichillilorenzo.flutter_inappwebview_android.headless_in_app_webview;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.Size2D;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class HeadlessWebViewChannelDelegate extends ChannelDelegateImpl {
@Nullable
private HeadlessInAppWebView headlessWebView;
public HeadlessWebViewChannelDelegate(@NonNull HeadlessInAppWebView headlessWebView, @NonNull MethodChannel channel) {
super(channel);
this.headlessWebView = headlessWebView;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "dispose":
if (headlessWebView != null) {
headlessWebView.dispose();
result.success(true);
} else {
result.success(false);
}
break;
case "setSize":
if (headlessWebView != null) {
Map<String, Object> sizeMap = (Map<String, Object>) call.argument("size");
Size2D size = Size2D.fromMap(sizeMap);
if (size != null)
headlessWebView.setSize(size);
result.success(true);
} else {
result.success(false);
}
break;
case "getSize":
if (headlessWebView != null) {
Size2D size = headlessWebView.getSize();
result.success(size != null ? size.toMap() : null);
} else {
result.success(null);
}
break;
default:
result.notImplemented();
}
}
public void onWebViewCreated() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onWebViewCreated", obj);
}
@Override
public void dispose() {
super.dispose();
headlessWebView = null;
}
}
@@ -0,0 +1,8 @@
package com.pichillilorenzo.flutter_inappwebview_android.in_app_browser;
import android.content.Intent;
public interface ActivityResultListener {
/** @return true if the result has been handled. */
boolean onActivityResult(int requestCode, int resultCode, Intent data);
}
@@ -0,0 +1,643 @@
package com.pichillilorenzo.flutter_inappwebview_android.in_app_browser;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import com.pichillilorenzo.flutter_inappwebview_android.R;
import com.pichillilorenzo.flutter_inappwebview_android.Util;
import com.pichillilorenzo.flutter_inappwebview_android.find_interaction.FindInteractionController;
import com.pichillilorenzo.flutter_inappwebview_android.pull_to_refresh.PullToRefreshChannelDelegate;
import com.pichillilorenzo.flutter_inappwebview_android.pull_to_refresh.PullToRefreshLayout;
import com.pichillilorenzo.flutter_inappwebview_android.pull_to_refresh.PullToRefreshSettings;
import com.pichillilorenzo.flutter_inappwebview_android.types.AndroidResource;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview_android.types.InAppBrowserMenuItem;
import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview_android.webview.WebViewChannelDelegate;
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView;
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebViewSettings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
public class InAppBrowserActivity extends AppCompatActivity implements InAppBrowserDelegate, Disposable {
protected static final String LOG_TAG = "InAppBrowserActivity";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_";
@Nullable
public Integer windowId;
public String id;
@Nullable
public InAppWebView webView;
@Nullable
public PullToRefreshLayout pullToRefreshLayout;
@Nullable
public ActionBar actionBar;
@Nullable
public Menu menu;
@Nullable
public SearchView searchView;
public InAppBrowserSettings customSettings = new InAppBrowserSettings();
@Nullable
public ProgressBar progressBar;
public boolean isHidden = false;
@Nullable
public String fromActivity;
private List<ActivityResultListener> activityResultListeners = new ArrayList<>();
@Nullable
public InAppBrowserManager manager;
@Nullable
public InAppBrowserChannelDelegate channelDelegate;
public List<InAppBrowserMenuItem> menuItems = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle b = getIntent().getExtras();
if (b == null) return;
id = b.getString("id");
String managerId = b.getString("managerId");
manager = InAppBrowserManager.shared.get(managerId);
if (manager == null || manager.plugin == null|| manager.plugin.messenger == null) return;
Map<String, Object> settingsMap = (Map<String, Object>) b.getSerializable("settings");
customSettings.parse(settingsMap);
windowId = b.getInt("windowId");
setContentView(R.layout.activity_web_view);
Map<String, Object> pullToRefreshInitialSettings = (Map<String, Object>) b.getSerializable("pullToRefreshInitialSettings");
MethodChannel pullToRefreshLayoutChannel = new MethodChannel(manager.plugin.messenger, PullToRefreshLayout.METHOD_CHANNEL_NAME_PREFIX + id);
PullToRefreshSettings pullToRefreshSettings = new PullToRefreshSettings();
pullToRefreshSettings.parse(pullToRefreshInitialSettings);
pullToRefreshLayout = findViewById(R.id.pullToRefresh);
pullToRefreshLayout.channelDelegate = new PullToRefreshChannelDelegate(pullToRefreshLayout, pullToRefreshLayoutChannel);
pullToRefreshLayout.settings = pullToRefreshSettings;
pullToRefreshLayout.prepare();
webView = findViewById(R.id.webView);
webView.id = id;
webView.windowId = windowId;
webView.inAppBrowserDelegate = this;
webView.plugin = manager.plugin;
FindInteractionController findInteractionController = new FindInteractionController(webView, manager.plugin, id, null);
webView.findInteractionController = findInteractionController;
findInteractionController.prepare();
final MethodChannel channel = new MethodChannel(manager.plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
channelDelegate = new InAppBrowserChannelDelegate(channel);
webView.channelDelegate = new WebViewChannelDelegate(webView, channel);
fromActivity = b.getString("fromActivity");
Map<String, Object> contextMenu = (Map<String, Object>) b.getSerializable("contextMenu");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) b.getSerializable("initialUserScripts");
List<Map<String, Object>> menuItemList = (List<Map<String, Object>>) b.getSerializable("menuItems");
for (Map<String, Object> menuItem : menuItemList) {
menuItems.add(InAppBrowserMenuItem.fromMap(menuItem));
}
InAppWebViewSettings webViewSettings = new InAppWebViewSettings();
webViewSettings.parse(settingsMap);
webView.customSettings = webViewSettings;
webView.contextMenu = contextMenu;
List<UserScript> userScripts = new ArrayList<>();
if (initialUserScripts != null) {
for (Map<String, Object> initialUserScript : initialUserScripts) {
userScripts.add(UserScript.fromMap(initialUserScript));
}
}
webView.userContentController.addUserOnlyScripts(userScripts);
actionBar = getSupportActionBar();
prepareView();
if (windowId != -1) {
if (webView.plugin != null && webView.plugin.inAppWebViewManager != null) {
Message resultMsg = webView.plugin.inAppWebViewManager.windowWebViewMessages.get(windowId);
if (resultMsg != null) {
((WebView.WebViewTransport) resultMsg.obj).setWebView(webView);
resultMsg.sendToTarget();
}
}
} else {
String initialFile = b.getString("initialFile");
Map<String, Object> initialUrlRequest = (Map<String, Object>) b.getSerializable("initialUrlRequest");
String initialData = b.getString("initialData");
if (initialFile != null) {
try {
webView.loadFile(initialFile);
} catch (IOException e) {
Log.e(LOG_TAG, initialFile + " asset file cannot be found!", e);
return;
}
}
else if (initialData != null) {
String mimeType = b.getString("initialMimeType");
String encoding = b.getString("initialEncoding");
String baseUrl = b.getString("initialBaseUrl");
String historyUrl = b.getString("initialHistoryUrl");
webView.loadDataWithBaseURL(baseUrl, initialData, mimeType, encoding, historyUrl);
}
else if (initialUrlRequest != null) {
URLRequest urlRequest = URLRequest.fromMap(initialUrlRequest);
if (urlRequest != null) {
webView.loadUrl(urlRequest);
}
}
}
if (channelDelegate != null) {
channelDelegate.onBrowserCreated();
}
}
private void prepareView() {
if (webView != null) {
webView.prepare();
}
if (customSettings.hidden)
hide();
else
show();
progressBar = findViewById(R.id.progressBar);
if (progressBar != null) {
if (customSettings.hideProgressBar)
progressBar.setMax(0);
else
progressBar.setMax(100);
}
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(!customSettings.hideTitleBar);
if (customSettings.hideToolbarTop)
actionBar.hide();
if (customSettings.toolbarTopBackgroundColor != null && !customSettings.toolbarTopBackgroundColor.isEmpty())
actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(customSettings.toolbarTopBackgroundColor)));
if (customSettings.toolbarTopFixedTitle != null && !customSettings.toolbarTopFixedTitle.isEmpty())
actionBar.setTitle(customSettings.toolbarTopFixedTitle);
}
}
@SuppressLint("RestrictedApi")
@Override
public boolean onCreateOptionsMenu(Menu m) {
menu = m;
if (actionBar != null && (customSettings.toolbarTopFixedTitle == null || customSettings.toolbarTopFixedTitle.isEmpty()))
actionBar.setTitle(webView != null ? webView.getTitle() : "");
if (menu == null)
return super.onCreateOptionsMenu(m);
if (menu instanceof MenuBuilder) {
((MenuBuilder) menu).setOptionalIconsVisible(true);
}
MenuInflater inflater = getMenuInflater();
// Inflate menu to add items to action bar if it is present.
inflater.inflate(R.menu.menu_main, menu);
MenuItem menuSearchItem = menu.findItem(R.id.menu_search);
if (menuSearchItem != null) {
if (customSettings.hideUrlBar)
menuSearchItem.setVisible(false);
searchView = (SearchView) menuSearchItem.getActionView();
if (searchView != null) {
searchView.setFocusable(true);
searchView.setQuery(webView != null ? webView.getUrl() : "", false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
if (!query.isEmpty()) {
if (webView != null)
webView.loadUrl(query);
if (searchView != null) {
searchView.setQuery("", false);
searchView.setIconified(true);
}
return true;
}
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
@Override
public boolean onClose() {
if (searchView != null && searchView.getQuery().toString().isEmpty())
searchView.setQuery(webView != null ? webView.getUrl() : "", false);
return false;
}
});
searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (!b && searchView != null) {
searchView.setQuery("", false);
searchView.setIconified(true);
}
}
});
}
}
if (customSettings.hideDefaultMenuItems) {
MenuItem actionClose = menu.findItem(R.id.action_close);
if (actionClose != null) {
actionClose.setVisible(false);
}
MenuItem actionGoBack = menu.findItem(R.id.action_go_back);
if (actionGoBack != null) {
actionGoBack.setVisible(false);
}
MenuItem actionReload = menu.findItem(R.id.action_reload);
if (actionReload != null) {
actionReload.setVisible(false);
}
MenuItem actionGoForward = menu.findItem(R.id.action_go_forward);
if (actionGoForward != null) {
actionGoForward.setVisible(false);
}
MenuItem actionShare = menu.findItem(R.id.action_share);
if (actionShare != null) {
actionShare.setVisible(false);
}
}
for (final InAppBrowserMenuItem menuItem : menuItems) {
int order = menuItem.getOrder() != null ? menuItem.getOrder() : Menu.NONE;
MenuItem item = menu.add(Menu.NONE, menuItem.getId(), order, menuItem.getTitle());
if (menuItem.isShowAsAction()) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
Object icon = menuItem.getIcon();
if (icon != null) {
if (icon instanceof AndroidResource) {
item.setIcon(((AndroidResource) icon).getIdentifier(this));
} else {
item.setIcon(Util.drawableFromBytes(this, (byte[]) icon));
}
String iconColor = menuItem.getIconColor();
if (iconColor != null && !iconColor.isEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
item.getIcon().setTint(Color.parseColor(iconColor));
}
}
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(@NonNull MenuItem item) {
if (channelDelegate != null) {
channelDelegate.onMenuItemClicked(menuItem);
}
return true;
}
});
}
return true;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (customSettings.shouldCloseOnBackButtonPressed) {
close(null);
return true;
}
if (customSettings.allowGoBackWithBackButton) {
if (canGoBack())
goBack();
else if (customSettings.closeOnCannotGoBack)
close(null);
return true;
}
if (!customSettings.shouldCloseOnBackButtonPressed) {
return true;
}
}
return super.onKeyDown(keyCode, event);
}
public void close(final MethodChannel.Result result) {
if (channelDelegate != null) {
channelDelegate.onExit();
}
dispose();
if (result != null) {
result.success(true);
}
}
public void reload() {
if (webView != null)
webView.reload();
}
public void goBack() {
if (webView != null && canGoBack())
webView.goBack();
}
public boolean canGoBack() {
if (webView != null)
return webView.canGoBack();
return false;
}
public void goForward() {
if (webView != null && canGoForward())
webView.goForward();
}
public boolean canGoForward() {
if (webView != null)
return webView.canGoForward();
return false;
}
public void hide() {
if (fromActivity != null) {
try {
isHidden = true;
Intent openActivity = new Intent(this, Class.forName(fromActivity));
openActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityIfNeeded(openActivity, 0);
} catch (ClassNotFoundException e) {
Log.d(LOG_TAG, "", e);
}
}
}
public void show() {
isHidden = false;
Intent openActivity = new Intent(this, InAppBrowserActivity.class);
openActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityIfNeeded(openActivity, 0);
}
public void goBackButtonClicked(MenuItem item) {
goBack();
}
public void goForwardButtonClicked(MenuItem item) {
goForward();
}
public void shareButtonClicked(MenuItem item) {
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("text/plain");
share.putExtra(Intent.EXTRA_TEXT, webView != null ? webView.getUrl() : "");
startActivity(Intent.createChooser(share, "Share"));
}
public void reloadButtonClicked(MenuItem item) {
reload();
}
public void closeButtonClicked(MenuItem item) {
close(null);
}
public void setSettings(InAppBrowserSettings newSettings, HashMap<String, Object> newSettingsMap) {
InAppWebViewSettings newInAppWebViewSettings = new InAppWebViewSettings();
newInAppWebViewSettings.parse(newSettingsMap);
if (webView != null) {
webView.setSettings(newInAppWebViewSettings, newSettingsMap);
}
if (newSettingsMap.get("hidden") != null && customSettings.hidden != newSettings.hidden) {
if (newSettings.hidden)
hide();
else
show();
}
if (newSettingsMap.get("hideProgressBar") != null && customSettings.hideProgressBar != newSettings.hideProgressBar && progressBar != null) {
if (newSettings.hideProgressBar)
progressBar.setMax(0);
else
progressBar.setMax(100);
}
if (actionBar != null && newSettingsMap.get("hideTitleBar") != null && customSettings.hideTitleBar != newSettings.hideTitleBar)
actionBar.setDisplayShowTitleEnabled(!newSettings.hideTitleBar);
if (actionBar != null && newSettingsMap.get("hideToolbarTop") != null && customSettings.hideToolbarTop != newSettings.hideToolbarTop) {
if (newSettings.hideToolbarTop)
actionBar.hide();
else
actionBar.show();
}
if (actionBar != null && newSettingsMap.get("toolbarTopBackgroundColor") != null &&
!Util.objEquals(customSettings.toolbarTopBackgroundColor, newSettings.toolbarTopBackgroundColor) &&
newSettings.toolbarTopBackgroundColor != null && !newSettings.toolbarTopBackgroundColor.isEmpty())
actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(newSettings.toolbarTopBackgroundColor)));
if (actionBar != null && newSettingsMap.get("toolbarTopFixedTitle") != null &&
!Util.objEquals(customSettings.toolbarTopFixedTitle, newSettings.toolbarTopFixedTitle) &&
newSettings.toolbarTopFixedTitle != null && !newSettings.toolbarTopFixedTitle.isEmpty())
actionBar.setTitle(newSettings.toolbarTopFixedTitle);
if (menu != null && newSettingsMap.get("hideUrlBar") != null && customSettings.hideUrlBar != newSettings.hideUrlBar) {
MenuItem menuSearchItem = menu.findItem(R.id.menu_search);
if (menuSearchItem != null) {
menuSearchItem.setVisible(!newSettings.hideUrlBar);
}
}
if (menu != null && newSettingsMap.get("hideDefaultMenuItems") != null && customSettings.hideDefaultMenuItems != newSettings.hideDefaultMenuItems) {
MenuItem actionClose = menu.findItem(R.id.action_close);
if (actionClose != null) {
actionClose.setVisible(!newSettings.hideDefaultMenuItems);
}
MenuItem actionGoBack = menu.findItem(R.id.action_go_back);
if (actionGoBack != null) {
actionGoBack.setVisible(!newSettings.hideDefaultMenuItems);
}
MenuItem actionReload = menu.findItem(R.id.action_reload);
if (actionReload != null) {
actionReload.setVisible(!newSettings.hideDefaultMenuItems);
}
MenuItem actionGoForward = menu.findItem(R.id.action_go_forward);
if (actionGoForward != null) {
actionGoForward.setVisible(!newSettings.hideDefaultMenuItems);
}
MenuItem actionShare = menu.findItem(R.id.action_share);
if (actionShare != null) {
actionShare.setVisible(!newSettings.hideDefaultMenuItems);
}
}
customSettings = newSettings;
}
public Map<String, Object> getCustomSettings() {
Map<String, Object> webViewSettingsMap = webView != null ? webView.getCustomSettings() : null;
if (customSettings == null || webViewSettingsMap == null)
return null;
Map<String, Object> settingsMap = customSettings.getRealSettings(this);
settingsMap.putAll(webViewSettingsMap);
return settingsMap;
}
@Override
public Activity getActivity() {
return this;
}
@Override
public void didChangeTitle(String title) {
if (actionBar != null && (customSettings.toolbarTopFixedTitle == null || customSettings.toolbarTopFixedTitle.isEmpty())) {
actionBar.setTitle(title);
}
}
@Override
public void didStartNavigation(String url) {
if (progressBar != null) {
progressBar.setProgress(0);
}
if (searchView != null) {
searchView.setQuery(url, false);
}
}
@Override
public void didUpdateVisitedHistory(String url) {
if (searchView != null) {
searchView.setQuery(url, false);
}
}
@Override
public void didFinishNavigation(String url) {
if (searchView != null) {
searchView.setQuery(url, false);
}
if (progressBar != null) {
progressBar.setProgress(0);
}
}
@Override
public void didFailNavigation(String url, int errorCode, String description) {
if (progressBar != null) {
progressBar.setProgress(0);
}
}
@Override
public void didChangeProgress(int progress) {
if (progressBar != null) {
progressBar.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
progressBar.setProgress(progress, true);
} else {
progressBar.setProgress(progress);
}
if (progress == 100) {
progressBar.setVisibility(View.GONE);
}
}
}
public List<ActivityResultListener> getActivityResultListeners() {
return activityResultListeners;
}
@Override
protected void onActivityResult (int requestCode,
int resultCode,
Intent data) {
for (ActivityResultListener listener : activityResultListeners) {
if (listener.onActivityResult(requestCode, resultCode, data)) {
return;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
activityResultListeners.clear();
if (webView != null) {
if (manager != null && manager.plugin != null &&
manager.plugin.activityPluginBinding != null && webView.inAppWebViewChromeClient != null) {
manager.plugin.activityPluginBinding.removeActivityResultListener(webView.inAppWebViewChromeClient);
}
RelativeLayout containerView = (RelativeLayout) findViewById(R.id.container);
if (containerView != null) {
containerView.removeAllViews();
}
webView.dispose();
webView = null;
finish();
}
}
@Override
public void onDestroy() {
dispose();
super.onDestroy();
}
}
@@ -0,0 +1,39 @@
package com.pichillilorenzo.flutter_inappwebview_android.in_app_browser;
import androidx.annotation.NonNull;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.InAppBrowserMenuItem;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
public class InAppBrowserChannelDelegate extends ChannelDelegateImpl {
public InAppBrowserChannelDelegate(@NonNull MethodChannel channel) {
super(channel);
}
public void onBrowserCreated() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onBrowserCreated", obj);
}
public void onMenuItemClicked(InAppBrowserMenuItem menuItem) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("id", menuItem.getId());
channel.invokeMethod("onMenuItemClicked", obj);
}
public void onExit() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onExit", obj);
}
}
@@ -0,0 +1,16 @@
package com.pichillilorenzo.flutter_inappwebview_android.in_app_browser;
import android.app.Activity;
import java.util.List;
public interface InAppBrowserDelegate {
Activity getActivity();
List<ActivityResultListener> getActivityResultListeners();
void didChangeTitle(String title);
void didStartNavigation(String url);
void didUpdateVisitedHistory(String url);
void didFinishNavigation(String url);
void didFailNavigation(String url, int errorCode, String description);
void didChangeProgress(int progress);
}
@@ -0,0 +1,219 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package com.pichillilorenzo.flutter_inappwebview_android.in_app_browser;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.Browser;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
/**
* InAppBrowserManager
*/
public class InAppBrowserManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "InAppBrowserManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappbrowser";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public String id;
public static final Map<String, InAppBrowserManager> shared = new HashMap<>();
public InAppBrowserManager(final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.id = UUID.randomUUID().toString();
this.plugin = plugin;
shared.put(this.id, this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "open":
if (plugin != null && plugin.activity != null) {
open(plugin.activity, (Map<String, Object>) call.arguments());
result.success(true);
} else {
result.success(false);
}
break;
case "openWithSystemBrowser":
if (plugin != null && plugin.activity != null) {
String url = (String) call.argument("url");
openWithSystemBrowser(plugin.activity, url, result);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
public static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
/**
* Display a new browser with the specified URL.
*
* @param url the url to load.
* @param result
* @return "" if ok, or error message.
*/
public void openWithSystemBrowser(Activity activity, String url, Result result) {
try {
Intent intent;
intent = new Intent(Intent.ACTION_VIEW);
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
Uri uri = Uri.parse(url);
if ("file".equals(uri.getScheme())) {
intent.setDataAndType(uri, getMimeType(url));
} else {
intent.setData(uri);
}
intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
// CB-10795: Avoid circular loops by preventing it from opening in the current app
this.openExternalExcludeCurrentApp(activity, intent);
result.success(true);
// not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it
} catch (java.lang.RuntimeException e) {
Log.d(LOG_TAG, url + " cannot be opened: " + e.toString());
result.error(LOG_TAG, url + " cannot be opened!", null);
}
}
/**
* Opens the intent, providing a chooser that excludes the current app to avoid
* circular loops.
*/
public void openExternalExcludeCurrentApp(Activity activity, Intent intent) {
String currentPackage = activity.getPackageName();
boolean hasCurrentPackage = false;
PackageManager pm = activity.getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
ArrayList<Intent> targetIntents = new ArrayList<Intent>();
for (ResolveInfo ri : activities) {
if (!currentPackage.equals(ri.activityInfo.packageName)) {
Intent targetIntent = (Intent) intent.clone();
targetIntent.setPackage(ri.activityInfo.packageName);
targetIntents.add(targetIntent);
} else {
hasCurrentPackage = true;
}
}
// If the current app package isn't a target for this URL, then use
// the normal launch behavior
if (!hasCurrentPackage || targetIntents.size() == 0) {
activity.startActivity(intent);
}
// If there's only one possible intent, launch it directly
else if (targetIntents.size() == 1) {
activity.startActivity(targetIntents.get(0));
}
// Otherwise, show a custom chooser without the current app listed
else if (targetIntents.size() > 0) {
Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size() - 1), null);
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[]{}));
activity.startActivity(chooser);
}
}
public void open(Activity activity, Map<String, Object> arguments) {
String id = (String) arguments.get("id");
Map<String, Object> urlRequest = (Map<String, Object>) arguments.get("urlRequest");
String assetFilePath = (String) arguments.get("assetFilePath");
String data = (String) arguments.get("data");
String mimeType = (String) arguments.get("mimeType");
String encoding = (String) arguments.get("encoding");
String baseUrl = (String) arguments.get("baseUrl");
String historyUrl = (String) arguments.get("historyUrl");
Map<String, Object> settings = (Map<String, Object>) arguments.get("settings");
Map<String, Object> contextMenu = (Map<String, Object>) arguments.get("contextMenu");
Integer windowId = (Integer) arguments.get("windowId");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) arguments.get("initialUserScripts");
Map<String, Object> pullToRefreshInitialSettings = (Map<String, Object>) arguments.get("pullToRefreshSettings");
List<Map<String, Object>> menuItems = (List<Map<String, Object>>) arguments.get("menuItems");
Bundle extras = new Bundle();
extras.putString("fromActivity", activity.getClass().getName());
extras.putSerializable("initialUrlRequest", (Serializable) urlRequest);
extras.putString("initialFile", assetFilePath);
extras.putString("initialData", data);
extras.putString("initialMimeType", mimeType);
extras.putString("initialEncoding", encoding);
extras.putString("initialBaseUrl", baseUrl);
extras.putString("initialHistoryUrl", historyUrl);
extras.putString("id", id);
extras.putString("managerId", this.id);
extras.putSerializable("settings", (Serializable) settings);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
extras.putInt("windowId", windowId != null ? windowId : -1);
extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts);
extras.putSerializable("pullToRefreshInitialSettings", (Serializable) pullToRefreshInitialSettings);
extras.putSerializable("menuItems", (Serializable) menuItems);
startInAppBrowserActivity(activity, extras);
}
public void startInAppBrowserActivity(Activity activity, Bundle extras) {
Intent intent = new Intent(activity, InAppBrowserActivity.class);
if (extras != null)
intent.putExtras(extras);
activity.startActivity(intent);
}
@Override
public void dispose() {
super.dispose();
shared.remove(this.id);
plugin = null;
}
}
@@ -0,0 +1,109 @@
package com.pichillilorenzo.flutter_inappwebview_android.in_app_browser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import com.pichillilorenzo.flutter_inappwebview_android.R;
import java.util.HashMap;
import java.util.Map;
public class InAppBrowserSettings implements ISettings<InAppBrowserActivity> {
public static final String LOG_TAG = "InAppBrowserSettings";
public Boolean hidden = false;
public Boolean hideToolbarTop = false;
@Nullable
public String toolbarTopBackgroundColor;
@Nullable
public String toolbarTopFixedTitle;
public Boolean hideUrlBar = false;
public Boolean hideProgressBar = false;
public Boolean hideTitleBar = false;
public Boolean closeOnCannotGoBack = true;
public Boolean allowGoBackWithBackButton = true;
public Boolean shouldCloseOnBackButtonPressed = false;
public Boolean hideDefaultMenuItems = false;
@NonNull
@Override
public InAppBrowserSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "hidden":
hidden = (Boolean) value;
break;
case "hideToolbarTop":
hideToolbarTop = (Boolean) value;
break;
case "toolbarTopBackgroundColor":
toolbarTopBackgroundColor = (String) value;
break;
case "toolbarTopFixedTitle":
toolbarTopFixedTitle = (String) value;
break;
case "hideUrlBar":
hideUrlBar = (Boolean) value;
break;
case "hideTitleBar":
hideTitleBar = (Boolean) value;
break;
case "closeOnCannotGoBack":
closeOnCannotGoBack = (Boolean) value;
break;
case "hideProgressBar":
hideProgressBar = (Boolean) value;
break;
case "allowGoBackWithBackButton":
allowGoBackWithBackButton = (Boolean) value;
break;
case "shouldCloseOnBackButtonPressed":
shouldCloseOnBackButtonPressed = (Boolean) value;
break;
case "hideDefaultMenuItems":
hideDefaultMenuItems = (Boolean) value;
break;
}
}
return this;
}
@NonNull
@Override
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("hidden", hidden);
settings.put("hideToolbarTop", hideToolbarTop);
settings.put("toolbarTopBackgroundColor", toolbarTopBackgroundColor);
settings.put("toolbarTopFixedTitle", toolbarTopFixedTitle);
settings.put("hideUrlBar", hideUrlBar);
settings.put("hideTitleBar", hideTitleBar);
settings.put("closeOnCannotGoBack", closeOnCannotGoBack);
settings.put("hideProgressBar", hideProgressBar);
settings.put("allowGoBackWithBackButton", allowGoBackWithBackButton);
settings.put("shouldCloseOnBackButtonPressed", shouldCloseOnBackButtonPressed);
settings.put("hideDefaultMenuItems", hideDefaultMenuItems);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull InAppBrowserActivity inAppBrowserActivity) {
Map<String, Object> realSettings = toMap();
realSettings.put("hidden", inAppBrowserActivity.isHidden);
realSettings.put("hideToolbarTop", inAppBrowserActivity.actionBar == null || !inAppBrowserActivity.actionBar.isShowing());
realSettings.put("hideUrlBar", inAppBrowserActivity.menu == null || !inAppBrowserActivity.menu.findItem(R.id.menu_search).isVisible());
realSettings.put("hideProgressBar", inAppBrowserActivity.progressBar == null || inAppBrowserActivity.progressBar.getMax() == 0);
return realSettings;
}
}
@@ -0,0 +1,266 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class InterceptAjaxRequestJS {
public static final String INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT";
public static final String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useShouldInterceptAjaxRequest";
public static final String FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._interceptOnlyAsyncAjaxRequests";
public static final PluginScript INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = new PluginScript(
InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
true,
null
);
public static PluginScript createInterceptOnlyAsyncAjaxRequestsPluginScript(boolean onlyAsync) {
return new PluginScript(
InterceptAjaxRequestJS.INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
"window." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE + " = " + onlyAsync +";",
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
true,
null
);
}
public static final String INTERCEPT_AJAX_REQUEST_JS_SOURCE = "(function(ajax) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " = true;" +
" var send = ajax.prototype.send;" +
" var open = ajax.prototype.open;" +
" var setRequestHeader = ajax.prototype.setRequestHeader;" +
" ajax.prototype._flutter_inappwebview_url = null;" +
" ajax.prototype._flutter_inappwebview_method = null;" +
" ajax.prototype._flutter_inappwebview_isAsync = null;" +
" ajax.prototype._flutter_inappwebview_user = null;" +
" ajax.prototype._flutter_inappwebview_password = null;" +
" ajax.prototype._flutter_inappwebview_password = null;" +
" ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false;" +
" ajax.prototype._flutter_inappwebview_request_headers = {};" +
" function convertRequestResponse(request, callback) {" +
" if (request.response != null && request.responseType != null) {" +
" switch (request.responseType) {" +
" case 'arraybuffer':" +
" callback(new Uint8Array(request.response));" +
" return;" +
" case 'blob':" +
" const reader = new FileReader();" +
" reader.addEventListener('loadend', function() { " +
" callback(new Uint8Array(reader.result));" +
" });" +
" reader.readAsArrayBuffer(blob);" +
" return;" +
" case 'document':" +
" callback(request.response.documentElement.outerHTML);" +
" return;" +
" case 'json':" +
" callback(request.response);" +
" return;" +
" };" +
" }" +
" callback(null);" +
" };" +
" ajax.prototype.open = function(method, url, isAsync, user, password) {" +
" isAsync = (isAsync != null) ? isAsync : true;" +
" this._flutter_inappwebview_url = url;" +
" this._flutter_inappwebview_method = method;" +
" this._flutter_inappwebview_isAsync = isAsync;" +
" this._flutter_inappwebview_user = user;" +
" this._flutter_inappwebview_password = password;" +
" this._flutter_inappwebview_request_headers = {};" +
" open.call(this, method, url, isAsync, user, password);" +
" };" +
" ajax.prototype.setRequestHeader = function(header, value) {" +
" this._flutter_inappwebview_request_headers[header] = value;" +
" setRequestHeader.call(this, header, value);" +
" };" +
" function handleEvent(e) {" +
" var self = this;" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true) {" +
" var headers = this.getAllResponseHeaders();" +
" var responseHeaders = {};" +
" if (headers != null) {" +
" var arr = headers.trim().split(/[\\r\\n]+/);" +
" arr.forEach(function (line) {" +
" var parts = line.split(': ');" +
" var header = parts.shift();" +
" var value = parts.join(': ');" +
" responseHeaders[header] = value;" +
" });" +
" }" +
" convertRequestResponse(this, function(response) {" +
" var ajaxRequest = {" +
" method: self._flutter_inappwebview_method," +
" url: self._flutter_inappwebview_url," +
" isAsync: self._flutter_inappwebview_isAsync," +
" user: self._flutter_inappwebview_user," +
" password: self._flutter_inappwebview_password," +
" withCredentials: self.withCredentials," +
" headers: self._flutter_inappwebview_request_headers," +
" readyState: self.readyState," +
" status: self.status," +
" responseURL: self.responseURL," +
" responseType: self.responseType," +
" response: response," +
" responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null," +
" responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null," +
" statusText: self.statusText," +
" responseHeaders, responseHeaders," +
" event: {" +
" type: e.type," +
" loaded: e.loaded," +
" lengthComputable: e.lengthComputable," +
" total: e.total" +
" }" +
" };" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onAjaxProgress', ajaxRequest).then(function(result) {" +
" if (result != null) {" +
" switch (result) {" +
" case 0:" +
" self.abort();" +
" return;" +
" };" +
" }" +
" });" +
" });" +
" }" +
" };" +
" ajax.prototype.send = function(data) {" +
" var self = this;" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" var canBeIntercepted = self._flutter_inappwebview_isAsync || w." + FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE + " === false;" +
" if (canBeIntercepted && (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true)) {" +
" if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {" +
" this._flutter_inappwebview_already_onreadystatechange_wrapped = true;" +
" var onreadystatechange = this.onreadystatechange;" +
" this.onreadystatechange = function() {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE + " == true) {" +
" var headers = this.getAllResponseHeaders();" +
" var responseHeaders = {};" +
" if (headers != null) {" +
" var arr = headers.trim().split(/[\\r\\n]+/);" +
" arr.forEach(function (line) {" +
" var parts = line.split(': ');" +
" var header = parts.shift();" +
" var value = parts.join(': ');" +
" responseHeaders[header] = value;" +
" });" +
" }" +
" convertRequestResponse(this, function(response) {" +
" var ajaxRequest = {" +
" method: self._flutter_inappwebview_method," +
" url: self._flutter_inappwebview_url," +
" isAsync: self._flutter_inappwebview_isAsync," +
" user: self._flutter_inappwebview_user," +
" password: self._flutter_inappwebview_password," +
" withCredentials: self.withCredentials," +
" headers: self._flutter_inappwebview_request_headers," +
" readyState: self.readyState," +
" status: self.status," +
" responseURL: self.responseURL," +
" responseType: self.responseType," +
" response: response," +
" responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null," +
" responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null," +
" statusText: self.statusText," +
" responseHeaders: responseHeaders" +
" };" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {" +
" if (result != null) {" +
" switch (result) {" +
" case 0:" +
" self.abort();" +
" return;" +
" };" +
" }" +
" if (onreadystatechange != null) {" +
" onreadystatechange();" +
" }" +
" });" +
" });" +
" } else if (onreadystatechange != null) {" +
" onreadystatechange();" +
" }" +
" };" +
" }" +
" this.addEventListener('loadstart', handleEvent);" +
" this.addEventListener('load', handleEvent);" +
" this.addEventListener('loadend', handleEvent);" +
" this.addEventListener('progress', handleEvent);" +
" this.addEventListener('error', handleEvent);" +
" this.addEventListener('abort', handleEvent);" +
" this.addEventListener('timeout', handleEvent);" +
" " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(data).then(function(data) {" +
" var ajaxRequest = {" +
" data: data," +
" method: self._flutter_inappwebview_method," +
" url: self._flutter_inappwebview_url," +
" isAsync: self._flutter_inappwebview_isAsync," +
" user: self._flutter_inappwebview_user," +
" password: self._flutter_inappwebview_password," +
" withCredentials: self.withCredentials," +
" headers: self._flutter_inappwebview_request_headers," +
" responseType: self.responseType" +
" };" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" +
" if (result != null) {" +
" switch (result) {" +
" case 0:" +
" self.abort();" +
" return;" +
" };" +
" if (result.data != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) && result.data.length > 0) {" +
" var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.data);" +
" if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" +
" var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".getFormDataContentType(bodyString);" +
" if (result.headers != null) {" +
" result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" +
" } else {" +
" result.headers = { 'Content-Type': formDataContentType };" +
" }" +
" }" +
" }" +
" if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) || result.data == null) {" +
" data = result.data;" +
" } else if (result.data.length > 0) {" +
" data = new Uint8Array(result.data);" +
" }" +
" self.withCredentials = result.withCredentials;" +
" if (result.responseType != null && self._flutter_inappwebview_isAsync) {" +
" self.responseType = result.responseType;" +
" };" +
" for (var header in result.headers) {" +
" var value = result.headers[header];" +
" var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];" +
" if (flutter_inappwebview_value == null) {" +
" self._flutter_inappwebview_request_headers[header] = value;" +
" } else {" +
" self._flutter_inappwebview_request_headers[header] += ', ' + value;" +
" }" +
" setRequestHeader.call(self, header, value);" +
" };" +
" if ((self._flutter_inappwebview_method != result.method && result.method != null) ||" +
" (self._flutter_inappwebview_url != result.url && result.url != null) ||" +
" (self._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) ||" +
" (self._flutter_inappwebview_user != result.user && result.user != null) ||" +
" (self._flutter_inappwebview_password != result.password && result.password != null)) {" +
" self.abort();" +
" self.open(result.method, result.url, result.isAsync, result.user, result.password);" +
" }" +
" }" +
" send.call(self, data);" +
" });" +
" });" +
" } else {" +
" send.call(this, data);" +
" }" +
" };" +
"})(window.XMLHttpRequest);";
}
@@ -0,0 +1,152 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class InterceptFetchRequestJS {
public static final String INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT";
public static final String FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useShouldInterceptFetchRequest";
public static final PluginScript INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = new PluginScript(
InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
InterceptFetchRequestJS.INTERCEPT_FETCH_REQUEST_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
true,
null
);
public static final String INTERCEPT_FETCH_REQUEST_JS_SOURCE = "(function(fetch) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " = true;" +
" if (fetch == null) {" +
" return;" +
" }" +
" window.fetch = async function(resource, init) {" +
" var w = (window.top == null || window.top === window) ? window : window.top;" +
" if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == true) {" +
" var fetchRequest = {" +
" url: null," +
" method: null," +
" headers: null," +
" body: null," +
" mode: null," +
" credentials: null," +
" cache: null," +
" redirect: null," +
" referrer: null," +
" referrerPolicy: null," +
" integrity: null," +
" keepalive: null" +
" };" +
" if (resource instanceof Request) {" +
" fetchRequest.url = resource.url;" +
" fetchRequest.method = resource.method;" +
" fetchRequest.headers = resource.headers;" +
" fetchRequest.body = resource.body;" +
" fetchRequest.mode = resource.mode;" +
" fetchRequest.credentials = resource.credentials;" +
" fetchRequest.cache = resource.cache;" +
" fetchRequest.redirect = resource.redirect;" +
" fetchRequest.referrer = resource.referrer;" +
" fetchRequest.referrerPolicy = resource.referrerPolicy;" +
" fetchRequest.integrity = resource.integrity;" +
" fetchRequest.keepalive = resource.keepalive;" +
" } else {" +
" fetchRequest.url = resource != null ? resource.toString() : null;" +
" if (init != null) {" +
" fetchRequest.method = init.method;" +
" fetchRequest.headers = init.headers;" +
" fetchRequest.body = init.body;" +
" fetchRequest.mode = init.mode;" +
" fetchRequest.credentials = init.credentials;" +
" fetchRequest.cache = init.cache;" +
" fetchRequest.redirect = init.redirect;" +
" fetchRequest.referrer = init.referrer;" +
" fetchRequest.referrerPolicy = init.referrerPolicy;" +
" fetchRequest.integrity = init.integrity;" +
" fetchRequest.keepalive = init.keepalive;" +
" }" +
" }" +
" if (fetchRequest.headers instanceof Headers) {" +
" fetchRequest.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertHeadersToJson(fetchRequest.headers);" +
" }" +
" fetchRequest.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertCredentialsToJson(fetchRequest.credentials);" +
" return " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(fetchRequest.body).then(function(body) {" +
" fetchRequest.body = body;" +
" return window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {" +
" if (result != null) {" +
" switch (result.action) {" +
" case 0:" +
" var controller = new AbortController();" +
" if (init != null) {" +
" init.signal = controller.signal;" +
" } else {" +
" init = {" +
" signal: controller.signal" +
" };" +
" }" +
" controller.abort();" +
" break;" +
" }" +
" if (result.body != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) && result.body.length > 0) {" +
" var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.body);" +
" if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" +
" var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".getFormDataContentType(bodyString);" +
" if (result.headers != null) {" +
" result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" +
" } else {" +
" result.headers = { 'Content-Type': formDataContentType };" +
" }" +
" }" +
" }" +
" resource = result.url;" +
" if (init == null) {" +
" init = {};" +
" }" +
" if (result.method != null && result.method.length > 0) {" +
" init.method = result.method;" +
" }" +
" if (result.headers != null && Object.keys(result.headers).length > 0) {" +
" init.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToHeaders(result.headers);" +
" }" +
" if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) || result.body == null) {" +
" init.body = result.body;" +
" } else if (result.body.length > 0) {" +
" init.body = new Uint8Array(result.body);" +
" }" +
" if (result.mode != null && result.mode.length > 0) {" +
" init.mode = result.mode;" +
" }" +
" if (result.credentials != null) {" +
" init.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToCredential(result.credentials);" +
" }" +
" if (result.cache != null && result.cache.length > 0) {" +
" init.cache = result.cache;" +
" }" +
" if (result.redirect != null && result.redirect.length > 0) {" +
" init.redirect = result.redirect;" +
" }" +
" if (result.referrer != null && result.referrer.length > 0) {" +
" init.referrer = result.referrer;" +
" }" +
" if (result.referrerPolicy != null && result.referrerPolicy.length > 0) {" +
" init.referrerPolicy = result.referrerPolicy;" +
" }" +
" if (result.integrity != null && result.integrity.length > 0) {" +
" init.integrity = result.integrity;" +
" }" +
" if (result.keepalive != null) {" +
" init.keepalive = result.keepalive;" +
" }" +
" return fetch(resource, init);" +
" }" +
" return fetch(resource, init);" +
" });" +
" });" +
" } else {" +
" return fetch(resource, init);" +
" }" +
" };" +
"})(window.fetch);";
}
@@ -0,0 +1,250 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class JavaScriptBridgeJS {
public static final String JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview";
public static final String JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT";
public static final PluginScript JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = new PluginScript(
JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME,
JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
true,
null
);
public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util";
public static final String WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._webMessageChannels";
public static final String UTIL_JS_SOURCE = JAVASCRIPT_UTIL_VAR_NAME + " = {" +
" support: {" +
" searchParams: 'URLSearchParams' in window," +
" iterable: 'Symbol' in window && 'iterator' in Symbol," +
" blob:" +
" 'FileReader' in window &&" +
" 'Blob' in window &&" +
" (function() {" +
" try {" +
" new Blob();" +
" return true;" +
" } catch (e) {" +
" return false;" +
" }" +
" })()," +
" formData: 'FormData' in window," +
" arrayBuffer: 'ArrayBuffer' in window" +
" }," +
" isDataView: function(obj) {" +
" return obj && DataView.prototype.isPrototypeOf(obj);" +
" }," +
" fileReaderReady: function(reader) {" +
" return new Promise(function(resolve, reject) {" +
" reader.onload = function() {" +
" resolve(reader.result);" +
" };" +
" reader.onerror = function() {" +
" reject(reader.error);" +
" };" +
" });" +
" }," +
" readBlobAsArrayBuffer: function(blob) {" +
" var reader = new FileReader();" +
" var promise = " + JAVASCRIPT_UTIL_VAR_NAME + ".fileReaderReady(reader);" +
" reader.readAsArrayBuffer(blob);" +
" return promise;" +
" }," +
" convertBodyToArrayBuffer: function(body) {" +
" var viewClasses = [" +
" '[object Int8Array]'," +
" '[object Uint8Array]'," +
" '[object Uint8ClampedArray]'," +
" '[object Int16Array]'," +
" '[object Uint16Array]'," +
" '[object Int32Array]'," +
" '[object Uint32Array]'," +
" '[object Float32Array]'," +
" '[object Float64Array]'" +
" ];" +
" var isArrayBufferView = null;" +
" if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer) {" +
" isArrayBufferView =" +
" ArrayBuffer.isView ||" +
" function(obj) {" +
" return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;" +
" };" +
" }" +
" var bodyUsed = false;" +
" this._bodyInit = body;" +
" if (!body) {" +
" this._bodyText = '';" +
" } else if (typeof body === 'string') {" +
" this._bodyText = body;" +
" } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && Blob.prototype.isPrototypeOf(body)) {" +
" this._bodyBlob = body;" +
" } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.formData && FormData.prototype.isPrototypeOf(body)) {" +
" this._bodyFormData = body;" +
" } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {" +
" this._bodyText = body.toString();" +
" } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && " + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && " + JAVASCRIPT_UTIL_VAR_NAME + ".isDataView(body)) {" +
" this._bodyArrayBuffer = bufferClone(body.buffer);" +
" this._bodyInit = new Blob([this._bodyArrayBuffer]);" +
" } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {" +
" this._bodyArrayBuffer = bufferClone(body);" +
" } else {" +
" this._bodyText = body = Object.prototype.toString.call(body);" +
" }" +
" this.blob = function () {" +
" if (bodyUsed) {" +
" return Promise.reject(new TypeError('Already read'));" +
" }" +
" bodyUsed = true;" +
" if (this._bodyBlob) {" +
" return Promise.resolve(this._bodyBlob);" +
" } else if (this._bodyArrayBuffer) {" +
" return Promise.resolve(new Blob([this._bodyArrayBuffer]));" +
" } else if (this._bodyFormData) {" +
" throw new Error('could not read FormData body as blob');" +
" } else {" +
" return Promise.resolve(new Blob([this._bodyText]));" +
" }" +
" };" +
" if (this._bodyArrayBuffer) {" +
" if (bodyUsed) {" +
" return Promise.reject(new TypeError('Already read'));" +
" }" +
" bodyUsed = true;" +
" if (ArrayBuffer.isView(this._bodyArrayBuffer)) {" +
" return Promise.resolve(" +
" this._bodyArrayBuffer.buffer.slice(" +
" this._bodyArrayBuffer.byteOffset," +
" this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength" +
" )" +
" );" +
" } else {" +
" return Promise.resolve(this._bodyArrayBuffer);" +
" }" +
" }" +
" return this.blob().then(" + JAVASCRIPT_UTIL_VAR_NAME + ".readBlobAsArrayBuffer);" +
" }," +
" isString: function(variable) {" +
" return typeof variable === 'string' || variable instanceof String;" +
" }," +
" convertBodyRequest: function(body) {" +
" if (body == null) {" +
" return new Promise(function(resolve, reject) { resolve(null); });" +
" }" +
" if (" + JAVASCRIPT_UTIL_VAR_NAME + ".isString(body) || (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && body instanceof URLSearchParams)) {" +
" return new Promise(function(resolve, reject) { resolve(body.toString()); });" +
" }" +
" if (window.Response != null) {" +
" return new Response(body).arrayBuffer().then(function(arrayBuffer) {" +
" return Array.from(new Uint8Array(arrayBuffer));" +
" });" +
" }" +
" return " + JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyToArrayBuffer(body).then(function(arrayBuffer) {" +
" return Array.from(new Uint8Array(arrayBuffer));" +
" });" +
" }," +
" arrayBufferToString: function(arrayBuffer) {" +
" var uint8Array = new Uint8Array(arrayBuffer);" +
" return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');" +
" }," +
" isBodyFormData: function(bodyString) {" +
" return bodyString.indexOf('------WebKitFormBoundary') >= 0;" +
" }," +
" getFormDataContentType: function(bodyString) {" +
" var boundary = bodyString.substr(2, 40);" +
" return 'multipart/form-data; boundary=' + boundary;" +
" }," +
" convertHeadersToJson: function(headers) {" +
" var headersObj = {};" +
" for (var header of headers.keys()) {" +
" var value = headers.get(header);" +
" headersObj[header] = value;" +
" }" +
" return headersObj;" +
" }," +
" convertJsonToHeaders: function(headersJson) {" +
" return new Headers(headersJson);" +
" }," +
" convertCredentialsToJson: function(credentials) {" +
" var credentialsObj = {};" +
" if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {" +
" credentialsObj.type = credentials.type;" +
" credentialsObj.id = credentials.id;" +
" credentialsObj.name = credentials.name;" +
" credentialsObj.protocol = credentials.protocol;" +
" credentialsObj.provider = credentials.provider;" +
" credentialsObj.iconURL = credentials.iconURL;" +
" } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {" +
" credentialsObj.type = credentials.type;" +
" credentialsObj.id = credentials.id;" +
" credentialsObj.name = credentials.name;" +
" credentialsObj.password = credentials.password;" +
" credentialsObj.iconURL = credentials.iconURL;" +
" } else {" +
" credentialsObj.type = 'default';" +
" credentialsObj.value = credentials;" +
" }" +
" return credentialsObj;" +
" }," +
" convertJsonToCredential: function(credentialsJson) {" +
" var credentials;" +
" if (window.FederatedCredential != null && credentialsJson.type === 'federated') {" +
" credentials = new FederatedCredential({" +
" id: credentialsJson.id," +
" name: credentialsJson.name," +
" protocol: credentialsJson.protocol," +
" provider: credentialsJson.provider," +
" iconURL: credentialsJson.iconURL" +
" });" +
" } else if (window.PasswordCredential != null && credentialsJson.type === 'password') {" +
" credentials = new PasswordCredential({" +
" id: credentialsJson.id," +
" name: credentialsJson.name," +
" password: credentialsJson.password," +
" iconURL: credentialsJson.iconURL" +
" });" +
" } else {" +
" credentials = credentialsJson.value == null ? undefined : credentialsJson.value;" +
" }" +
" return credentials;" +
" }" +
"};";
public static final String JAVASCRIPT_BRIDGE_JS_SOURCE = "if (window." + JAVASCRIPT_BRIDGE_NAME + " != null) {" +
" window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" +
" var _callHandlerID = setTimeout(function(){});" +
" window." + JAVASCRIPT_BRIDGE_NAME + "._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));" +
" return new Promise(function(resolve, reject) {" +
" window." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = {resolve: resolve, reject: reject};" +
" });" +
" };" +
"}"+
"if (window.top != null && window.top !== window && window." + JAVASCRIPT_BRIDGE_NAME + " == null) {" +
" window." + JAVASCRIPT_BRIDGE_NAME + " = {};" +
" window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" +
" var _callHandlerID = setTimeout(function(){});" +
" try {" +
" window.top." + JAVASCRIPT_BRIDGE_NAME + "._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));" +
" return new Promise(function(resolve, reject) {" +
" window.top." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = {resolve: resolve, reject: reject};" +
" });" +
" } catch (error) {" +
" return new Promise(function(resolve, reject) { reject(error); });" +
" }" +
" };" +
"}" +
"if (window." + JAVASCRIPT_BRIDGE_NAME + " != null) {" +
" " + UTIL_JS_SOURCE +
"}";
public static final String PLATFORM_READY_JS_SOURCE = "(function() {" +
" if ((window.top == null || window.top === window) && window." + JAVASCRIPT_BRIDGE_NAME + " != null && window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady == null) {" +
" window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" +
" window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady = true;" +
" }" +
"})();";
}
@@ -0,0 +1,35 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class OnLoadResourceJS {
public static final String ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT";
public static final String FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._useOnLoadResource";
public static final PluginScript ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = new PluginScript(
OnLoadResourceJS.ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME,
OnLoadResourceJS.ON_LOAD_RESOURCE_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
false,
null
);
public static final String ON_LOAD_RESOURCE_JS_SOURCE = "window." + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " = true;" +
"(function() {" +
" var observer = new PerformanceObserver(function(list) {" +
" list.getEntries().forEach(function(entry) {" +
" if (" + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " == null || " + FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE + " == true) {" +
" var resource = {" +
" 'url': entry.name," +
" 'initiatorType': entry.initiatorType," +
" 'startTime': entry.startTime," +
" 'duration': entry.duration" +
" };" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onLoadResource', resource);" +
" }" +
" });" +
" });" +
" observer.observe({entryTypes: ['resource']});" +
"})();";
}
@@ -0,0 +1,22 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class OnWindowBlurEventJS {
public static final String ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT";
public static final PluginScript ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = new PluginScript(
OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
OnWindowBlurEventJS.ON_WINDOW_BLUR_EVENT_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
false,
null
);
public static final String ON_WINDOW_BLUR_EVENT_JS_SOURCE = "(function(){" +
" window.addEventListener('blur', function(e) {" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWindowBlur');" +
" });" +
"})();";
}
@@ -0,0 +1,22 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class OnWindowFocusEventJS {
public static final String ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT";
public static final PluginScript ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = new PluginScript(
OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
OnWindowFocusEventJS.ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
false,
null
);
public static final String ON_WINDOW_FOCUS_EVENT_JS_SOURCE = "(function(){" +
" window.addEventListener('focus', function(e) {" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onWindowFocus');" +
" });" +
"})();";
}
@@ -0,0 +1,89 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class PluginScriptsUtil {
public static final String VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE";
public static final String VAR_CONTENT_WORLD_NAME_ARRAY = "$IN_APP_WEBVIEW_CONTENT_WORLD_NAME_ARRAY";
public static final String VAR_CONTENT_WORLD_NAME = "$IN_APP_WEBVIEW_CONTENT_WORLD_NAME";
public static final String VAR_JSON_SOURCE_ENCODED = "$IN_APP_WEBVIEW_JSON_SOURCE_ENCODED";
public static final String VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES";
public static final String VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES";
public static final String VAR_FUNCTION_ARGUMENTS_OBJ = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENTS_OBJ";
public static final String VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY";
public static final String VAR_RESULT_UUID = "$IN_APP_WEBVIEW_RESULT_UUID";
public static final String VAR_RANDOM_NAME = "$IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME";
public static final String CALL_ASYNC_JAVA_SCRIPT_WRAPPER_JS_SOURCE = "(function(obj) {" +
" (async function(" + VAR_FUNCTION_ARGUMENT_NAMES + ") {" +
" \n" + VAR_FUNCTION_BODY + "\n" +
" })(" + VAR_FUNCTION_ARGUMENT_VALUES + ").then(function(value) {" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('callAsyncJavaScript', {'value': value, 'error': null, 'resultUuid': '" + VAR_RESULT_UUID + "'});" +
" }).catch(function(error) {" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('callAsyncJavaScript', {'value': null, 'error': error + '', 'resultUuid': '" + VAR_RESULT_UUID + "'});" +
" });" +
" return null;" +
"})(" + VAR_FUNCTION_ARGUMENTS_OBJ + ");";
public static final String EVALUATE_JAVASCRIPT_WITH_CONTENT_WORLD_WRAPPER_JS_SOURCE = "var $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = null;" +
"try {" +
" $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME = eval(" + VAR_PLACEHOLDER_VALUE + ");" +
"} catch(e) {" +
" console.error(e);" +
"}" +
"window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('evaluateJavaScriptWithContentWorld', {'value': $IN_APP_WEBVIEW_VARIABLE_RANDOM_NAME, 'resultUuid': '" + VAR_RESULT_UUID + "'});";
public static final String IS_ACTIVE_ELEMENT_INPUT_EDITABLE_JS_SOURCE =
"var activeEl = document.activeElement;" +
"var nodeName = (activeEl != null) ? activeEl.nodeName.toLowerCase() : '';" +
"var isActiveElementInputEditable = activeEl != null && " +
"(activeEl.nodeType == 1 && (nodeName == 'textarea' || (nodeName == 'input' && /^(?:text|email|number|search|tel|url|password)$/i.test(activeEl.type != null ? activeEl.type : 'text')))) && " +
"!activeEl.disabled && !activeEl.readOnly;" +
"var isActiveElementEditable = isActiveElementInputEditable || (activeEl != null && activeEl.isContentEditable) || document.designMode === 'on';";
// android Workaround to hide context menu when selected text is empty
// and the document active element is not an input element.
public static final String CHECK_CONTEXT_MENU_SHOULD_BE_HIDDEN_JS_SOURCE = "(function(){" +
" var txt;" +
" if (window.getSelection) {" +
" txt = window.getSelection().toString();" +
" } else if (window.document.getSelection) {" +
" txt = window.document.getSelection().toString();" +
" } else if (window.document.selection) {" +
" txt = window.document.selection.createRange().text;" +
" }" +
IS_ACTIVE_ELEMENT_INPUT_EDITABLE_JS_SOURCE +
" return txt === '' && !isActiveElementEditable;" +
"})();";
public static final String GET_SELECTED_TEXT_JS_SOURCE = "(function(){" +
" var txt;" +
" if (window.getSelection) {" +
" txt = window.getSelection().toString();" +
" } else if (window.document.getSelection) {" +
" txt = window.document.getSelection().toString();" +
" } else if (window.document.selection) {" +
" txt = window.document.selection.createRange().text;" +
" }" +
" return txt;" +
"})();";
public static final String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME = "CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT";
public static final PluginScript CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT = new PluginScript(
PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_PLUGIN_SCRIPT_GROUP_NAME,
PluginScriptsUtil.CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
false,
null
);
// android Workaround to hide context menu when user emit a keydown event
public static final String CHECK_GLOBAL_KEY_DOWN_EVENT_TO_HIDE_CONTEXT_MENU_JS_SOURCE = "(function(){" +
" document.addEventListener('keydown', function(e) {" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "._hideContextMenu();" +
" });" +
"})();";
}
@@ -0,0 +1,24 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class PrintJS {
public static final String PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT";
public static final PluginScript PRINT_JS_PLUGIN_SCRIPT = new PluginScript(
PrintJS.PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
PrintJS.PRINT_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
false,
null
);
public static final String PRINT_JS_SOURCE = "window.print = function() {" +
" if (window.top == null || window.top === window) {" +
" window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('onPrintRequest', window.location.href);" +
" } else {" +
" window.top.print();" +
" }" +
"};";
}
@@ -0,0 +1,21 @@
package com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js;
import com.pichillilorenzo.flutter_inappwebview_android.types.PluginScript;
import com.pichillilorenzo.flutter_inappwebview_android.types.UserScriptInjectionTime;
public class PromisePolyfillJS {
public static final String PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT";
public static final PluginScript PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = new PluginScript(
PromisePolyfillJS.PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME,
PromisePolyfillJS.PROMISE_POLYFILL_JS_SOURCE,
UserScriptInjectionTime.AT_DOCUMENT_START,
null,
true,
null
);
// https://github.com/tildeio/rsvp.js
public static final String PROMISE_POLYFILL_JS_SOURCE = "if (window.Promise == null) {" +
" !function(t,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?e(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],e):e(t.RSVP={})}(this,function(t){\"use strict\";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if(\"function\"!=typeof r)throw new TypeError(\"Callback must be a function\");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i<o.length;i++)(0,o[i])(r,n)}},n={instrument:!1};function o(t,e){if(2!==arguments.length)return n[t];n[t]=e}r.mixin(n);var i=[];function s(t,e,r){1===i.push({name:t,payload:{key:e._guidKey,id:e._id,eventName:t,detail:e._result,childId:r&&r._id,label:e._label,timeStamp:Date.now(),error:n[\"instrument-with-stack\"]?new Error(e._label):null}})&&setTimeout(function(){for(var t=0;t<i.length;t++){var e=i[t],r=e.payload;r.guid=r.key+r.id,r.childGuid=r.key+r.childId,r.error&&(r.stack=r.error.stack),n.trigger(e.name,e.payload)}i.length=0},50)}function u(t,e){if(t&&\"object\"==typeof t&&t.constructor===this)return t;var r=new this(c,e);return m(r,t),r}function c(){}var a=void 0,f=1,l=2,h={error:null};function p(t){try{return t.then}catch(t){return h.error=t,h}}var y=void 0;function _(){try{var t=y;return y=null,t.apply(this,arguments)}catch(t){return h.error=t,h}}function v(t){return y=t,_}function d(t,e,r){if(e.constructor===t.constructor&&r===A&&t.constructor.resolve===u)!function(t,e){e._state===f?b(t,e._result):e._state===l?(e._onError=null,g(t,e._result)):j(e,void 0,function(r){e===r?b(t,r):m(t,r)},function(e){return g(t,e)})}(t,e);else if(r===h){var o=h.error;h.error=null,g(t,o)}else\"function\"==typeof r?function(t,e,r){n.async(function(t){var n=!1,o=v(r).call(e,function(r){n||(n=!0,e===r?b(t,r):m(t,r))},function(e){n||(n=!0,g(t,e))},\"Settle: \"+(t._label||\" unknown promise\"));if(!n&&o===h){n=!0;var i=h.error;h.error=null,g(t,i)}},t)}(t,e,r):b(t,e)}function m(t,e){var r,n;t===e?b(t,e):(n=typeof(r=e),null===r||\"object\"!==n&&\"function\"!==n?b(t,e):d(t,e,p(e)))}function w(t){t._onError&&t._onError(t._result),O(t)}function b(t,e){t._state===a&&(t._result=e,t._state=f,0===t._subscribers.length?n.instrument&&s(\"fulfilled\",t):n.async(O,t))}function g(t,e){t._state===a&&(t._state=l,t._result=e,n.async(w,t))}function j(t,e,r,o){var i=t._subscribers,s=i.length;t._onError=null,i[s]=e,i[s+f]=r,i[s+l]=o,0===s&&t._state&&n.async(O,t)}function O(t){var e=t._subscribers,r=t._state;if(n.instrument&&s(r===f?\"fulfilled\":\"rejected\",t),0!==e.length){for(var o=void 0,i=void 0,u=t._result,c=0;c<e.length;c+=3)o=e[c],i=e[c+r],o?E(r,o,i,u):i(u);t._subscribers.length=0}}function E(t,e,r,n){var o=\"function\"==typeof r,i=void 0;if(i=o?v(r)(n):n,e._state!==a);else if(i===e)g(e,new TypeError(\"A promises callback cannot return that same promise.\"));else if(i===h){var s=h.error;h.error=null,g(e,s)}else o?m(e,i):t===f?b(e,i):t===l&&g(e,i)}function A(t,e,r){var o=this._state;if(o===f&&!t||o===l&&!e)return n.instrument&&s(\"chained\",this,this),this;this._onError=null;var i=new this.constructor(c,r),u=this._result;if(n.instrument&&s(\"chained\",this,i),o===a)j(this,i,t,e);else{var h=o===f?t:e;n.async(function(){return E(o,i,h,u)})}return i}var T=function(){function t(t,e,r,n){this._instanceConstructor=t,this.promise=new t(c,n),this._abortOnReject=r,this._isUsingOwnPromise=t===k,this._isUsingOwnResolve=t.resolve===u,this._init.apply(this,arguments)}return t.prototype._init=function(t,e){var r=e.length||0;this.length=r,this._remaining=r,this._result=new Array(r),this._enumerate(e)},t.prototype._enumerate=function(t){for(var e=this.length,r=this.promise,n=0;r._state===a&&n<e;n++)this._eachEntry(t[n],n,!0);this._checkFullfillment()},t.prototype._checkFullfillment=function(){if(0===this._remaining){var t=this._result;b(this.promise,t),this._result=null}},t.prototype._settleMaybeThenable=function(t,e,r){var n=this._instanceConstructor;if(this._isUsingOwnResolve){var o=p(t);if(o===A&&t._state!==a)t._onError=null,this._settledAt(t._state,e,t._result,r);else if(\"function\"!=typeof o)this._settledAt(f,e,t,r);else if(this._isUsingOwnPromise){var i=new n(c);d(i,t,o),this._willSettleAt(i,e,r)}else this._willSettleAt(new n(function(e){return e(t)}),e,r)}else this._willSettleAt(n.resolve(t),e,r)},t.prototype._eachEntry=function(t,e,r){null!==t&&\"object\"==typeof t?this._settleMaybeThenable(t,e,r):this._setResultAt(f,e,t,r)},t.prototype._settledAt=function(t,e,r,n){var o=this.promise;o._state===a&&(this._abortOnReject&&t===l?g(o,r):(this._setResultAt(t,e,r,n),this._checkFullfillment()))},t.prototype._setResultAt=function(t,e,r,n){this._remaining--,this._result[e]=r},t.prototype._willSettleAt=function(t,e,r){var n=this;j(t,void 0,function(tLine truncated
"}";
}
@@ -0,0 +1,70 @@
package com.pichillilorenzo.flutter_inappwebview_android.print_job;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.PrintJobInfoExt;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class PrintJobChannelDelegate extends ChannelDelegateImpl {
@Nullable
private PrintJobController printJobController;
public PrintJobChannelDelegate(@NonNull PrintJobController printJobController, @NonNull MethodChannel channel) {
super(channel);
this.printJobController = printJobController;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "cancel":
if (printJobController != null) {
printJobController.cancel();
result.success(true);
} else {
result.success(false);
}
break;
case "restart":
if (printJobController != null) {
printJobController.restart();
result.success(true);
} else {
result.success(false);
}
break;
case "getInfo":
if (printJobController != null) {
PrintJobInfoExt info = printJobController.getInfo();
result.success(info != null ? info.toMap() : null);
} else {
result.success(null);
}
break;
case "dispose":
if (printJobController != null) {
printJobController.dispose();
result.success(true);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
printJobController = null;
}
}
@@ -0,0 +1,96 @@
package com.pichillilorenzo.flutter_inappwebview_android.print_job;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview_android.types.PrintJobInfoExt;
import io.flutter.plugin.common.MethodChannel;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class PrintJobController implements Disposable {
protected static final String LOG_TAG = "PrintJob";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_";
@NonNull
public String id;
@Nullable
public InAppWebViewFlutterPlugin plugin;
@Nullable
public PrintJobChannelDelegate channelDelegate;
@Nullable
public android.print.PrintJob job;
@Nullable
public PrintJobSettings settings;
public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job,
@Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) {
this.id = id;
this.plugin = plugin;
this.job = job;
this.settings = settings;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
this.channelDelegate = new PrintJobChannelDelegate(this, channel);
}
public void cancel() {
if (this.job != null) {
this.job.cancel();
}
}
public void restart() {
if (this.job != null) {
this.job.restart();
}
}
@Nullable
public PrintJobInfoExt getInfo() {
if (this.job != null) {
return PrintJobInfoExt.fromPrintJobInfo(this.job.getInfo());
}
return null;
}
public void disposeNoCancel() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
if (plugin != null) {
PrintJobManager printJobManager = plugin.printJobManager;
if (printJobManager != null && printJobManager.jobs.containsKey(id)) {
printJobManager.jobs.put(id, null);
}
}
if (job != null) {
job = null;
}
plugin = null;
}
@Override
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
if (plugin != null) {
PrintJobManager printJobManager = plugin.printJobManager;
if (printJobManager != null && printJobManager.jobs.containsKey(id)) {
printJobManager.jobs.put(id, null);
}
}
if (job != null) {
job.cancel();
job = null;
}
plugin = null;
}
}
@@ -0,0 +1,59 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package com.pichillilorenzo.flutter_inappwebview_android.print_job;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class PrintJobManager implements Disposable {
protected static final String LOG_TAG = "PrintJobManager";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public final Map<String, PrintJobController> jobs = new HashMap<>();
public PrintJobManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super();
this.plugin = plugin;
}
public void dispose() {
Collection<PrintJobController> printJobControllers = jobs.values();
for (PrintJobController job : printJobControllers) {
if (job != null) {
job.dispose();
}
}
jobs.clear();
plugin = null;
}
}
@@ -0,0 +1,99 @@
package com.pichillilorenzo.flutter_inappwebview_android.print_job;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import com.pichillilorenzo.flutter_inappwebview_android.types.MediaSizeExt;
import com.pichillilorenzo.flutter_inappwebview_android.types.ResolutionExt;
import java.util.HashMap;
import java.util.Map;
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class PrintJobSettings implements ISettings<PrintJobController> {
public static final String LOG_TAG = "PrintJobSettings";
public Boolean handledByClient = false;
@Nullable
public String jobName;
@Nullable
public Integer orientation;
// @Nullable
// public MarginsExt margins;
@Nullable
public MediaSizeExt mediaSize;
@Nullable
public Integer colorMode;
@Nullable
public Integer duplexMode;
@Nullable
public ResolutionExt resolution;
@NonNull
@Override
public PrintJobSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "handledByClient":
handledByClient = (Boolean) value;
break;
case "jobName":
jobName = (String) value;
break;
case "orientation":
orientation = (Integer) value;
break;
// case "margins":
// margins = MarginsExt.fromMap((Map<String, Object>) value);
// break;
case "mediaSize":
mediaSize = MediaSizeExt.fromMap((Map<String, Object>) value);
break;
case "colorMode":
colorMode = (Integer) value;
break;
case "duplexMode":
duplexMode = (Integer) value;
break;
case "resolution":
resolution = ResolutionExt.fromMap((Map<String, Object>) value);
break;
}
}
return this;
}
@NonNull
@Override
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("handledByClient", handledByClient);
settings.put("jobName", jobName);
settings.put("orientation", orientation);
// settings.put("margins", margins != null ? margins.toMap() : null);
settings.put("mediaSize", mediaSize != null ? mediaSize.toMap() : null);
settings.put("colorMode", colorMode);
settings.put("duplexMode", duplexMode);
settings.put("resolution", resolution != null ? resolution.toMap() : null);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull PrintJobController printJobController) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}
@@ -0,0 +1,56 @@
package com.pichillilorenzo.flutter_inappwebview_android.process_global_config;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.ProcessGlobalConfig;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ProcessGlobalConfigManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "ProcessGlobalConfigM";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_processglobalconfig";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public ProcessGlobalConfigManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "apply":
if (plugin != null && plugin.activity != null) {
ProcessGlobalConfigSettings settings = (new ProcessGlobalConfigSettings())
.parse((Map<String, Object>) call.argument("settings"));
try {
ProcessGlobalConfig.apply(settings.toProcessGlobalConfig(plugin.activity));
result.success(true);
} catch (Exception e) {
result.error(LOG_TAG, "", e);
}
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}
@@ -0,0 +1,119 @@
package com.pichillilorenzo.flutter_inappwebview_android.process_global_config;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.ProcessGlobalConfig;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class ProcessGlobalConfigSettings implements ISettings<ProcessGlobalConfig> {
public static final String LOG_TAG = "ProcessGlobalConfigSettings";
@Nullable
public String dataDirectorySuffix;
@Nullable
public DirectoryBasePaths directoryBasePaths;
@NonNull
@Override
public ProcessGlobalConfigSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "dataDirectorySuffix":
dataDirectorySuffix = (String) value;
break;
case "directoryBasePaths":
directoryBasePaths = (new DirectoryBasePaths()).parse((Map<String, Object>) value);
break;
}
}
return this;
}
public ProcessGlobalConfig toProcessGlobalConfig(@NonNull Context context) {
ProcessGlobalConfig config = new ProcessGlobalConfig();
if (dataDirectorySuffix != null &&
WebViewFeature.isStartupFeatureSupported(context, WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX)) {
config.setDataDirectorySuffix(context, dataDirectorySuffix);
}
if (directoryBasePaths != null &&
WebViewFeature.isStartupFeatureSupported(context, WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATHS)) {
config.setDirectoryBasePaths(context,
new File(directoryBasePaths.dataDirectoryBasePath),
new File(directoryBasePaths.cacheDirectoryBasePath));
}
return config;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("dataDirectorySuffix", dataDirectorySuffix);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull ProcessGlobalConfig processGlobalConfig) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
static class DirectoryBasePaths implements ISettings<Object> {
public static final String LOG_TAG = "ProcessGlobalConfigSettings";
public String cacheDirectoryBasePath;
public String dataDirectoryBasePath;
@NonNull
@Override
public DirectoryBasePaths parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "cacheDirectoryBasePath":
cacheDirectoryBasePath = (String) value;
break;
case "dataDirectoryBasePath":
dataDirectoryBasePath = (String) value;
break;
}
}
return this;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("cacheDirectoryBasePath", cacheDirectoryBasePath);
settings.put("dataDirectoryBasePath", dataDirectoryBasePath);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull Object obj) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}
}
@@ -0,0 +1,129 @@
package com.pichillilorenzo.flutter_inappwebview_android.proxy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.ProxyRuleExt;
import java.util.HashMap;
import java.util.concurrent.Executor;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ProxyManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "ProxyManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_proxycontroller";
@Nullable
public static ProxyController proxyController;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public ProxyManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin;
}
public static void init() {
if (proxyController == null &&
WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
proxyController = ProxyController.getInstance();
}
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
init();
switch (call.method) {
case "setProxyOverride":
if (proxyController != null) {
HashMap<String, Object> settingsMap = (HashMap<String, Object>) call.argument("settings");
ProxySettings settings = new ProxySettings();
if (settingsMap != null) {
settings.parse(settingsMap);
}
setProxyOverride(settings, result);
} else {
result.success(false);
}
break;
case "clearProxyOverride":
if (proxyController != null ) {
clearProxyOverride(result);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
private void setProxyOverride(ProxySettings settings, final MethodChannel.Result result) {
if (proxyController != null) {
ProxyConfig.Builder proxyConfigBuilder = new ProxyConfig.Builder();
for (String bypassRule : settings.bypassRules) {
proxyConfigBuilder.addBypassRule(bypassRule);
}
for (String direct : settings.directs) {
proxyConfigBuilder.addDirect(direct);
}
for (ProxyRuleExt proxyRule : settings.proxyRules) {
if (proxyRule.getSchemeFilter() != null) {
proxyConfigBuilder.addProxyRule(proxyRule.getUrl(), proxyRule.getSchemeFilter());
} else {
proxyConfigBuilder.addProxyRule(proxyRule.getUrl());
}
}
if (settings.bypassSimpleHostnames != null && settings.bypassSimpleHostnames) {
proxyConfigBuilder.bypassSimpleHostnames();
}
if (settings.removeImplicitRules != null && settings.removeImplicitRules) {
proxyConfigBuilder.removeImplicitRules();
}
if (settings.reverseBypassEnabled != null && WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS)) {
proxyConfigBuilder.setReverseBypassEnabled(settings.reverseBypassEnabled);
}
proxyController.setProxyOverride(proxyConfigBuilder.build(), new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
}, new Runnable() {
@Override
public void run() {
result.success(true);
}
});
}
}
private void clearProxyOverride(final MethodChannel.Result result) {
if (proxyController != null) {
proxyController.clearProxyOverride(new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
}, new Runnable() {
@Override
public void run() {
result.success(true);
}
});
}
}
@Override
public void dispose() {
super.dispose();
plugin = null;
}
}
@@ -0,0 +1,98 @@
package com.pichillilorenzo.flutter_inappwebview_android.proxy;
import androidx.annotation.NonNull;
import androidx.webkit.ProxyConfig;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import com.pichillilorenzo.flutter_inappwebview_android.types.ProxyRuleExt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ProxySettings implements ISettings<ProxyConfig> {
List<String> bypassRules = new ArrayList<>();
List<String> directs = new ArrayList<>();
List<ProxyRuleExt> proxyRules = new ArrayList<>();
Boolean bypassSimpleHostnames = null;
Boolean removeImplicitRules = null;
Boolean reverseBypassEnabled = false;
@NonNull
@Override
public ProxySettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "bypassRules":
bypassRules = (List<String>) value;
break;
case "directs":
directs = (List<String>) value;
break;
case "proxyRules":
proxyRules = new ArrayList<>();
List<Map<String, String>> proxyRuleMapList = (List<Map<String, String>>) value;
for (Map<String, String> proxyRuleMap : proxyRuleMapList) {
ProxyRuleExt proxyRuleExt = ProxyRuleExt.fromMap(proxyRuleMap);
if (proxyRuleExt != null) {
proxyRules.add(proxyRuleExt);
}
}
break;
case "bypassSimpleHostnames":
bypassSimpleHostnames = (Boolean) value;
break;
case "removeImplicitRules":
removeImplicitRules = (Boolean) value;
break;
case "reverseBypassEnabled":
reverseBypassEnabled = (Boolean) value;
break;
}
}
return this;
}
@NonNull
@Override
public Map<String, Object> toMap() {
List<Map<String, String>> proxyRuleMapList = new ArrayList<>();
for (ProxyRuleExt proxyRuleExt : proxyRules) {
proxyRuleMapList.add(proxyRuleExt.toMap());
}
Map<String, Object> settings = new HashMap<>();
settings.put("bypassRules", bypassRules);
settings.put("directs", directs);
settings.put("proxyRules", proxyRuleMapList);
settings.put("bypassSimpleHostnames", bypassSimpleHostnames);
settings.put("removeImplicitRules", removeImplicitRules);
settings.put("reverseBypassEnabled", reverseBypassEnabled);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull ProxyConfig proxyConfig) {
Map<String, Object> realSettings = toMap();
List<Map<String, String>> proxyRuleMapList = new ArrayList<>();
List<ProxyConfig.ProxyRule> proxyRules = proxyConfig.getProxyRules();
for (ProxyConfig.ProxyRule proxyRule : proxyRules) {
Map<String, String> proxyRuleMap = new HashMap<>();
proxyRuleMap.put("url", proxyRule.getUrl());
proxyRuleMap.put("schemeFilter", proxyRule.getSchemeFilter());
proxyRuleMapList.add(proxyRuleMap);
}
realSettings.put("bypassRules", proxyConfig.getBypassRules());
realSettings.put("proxyRules", proxyRuleMapList);
realSettings.put("reverseBypassEnabled", proxyConfig.isReverseBypassEnabled());
return realSettings;
}
}
@@ -0,0 +1,123 @@
package com.pichillilorenzo.flutter_inappwebview_android.pull_to_refresh;
import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class PullToRefreshChannelDelegate extends ChannelDelegateImpl {
@Nullable
private PullToRefreshLayout pullToRefreshView;
public PullToRefreshChannelDelegate(@NonNull PullToRefreshLayout pullToRefreshView, @NonNull MethodChannel channel) {
super(channel);
this.pullToRefreshView = pullToRefreshView;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "setEnabled":
if (pullToRefreshView != null) {
Boolean enabled = (Boolean) call.argument("enabled");
pullToRefreshView.settings.enabled = enabled; // used by InAppWebView.onOverScrolled
pullToRefreshView.setEnabled(enabled);
result.success(true);
} else {
result.success(false);
}
break;
case "isEnabled":
if (pullToRefreshView != null) {
result.success(pullToRefreshView.isEnabled());
} else {
result.success(false);
}
break;
case "setRefreshing":
if (pullToRefreshView != null) {
Boolean refreshing = (Boolean) call.argument("refreshing");
pullToRefreshView.setRefreshing(refreshing);
result.success(true);
} else {
result.success(false);
}
break;
case "isRefreshing":
result.success(pullToRefreshView != null && pullToRefreshView.isRefreshing());
break;
case "setColor":
if (pullToRefreshView != null) {
String color = (String) call.argument("color");
pullToRefreshView.setColorSchemeColors(Color.parseColor(color));
result.success(true);
} else {
result.success(false);
}
break;
case "setBackgroundColor":
if (pullToRefreshView != null) {
String color = (String) call.argument("color");
pullToRefreshView.setProgressBackgroundColorSchemeColor(Color.parseColor(color));
result.success(true);
} else {
result.success(false);
}
break;
case "setDistanceToTriggerSync":
if (pullToRefreshView != null) {
Integer distanceToTriggerSync = (Integer) call.argument("distanceToTriggerSync");
pullToRefreshView.setDistanceToTriggerSync(distanceToTriggerSync);
result.success(true);
} else {
result.success(false);
}
break;
case "setSlingshotDistance":
if (pullToRefreshView != null) {
Integer slingshotDistance = (Integer) call.argument("slingshotDistance");
pullToRefreshView.setSlingshotDistance(slingshotDistance);
result.success(true);
} else {
result.success(false);
}
break;
case "getDefaultSlingshotDistance":
result.success(SwipeRefreshLayout.DEFAULT_SLINGSHOT_DISTANCE);
break;
case "setSize":
if (pullToRefreshView != null) {
Integer size = (Integer) call.argument("size");
pullToRefreshView.setSize(size);
result.success(true);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
public void onRefresh() {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onRefresh", obj);
}
@Override
public void dispose() {
super.dispose();
pullToRefreshView = null;
}
}
@@ -0,0 +1,86 @@
package com.pichillilorenzo.flutter_inappwebview_android.pull_to_refresh;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView;
import io.flutter.plugin.common.MethodChannel;
public class PullToRefreshLayout extends SwipeRefreshLayout implements Disposable {
static final String LOG_TAG = "PullToRefreshLayout";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_";
@Nullable
public PullToRefreshChannelDelegate channelDelegate;
public PullToRefreshSettings settings = new PullToRefreshSettings();
public PullToRefreshLayout(@NonNull Context context, @NonNull InAppWebViewFlutterPlugin plugin,
@NonNull Object id, @NonNull PullToRefreshSettings settings) {
super(context);
this.settings = settings;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
this.channelDelegate = new PullToRefreshChannelDelegate(this, channel);
}
public PullToRefreshLayout(@NonNull Context context) {
super(context);
}
public PullToRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void prepare() {
final PullToRefreshLayout self = this;
setEnabled(settings.enabled);
setOnChildScrollUpCallback(new OnChildScrollUpCallback() {
@Override
public boolean canChildScrollUp(@NonNull SwipeRefreshLayout parent, @Nullable View child) {
if (child instanceof InAppWebView) {
InAppWebView inAppWebView = (InAppWebView) child;
return (inAppWebView.canScrollVertically() && inAppWebView.getScrollY() > 0) ||
(!inAppWebView.canScrollVertically() && inAppWebView.getScrollY() == 0);
}
return true;
}
});
setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (channelDelegate == null) {
self.setRefreshing(false);
return;
}
channelDelegate.onRefresh();
}
});
if (settings.color != null)
setColorSchemeColors(Color.parseColor(settings.color));
if (settings.backgroundColor != null)
setProgressBackgroundColorSchemeColor(Color.parseColor(settings.backgroundColor));
if (settings.distanceToTriggerSync != null)
setDistanceToTriggerSync(settings.distanceToTriggerSync);
if (settings.slingshotDistance != null)
setSlingshotDistance(settings.slingshotDistance);
if (settings.size != null)
setSize(settings.size);
}
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
removeAllViews();
}
}
@@ -0,0 +1,80 @@
package com.pichillilorenzo.flutter_inappwebview_android.pull_to_refresh;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import java.util.HashMap;
import java.util.Map;
public class PullToRefreshSettings implements ISettings<PullToRefreshLayout> {
public static final String LOG_TAG = "PullToRefreshSettings";
public Boolean enabled = true;
@Nullable
public String color;
@Nullable
public String backgroundColor;
@Nullable
public Integer distanceToTriggerSync;
@Nullable
public Integer slingshotDistance;
@Nullable
public Integer size;
@NonNull
@Override
public PullToRefreshSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "enabled":
enabled = (Boolean) value;
break;
case "color":
color = (String) value;
break;
case "backgroundColor":
backgroundColor = (String) value;
break;
case "distanceToTriggerSync":
distanceToTriggerSync = (Integer) value;
break;
case "slingshotDistance":
slingshotDistance = (Integer) value;
break;
case "size":
size = (Integer) value;
break;
}
}
return this;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("enabled", enabled);
settings.put("color", color);
settings.put("backgroundColor", backgroundColor);
settings.put("distanceToTriggerSync", distanceToTriggerSync);
settings.put("slingshotDistance", slingshotDistance);
settings.put("size", size);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull PullToRefreshLayout pullToRefreshLayout) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}
@@ -0,0 +1,147 @@
package com.pichillilorenzo.flutter_inappwebview_android.service_worker;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.webkit.ServiceWorkerControllerCompat;
import androidx.webkit.ServiceWorkerWebSettingsCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.Util;
import com.pichillilorenzo.flutter_inappwebview_android.types.BaseCallbackResultImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.SyncBaseCallbackResultImpl;
import com.pichillilorenzo.flutter_inappwebview_android.types.WebResourceRequestExt;
import com.pichillilorenzo.flutter_inappwebview_android.types.WebResourceResponseExt;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@RequiresApi(api = Build.VERSION_CODES.N)
public class ServiceWorkerChannelDelegate extends ChannelDelegateImpl {
@Nullable
private ServiceWorkerManager serviceWorkerManager;
public ServiceWorkerChannelDelegate(@NonNull ServiceWorkerManager serviceWorkerManager, @NonNull MethodChannel channel) {
super(channel);
this.serviceWorkerManager = serviceWorkerManager;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
ServiceWorkerManager.init();
ServiceWorkerControllerCompat serviceWorkerController = ServiceWorkerManager.serviceWorkerController;
ServiceWorkerWebSettingsCompat serviceWorkerWebSettings = (serviceWorkerController != null) ?
serviceWorkerController.getServiceWorkerWebSettings() : null;
switch (call.method) {
case "setServiceWorkerClient":
if (serviceWorkerManager != null) {
Boolean isNull = (Boolean) call.argument("isNull");
serviceWorkerManager.setServiceWorkerClient(isNull);
result.success(true);
} else {
result.success(false);
}
break;
case "getAllowContentAccess":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS)) {
result.success(serviceWorkerWebSettings.getAllowContentAccess());
} else {
result.success(false);
}
break;
case "getAllowFileAccess":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_FILE_ACCESS)) {
result.success(serviceWorkerWebSettings.getAllowFileAccess());
} else {
result.success(false);
}
break;
case "getBlockNetworkLoads":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS)) {
result.success(serviceWorkerWebSettings.getBlockNetworkLoads());
} else {
result.success(false);
}
break;
case "getCacheMode":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_CACHE_MODE)) {
result.success(serviceWorkerWebSettings.getCacheMode());
} else {
result.success(null);
}
break;
case "setAllowContentAccess":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS)) {
Boolean allow = (Boolean) call.argument("allow");
serviceWorkerWebSettings.setAllowContentAccess(allow);
}
result.success(true);
break;
case "setAllowFileAccess":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_FILE_ACCESS)) {
Boolean allow = (Boolean) call.argument("allow");
serviceWorkerWebSettings.setAllowFileAccess(allow);
}
result.success(true);
break;
case "setBlockNetworkLoads":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS)) {
Boolean flag = (Boolean) call.argument("flag");
serviceWorkerWebSettings.setBlockNetworkLoads(flag);
}
result.success(true);
break;
case "setCacheMode":
if (serviceWorkerWebSettings != null && WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_CACHE_MODE)) {
Integer mode = (Integer) call.argument("mode");
serviceWorkerWebSettings.setCacheMode(mode);
}
result.success(true);
break;
default:
result.notImplemented();
}
}
public static class ShouldInterceptRequestCallback extends BaseCallbackResultImpl<WebResourceResponseExt> {
@Nullable
@Override
public WebResourceResponseExt decodeResult(@Nullable Object obj) {
return WebResourceResponseExt.fromMap((Map<String, Object>) obj);
}
}
public void shouldInterceptRequest(WebResourceRequestExt request, @NonNull ShouldInterceptRequestCallback callback) {
MethodChannel channel = getChannel();
if (channel == null) return;
channel.invokeMethod("shouldInterceptRequest", request.toMap(), callback);
}
public static class SyncShouldInterceptRequestCallback extends SyncBaseCallbackResultImpl<WebResourceResponseExt> {
@Nullable
@Override
public WebResourceResponseExt decodeResult(@Nullable Object obj) {
return (new ShouldInterceptRequestCallback()).decodeResult(obj);
}
}
@Nullable
public WebResourceResponseExt shouldInterceptRequest(WebResourceRequestExt request) throws InterruptedException {
MethodChannel channel = getChannel();
if (channel == null) return null;
final SyncShouldInterceptRequestCallback callback = new SyncShouldInterceptRequestCallback();
return Util.invokeMethodAndWaitResult(channel, "shouldInterceptRequest", request.toMap(), callback);
}
@Override
public void dispose() {
super.dispose();
serviceWorkerManager = null;
}
}
@@ -0,0 +1,115 @@
package com.pichillilorenzo.flutter_inappwebview_android.service_worker;
import android.os.Build;
import android.util.Log;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.webkit.ServiceWorkerClientCompat;
import androidx.webkit.ServiceWorkerControllerCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview_android.types.WebResourceRequestExt;
import com.pichillilorenzo.flutter_inappwebview_android.types.WebResourceResponseExt;
import java.io.ByteArrayInputStream;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
@RequiresApi(api = Build.VERSION_CODES.N)
public class ServiceWorkerManager implements Disposable {
protected static final String LOG_TAG = "ServiceWorkerManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_serviceworkercontroller";
@Nullable
public ServiceWorkerChannelDelegate channelDelegate;
@Nullable
public static ServiceWorkerControllerCompat serviceWorkerController;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public ServiceWorkerManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
this.plugin = plugin;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME);
this.channelDelegate = new ServiceWorkerChannelDelegate(this, channel);
}
public static void init() {
if (serviceWorkerController == null &&
WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_BASIC_USAGE)) {
serviceWorkerController = ServiceWorkerControllerCompat.getInstance();
}
}
public void setServiceWorkerClient(Boolean isNull) {
if (serviceWorkerController != null) {
// set ServiceWorkerClient as null makes the app crashes, so just set a dummy ServiceWorkerClientCompat.
// https://github.com/pichillilorenzo/flutter_inappwebview/issues/1151
serviceWorkerController.setServiceWorkerClient(isNull ? dummyServiceWorkerClientCompat() : new ServiceWorkerClientCompat() {
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(@NonNull WebResourceRequest request) {
WebResourceRequestExt requestExt = WebResourceRequestExt.fromWebResourceRequest(request);
WebResourceResponseExt response = null;
if (channelDelegate != null) {
try {
response = channelDelegate.shouldInterceptRequest(requestExt);
} catch (InterruptedException e) {
Log.e(LOG_TAG, "", e);
return null;
}
}
if (response != null) {
String contentType = response.getContentType();
String contentEncoding = response.getContentEncoding();
byte[] data = response.getData();
Map<String, String> responseHeaders = response.getHeaders();
Integer statusCode = response.getStatusCode();
String reasonPhrase = response.getReasonPhrase();
ByteArrayInputStream inputStream = (data != null) ? new ByteArrayInputStream(data) : null;
if (statusCode != null && reasonPhrase != null) {
return new WebResourceResponse(contentType, contentEncoding, statusCode, reasonPhrase, responseHeaders, inputStream);
} else {
return new WebResourceResponse(contentType, contentEncoding, inputStream);
}
}
return null;
}
});
}
}
private ServiceWorkerClientCompat dummyServiceWorkerClientCompat() {
return DummyServiceWorkerClientCompat.INSTANCE;
}
@Override
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
plugin = null;
}
private static final class DummyServiceWorkerClientCompat extends ServiceWorkerClientCompat {
static final ServiceWorkerClientCompat INSTANCE = new DummyServiceWorkerClientCompat();
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(@NonNull WebResourceRequest request) {
return null;
}
}
}
@@ -0,0 +1,78 @@
package com.pichillilorenzo.flutter_inappwebview_android.tracing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.TracingConfig;
import androidx.webkit.TracingController;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Map;
import java.util.concurrent.Executors;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class TracingControllerChannelDelegate extends ChannelDelegateImpl {
@Nullable
private TracingControllerManager tracingControllerManager;
public TracingControllerChannelDelegate(@NonNull TracingControllerManager tracingControllerManager, @NonNull MethodChannel channel) {
super(channel);
this.tracingControllerManager = tracingControllerManager;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
TracingControllerManager.init();
TracingController tracingController = TracingControllerManager.tracingController;
switch (call.method) {
case "isTracing":
if (tracingController != null) {
result.success(tracingController.isTracing());
} else {
result.success(false);
}
break;
case "start":
if (tracingController != null && WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
Map<String, Object> settingsMap = (Map<String, Object>) call.argument("settings");
TracingSettings settings = new TracingSettings();
settings.parse(settingsMap);
TracingConfig config = TracingControllerManager.buildTracingConfig(settings);
tracingController.start(config);
result.success(true);
} else {
result.success(false);
}
break;
case "stop":
if (tracingController != null && WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
String filePath = (String) call.argument("filePath");
try {
result.success(tracingController.stop(
filePath != null ? new FileOutputStream(filePath) : null,
Executors.newSingleThreadExecutor()));
} catch (FileNotFoundException e) {
e.printStackTrace();
result.success(false);
}
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
@Override
public void dispose() {
super.dispose();
tracingControllerManager = null;
}
}
@@ -0,0 +1,61 @@
package com.pichillilorenzo.flutter_inappwebview_android.tracing;
import androidx.annotation.Nullable;
import androidx.webkit.TracingConfig;
import androidx.webkit.TracingController;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
import io.flutter.plugin.common.MethodChannel;
public class TracingControllerManager implements Disposable {
protected static final String LOG_TAG = "TracingControllerMan";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_tracingcontroller";
@Nullable
public TracingControllerChannelDelegate channelDelegate;
@Nullable
public static TracingController tracingController;
@Nullable
public InAppWebViewFlutterPlugin plugin;
public TracingControllerManager(final InAppWebViewFlutterPlugin plugin) {
this.plugin = plugin;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME);
this.channelDelegate = new TracingControllerChannelDelegate(this, channel);
}
public static void init() {
if (tracingController == null &&
WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
tracingController = TracingController.getInstance();
}
}
public static TracingConfig buildTracingConfig(TracingSettings settings) {
TracingConfig.Builder builder = new TracingConfig.Builder();
for (Object category : settings.categories) {
if (category instanceof String) {
builder.addCategories((String) category);
}
if (category instanceof Integer) {
builder.addCategories((Integer) category);
}
}
if (settings.tracingMode != null) {
builder.setTracingMode(settings.tracingMode);
}
return builder.build();
}
@Override
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
plugin = null;
}
}
@@ -0,0 +1,61 @@
package com.pichillilorenzo.flutter_inappwebview_android.tracing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.webkit.TracingController;
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TracingSettings implements ISettings<TracingController> {
public static final String LOG_TAG = "TracingSettings";
@NonNull
public List<Object> categories = new ArrayList<>();
@Nullable
public Integer tracingMode;
@NonNull
@Override
public TracingSettings parse(@NonNull Map<String, Object> settings) {
for (Map.Entry<String, Object> pair : settings.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "categories":
categories = (List<Object>) value;
break;
case "tracingMode":
tracingMode = (Integer) value;
break;
}
}
return this;
}
@NonNull
@Override
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
settings.put("categories", categories);
settings.put("tracingMode", tracingMode);
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull TracingController tracingController) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}
@@ -0,0 +1,103 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class AndroidResource {
@NonNull
private String name;
@Nullable
private String defType;
@Nullable
private String defPackage;
public AndroidResource(@NonNull String name, @Nullable String defType, @Nullable String defPackage) {
this.name = name;
this.defType = defType;
this.defPackage = defPackage;
}
@Nullable
public static AndroidResource fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String name = (String) map.get("name");
String defType = (String) map.get("defType");
String defPackage = (String) map.get("defPackage");
return new AndroidResource(name, defType, defPackage);
}
public Map<String, Object> toMap() {
Map<String, Object> urlRequestMap = new HashMap<>();
urlRequestMap.put("name", name);
urlRequestMap.put("defType", defType);
urlRequestMap.put("defPackage", defPackage);
return urlRequestMap;
}
@NonNull
public String getName() {
return name;
}
public void setName(@NonNull String name) {
this.name = name;
}
@Nullable
public String getDefType() {
return defType;
}
public void setDefType(@Nullable String defType) {
this.defType = defType;
}
@Nullable
public String getDefPackage() {
return defPackage;
}
public void setDefPackage(@Nullable String defPackage) {
this.defPackage = defPackage;
}
public int getIdentifier(@NonNull Context ctx) {
return ctx.getResources().getIdentifier(name, defType, defPackage);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AndroidResource that = (AndroidResource) o;
if (!name.equals(that.name)) return false;
if (defType != null ? !defType.equals(that.defType) : that.defType != null) return false;
return defPackage != null ? defPackage.equals(that.defPackage) : that.defPackage == null;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (defType != null ? defType.hashCode() : 0);
result = 31 * result + (defPackage != null ? defPackage.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AndroidResource{" +
"name='" + name + '\'' +
", type='" + defType + '\'' +
", defPackage='" + defPackage + '\'' +
'}';
}
}
@@ -0,0 +1,47 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class BaseCallbackResultImpl<T> implements ICallbackResult<T> {
@Override
public boolean nonNullSuccess(@NonNull T result) {
return true;
}
@Override
public boolean nullSuccess() {
return true;
}
@Override
public void defaultBehaviour(@Nullable T result) {}
@Override
public void success(@Nullable Object obj) {
T result = decodeResult(obj);
boolean shouldRunDefaultBehaviour;
if (result == null) {
shouldRunDefaultBehaviour = nullSuccess();
} else {
shouldRunDefaultBehaviour = nonNullSuccess(result);
}
if (shouldRunDefaultBehaviour) {
defaultBehaviour(result);
}
}
@Nullable
@Override
public T decodeResult(@Nullable Object obj) {
return null;
}
@Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {}
@Override
public void notImplemented() {
defaultBehaviour(null);
}
}
@@ -0,0 +1,37 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class ChannelDelegateImpl implements IChannelDelegate {
@Nullable
private MethodChannel channel;
public ChannelDelegateImpl(@NonNull MethodChannel channel) {
this.channel = channel;
this.channel.setMethodCallHandler(this);
}
@Override
@Nullable
public MethodChannel getChannel() {
return channel;
}
@CallSuper
public void dispose() {
if (channel != null) {
channel.setMethodCallHandler(null);
channel = null;
}
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
}
}
@@ -0,0 +1,85 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.Nullable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ClientCertChallenge extends URLAuthenticationChallenge {
@Nullable
private Principal[] principals;
@Nullable
private String[] keyTypes;
public ClientCertChallenge(URLProtectionSpace protectionSpace, @Nullable Principal[] principals, @Nullable String[] keyTypes) {
super(protectionSpace);
this.principals = principals;
this.keyTypes = keyTypes;
}
public Map<String, Object> toMap() {
List<String> principalList = null;
if (principals != null) {
principalList = new ArrayList<>();
for (Principal principal : principals) {
principalList.add(principal.getName());
}
}
Map<String, Object> challengeMap = super.toMap();
challengeMap.put("principals", principalList);
challengeMap.put("keyTypes", keyTypes != null ? Arrays.asList(keyTypes) : null);
return challengeMap;
}
@Nullable
public Principal[] getPrincipals() {
return principals;
}
public void setPrincipals(@Nullable Principal[] principals) {
this.principals = principals;
}
@Nullable
public String[] getKeyTypes() {
return keyTypes;
}
public void setKeyTypes(@Nullable String[] keyTypes) {
this.keyTypes = keyTypes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ClientCertChallenge that = (ClientCertChallenge) o;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
if (!Arrays.equals(principals, that.principals)) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(keyTypes, that.keyTypes);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Arrays.hashCode(principals);
result = 31 * result + Arrays.hashCode(keyTypes);
return result;
}
@Override
public String toString() {
return "ClientCertChallenge{" +
"principals=" + Arrays.toString(principals) +
", keyTypes=" + Arrays.toString(keyTypes) +
"} " + super.toString();
}
}
@@ -0,0 +1,105 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public class ClientCertResponse {
@NonNull
private String certificatePath;
@Nullable
private String certificatePassword;
@NonNull
private String keyStoreType;
@Nullable
private Integer action;
public ClientCertResponse(@NonNull String certificatePath, @Nullable String certificatePassword, @NonNull String keyStoreType, @Nullable Integer action) {
this.certificatePath = certificatePath;
this.certificatePassword = certificatePassword;
this.keyStoreType = keyStoreType;
this.action = action;
}
@Nullable
public static ClientCertResponse fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String certificatePath = (String) map.get("certificatePath");
String certificatePassword = (String) map.get("certificatePassword");
String keyStoreType = (String) map.get("keyStoreType");
Integer action = (Integer) map.get("action");
return new ClientCertResponse(certificatePath, certificatePassword, keyStoreType, action);
}
@NonNull
public String getCertificatePath() {
return certificatePath;
}
public void setCertificatePath(@NonNull String certificatePath) {
this.certificatePath = certificatePath;
}
@Nullable
public String getCertificatePassword() {
return certificatePassword;
}
public void setCertificatePassword(@Nullable String certificatePassword) {
this.certificatePassword = certificatePassword;
}
@NonNull
public String getKeyStoreType() {
return keyStoreType;
}
public void setKeyStoreType(@NonNull String keyStoreType) {
this.keyStoreType = keyStoreType;
}
@Nullable
public Integer getAction() {
return action;
}
public void setAction(@Nullable Integer action) {
this.action = action;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClientCertResponse that = (ClientCertResponse) o;
if (!certificatePath.equals(that.certificatePath)) return false;
if (certificatePassword != null ? !certificatePassword.equals(that.certificatePassword) : that.certificatePassword != null)
return false;
if (!keyStoreType.equals(that.keyStoreType)) return false;
return action != null ? action.equals(that.action) : that.action == null;
}
@Override
public int hashCode() {
int result = certificatePath.hashCode();
result = 31 * result + (certificatePassword != null ? certificatePassword.hashCode() : 0);
result = 31 * result + keyStoreType.hashCode();
result = 31 * result + (action != null ? action.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ClientCertResponse{" +
"certificatePath='" + certificatePath + '\'' +
", certificatePassword='" + certificatePassword + '\'' +
", keyStoreType='" + keyStoreType + '\'' +
", action=" + action +
'}';
}
}
@@ -0,0 +1,63 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public class ContentWorld {
@NonNull
private String name;
public static final ContentWorld PAGE = new ContentWorld("page");
public static final ContentWorld DEFAULT_CLIENT = new ContentWorld("defaultClient");
private ContentWorld(@NonNull String name) {
this.name = name;
}
public static ContentWorld world(@NonNull String name) {
return new ContentWorld(name);
}
@Nullable
public static ContentWorld fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String name = (String) map.get("name");
assert name != null;
return new ContentWorld(name);
}
@NonNull
public String getName() {
return name;
}
public void setName(@NonNull String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ContentWorld that = (ContentWorld) o;
return name.equals(that.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return "ContentWorld{" +
"name='" + name + '\'' +
'}';
}
}
@@ -0,0 +1,70 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import java.util.Map;
public class CreateWindowAction extends NavigationAction {
int windowId;
boolean isDialog;
public CreateWindowAction(URLRequest request, boolean isForMainFrame, boolean hasGesture, boolean isRedirect, int windowId, boolean isDialog) {
super(request, isForMainFrame, hasGesture, isRedirect);
this.windowId = windowId;
this.isDialog = isDialog;
}
public Map<String, Object> toMap() {
Map<String, Object> createWindowActionMap = super.toMap();
createWindowActionMap.put("windowId", windowId);
createWindowActionMap.put("isDialog", isDialog);
createWindowActionMap.put("windowFeatures", null);
return createWindowActionMap;
}
public int getWindowId() {
return windowId;
}
public void setWindowId(int windowId) {
this.windowId = windowId;
}
public boolean isDialog() {
return isDialog;
}
public void setDialog(boolean dialog) {
isDialog = dialog;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
CreateWindowAction that = (CreateWindowAction) o;
if (windowId != that.windowId) return false;
return isDialog == that.isDialog;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + windowId;
result = 31 * result + (isDialog ? 1 : 0);
return result;
}
@Override
public String toString() {
return "CreateWindowAction{" +
"windowId=" + windowId +
", isDialog=" + isDialog +
", request=" + request +
", isForMainFrame=" + isForMainFrame +
", hasGesture=" + hasGesture +
", isRedirect=" + isRedirect +
'}';
}
}
@@ -0,0 +1,89 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.Map;
public class CustomSchemeResponse {
@NonNull
private byte[] data;
@NonNull
private String contentType;
@NonNull
private String contentEncoding;
public CustomSchemeResponse(@NonNull byte[] data, @NonNull String contentType, @NonNull String contentEncoding) {
this.data = data;
this.contentType = contentType;
this.contentEncoding = contentEncoding;
}
@Nullable
public static CustomSchemeResponse fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
byte[] data = (byte[]) map.get("data");
String contentType = (String) map.get("contentType");
String contentEncoding = (String) map.get("contentEncoding");
return new CustomSchemeResponse(data, contentType, contentEncoding);
}
@NonNull
public byte[] getData() {
return data;
}
public void setData(@NonNull byte[] data) {
this.data = data;
}
@NonNull
public String getContentType() {
return contentType;
}
public void setContentType(@NonNull String contentType) {
this.contentType = contentType;
}
@NonNull
public String getContentEncoding() {
return contentEncoding;
}
public void setContentEncoding(@NonNull String contentEncoding) {
this.contentEncoding = contentEncoding;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomSchemeResponse that = (CustomSchemeResponse) o;
if (!Arrays.equals(data, that.data)) return false;
if (!contentType.equals(that.contentType)) return false;
return contentEncoding.equals(that.contentEncoding);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(data);
result = 31 * result + contentType.hashCode();
result = 31 * result + contentEncoding.hashCode();
return result;
}
@Override
public String toString() {
return "CustomSchemeResponse{" +
"data=" + Arrays.toString(data) +
", contentType='" + contentType + '\'' +
", contentEncoding='" + contentEncoding + '\'' +
'}';
}
}
@@ -0,0 +1,104 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.Map;
public class CustomTabsActionButton {
private int id;
@NonNull
private byte[] icon;
@NonNull
private String description;
private boolean shouldTint;
public CustomTabsActionButton(int id, @NonNull byte[] icon, @NonNull String description, boolean shouldTint) {
this.id = id;
this.icon = icon;
this.description = description;
this.shouldTint = shouldTint;
}
@Nullable
public static CustomTabsActionButton fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
int id = (int) map.get("id");
byte[] icon = (byte[]) map.get("icon");
String description = (String) map.get("description");
boolean shouldTint = (boolean) map.get("shouldTint");
return new CustomTabsActionButton(id, icon, description, shouldTint);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public byte[] getIcon() {
return icon;
}
public void setIcon(@NonNull byte[] icon) {
this.icon = icon;
}
@NonNull
public String getDescription() {
return description;
}
public void setDescription(@NonNull String description) {
this.description = description;
}
public boolean isShouldTint() {
return shouldTint;
}
public void setShouldTint(boolean shouldTint) {
this.shouldTint = shouldTint;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomTabsActionButton that = (CustomTabsActionButton) o;
if (id != that.id) return false;
if (shouldTint != that.shouldTint) return false;
if (!Arrays.equals(icon, that.icon)) return false;
return description.equals(that.description);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + Arrays.hashCode(icon);
result = 31 * result + description.hashCode();
result = 31 * result + (shouldTint ? 1 : 0);
return result;
}
@Override
public String toString() {
return "CustomTabsActionButton{" +
"id=" + id +
", icon=" + Arrays.toString(icon) +
", description='" + description + '\'' +
", shouldTint=" + shouldTint +
'}';
}
}
@@ -0,0 +1,71 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public class CustomTabsMenuItem {
private int id;
@NonNull
private String label;
public CustomTabsMenuItem(int id, @NonNull String label) {
this.id = id;
this.label = label;
}
@Nullable
public static CustomTabsMenuItem fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
int id = (int) map.get("id");
String label = (String) map.get("label");
return new CustomTabsMenuItem(id, label);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public String getLabel() {
return label;
}
public void setLabel(@NonNull String label) {
this.label = label;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomTabsMenuItem that = (CustomTabsMenuItem) o;
if (id != that.id) return false;
return label.equals(that.label);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + label.hashCode();
return result;
}
@Override
public String toString() {
return "CustomTabsMenuItem{" +
"id=" + id +
", label='" + label + '\'' +
'}';
}
}
@@ -0,0 +1,83 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class CustomTabsSecondaryToolbar {
@NonNull
private AndroidResource layout;
@NonNull
private List<AndroidResource> clickableIDs = new ArrayList<>();
public CustomTabsSecondaryToolbar(@NonNull AndroidResource layout, @NonNull List<AndroidResource> clickableIDs) {
this.layout = layout;
this.clickableIDs = clickableIDs;
}
@Nullable
public static CustomTabsSecondaryToolbar fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
AndroidResource layout = AndroidResource.fromMap((Map<String, Object>) map.get("layout"));
List<AndroidResource> clickableIDs = new ArrayList<>();
List<Map<String, Object>> clickableIDList = (List<Map<String, Object>>) map.get("clickableIDs");
if (clickableIDList != null) {
for (Map<String, Object> clickableIDMap : clickableIDList) {
AndroidResource clickableID = AndroidResource.fromMap((Map<String, Object>) clickableIDMap.get("id"));
if (clickableID != null) {
clickableIDs.add(clickableID);
}
}
}
return new CustomTabsSecondaryToolbar(layout, clickableIDs);
}
@NonNull
public AndroidResource getLayout() {
return layout;
}
public void setLayout(@NonNull AndroidResource layout) {
this.layout = layout;
}
@NonNull
public List<AndroidResource> getClickableIDs() {
return clickableIDs;
}
public void setClickableIDs(@NonNull List<AndroidResource> clickableIDs) {
this.clickableIDs = clickableIDs;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomTabsSecondaryToolbar that = (CustomTabsSecondaryToolbar) o;
if (!layout.equals(that.layout)) return false;
return clickableIDs.equals(that.clickableIDs);
}
@Override
public int hashCode() {
int result = layout.hashCode();
result = 31 * result + clickableIDs.hashCode();
return result;
}
@Override
public String toString() {
return "CustomTabsSecondaryToolbar{" +
"layout=" + layout +
", clickableIDs=" + clickableIDs +
'}';
}
}
@@ -0,0 +1,5 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
public interface Disposable {
void dispose();
}
@@ -0,0 +1,150 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class DownloadStartRequest {
@NonNull
private String url;
@NonNull
private String userAgent;
@NonNull
private String contentDisposition;
@NonNull
private String mimeType;
private long contentLength;
@Nullable
private String suggestedFilename;
@Nullable
private String textEncodingName;
public DownloadStartRequest(@NonNull String url, @NonNull String userAgent, @NonNull String contentDisposition, @NonNull String mimeType, long contentLength, @Nullable String suggestedFilename, @Nullable String textEncodingName) {
this.url = url;
this.userAgent = userAgent;
this.contentDisposition = contentDisposition;
this.mimeType = mimeType;
this.contentLength = contentLength;
this.suggestedFilename = suggestedFilename;
this.textEncodingName = textEncodingName;
}
public Map<String, Object> toMap() {
Map<String, Object> obj = new HashMap<>();
obj.put("url", url);
obj.put("userAgent", userAgent);
obj.put("contentDisposition", contentDisposition);
obj.put("mimeType", mimeType);
obj.put("contentLength", contentLength);
obj.put("suggestedFilename", suggestedFilename);
obj.put("textEncodingName", textEncodingName);
return obj;
}
@NonNull
public String getUrl() {
return url;
}
public void setUrl(@NonNull String url) {
this.url = url;
}
@NonNull
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(@NonNull String userAgent) {
this.userAgent = userAgent;
}
@NonNull
public String getContentDisposition() {
return contentDisposition;
}
public void setContentDisposition(@NonNull String contentDisposition) {
this.contentDisposition = contentDisposition;
}
@NonNull
public String getMimeType() {
return mimeType;
}
public void setMimeType(@NonNull String mimeType) {
this.mimeType = mimeType;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
@Nullable
public String getSuggestedFilename() {
return suggestedFilename;
}
public void setSuggestedFilename(@Nullable String suggestedFilename) {
this.suggestedFilename = suggestedFilename;
}
@Nullable
public String getTextEncodingName() {
return textEncodingName;
}
public void setTextEncodingName(@Nullable String textEncodingName) {
this.textEncodingName = textEncodingName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DownloadStartRequest that = (DownloadStartRequest) o;
if (contentLength != that.contentLength) return false;
if (!url.equals(that.url)) return false;
if (!userAgent.equals(that.userAgent)) return false;
if (!contentDisposition.equals(that.contentDisposition)) return false;
if (!mimeType.equals(that.mimeType)) return false;
if (suggestedFilename != null ? !suggestedFilename.equals(that.suggestedFilename) : that.suggestedFilename != null)
return false;
return textEncodingName != null ? textEncodingName.equals(that.textEncodingName) : that.textEncodingName == null;
}
@Override
public int hashCode() {
int result = url.hashCode();
result = 31 * result + userAgent.hashCode();
result = 31 * result + contentDisposition.hashCode();
result = 31 * result + mimeType.hashCode();
result = 31 * result + (int) (contentLength ^ (contentLength >>> 32));
result = 31 * result + (suggestedFilename != null ? suggestedFilename.hashCode() : 0);
result = 31 * result + (textEncodingName != null ? textEncodingName.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "DownloadStartRequest{" +
"url='" + url + '\'' +
", userAgent='" + userAgent + '\'' +
", contentDisposition='" + contentDisposition + '\'' +
", mimeType='" + mimeType + '\'' +
", contentLength=" + contentLength +
", suggestedFilename='" + suggestedFilename + '\'' +
", textEncodingName='" + textEncodingName + '\'' +
'}';
}
}
@@ -0,0 +1,76 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import java.util.HashMap;
import java.util.Map;
public class FindSession {
private int resultCount;
private int highlightedResultIndex;
private int searchResultDisplayStyle = 2; // matches NONE of iOS
public FindSession(int resultCount, int highlightedResultIndex) {
this.resultCount = resultCount;
this.highlightedResultIndex = highlightedResultIndex;
}
public Map<String, Object> toMap() {
Map<String, Object> obj = new HashMap<>();
obj.put("resultCount", resultCount);
obj.put("highlightedResultIndex", highlightedResultIndex);
obj.put("searchResultDisplayStyle", searchResultDisplayStyle);
return obj;
}
public int getResultCount() {
return resultCount;
}
public void setResultCount(int resultCount) {
this.resultCount = resultCount;
}
public int getHighlightedResultIndex() {
return highlightedResultIndex;
}
public void setHighlightedResultIndex(int highlightedResultIndex) {
this.highlightedResultIndex = highlightedResultIndex;
}
public int getSearchResultDisplayStyle() {
return searchResultDisplayStyle;
}
public void setSearchResultDisplayStyle(int searchResultDisplayStyle) {
this.searchResultDisplayStyle = searchResultDisplayStyle;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FindSession that = (FindSession) o;
if (resultCount != that.resultCount) return false;
if (highlightedResultIndex != that.highlightedResultIndex) return false;
return searchResultDisplayStyle == that.searchResultDisplayStyle;
}
@Override
public int hashCode() {
int result = resultCount;
result = 31 * result + highlightedResultIndex;
result = 31 * result + searchResultDisplayStyle;
return result;
}
@Override
public String toString() {
return "FindSession{" +
"resultCount=" + resultCount +
", highlightedResultIndex=" + highlightedResultIndex +
", searchResultDisplayStyle=" + searchResultDisplayStyle +
'}';
}
}
@@ -0,0 +1,84 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public class GeolocationPermissionShowPromptResponse {
@NonNull
private String origin;
boolean allow;
boolean retain;
public GeolocationPermissionShowPromptResponse(@NonNull String origin, boolean allow, boolean retain) {
this.origin = origin;
this.allow = allow;
this.retain = retain;
}
@Nullable
public static GeolocationPermissionShowPromptResponse fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String origin = (String) map.get("origin");
boolean allow = (boolean) map.get("allow");
boolean retain = (boolean) map.get("retain");
return new GeolocationPermissionShowPromptResponse(origin, allow, retain);
}
@NonNull
public String getOrigin() {
return origin;
}
public void setOrigin(@NonNull String origin) {
this.origin = origin;
}
public boolean isAllow() {
return allow;
}
public void setAllow(boolean allow) {
this.allow = allow;
}
public boolean isRetain() {
return retain;
}
public void setRetain(boolean retain) {
this.retain = retain;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeolocationPermissionShowPromptResponse that = (GeolocationPermissionShowPromptResponse) o;
if (allow != that.allow) return false;
if (retain != that.retain) return false;
return origin.equals(that.origin);
}
@Override
public int hashCode() {
int result = origin.hashCode();
result = 31 * result + (allow ? 1 : 0);
result = 31 * result + (retain ? 1 : 0);
return result;
}
@Override
public String toString() {
return "GeolocationPermissionShowPromptResponse{" +
"origin='" + origin + '\'' +
", allow=" + allow +
", retain=" + retain +
'}';
}
}
@@ -0,0 +1,79 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import android.webkit.WebView;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class HitTestResult {
private int type;
@Nullable
private String extra;
public HitTestResult(int type, @Nullable String extra) {
this.type = type;
this.extra = extra;
}
@Nullable
static public HitTestResult fromWebViewHitTestResult(@Nullable WebView.HitTestResult hitTestResult) {
if (hitTestResult == null) {
return null;
}
return new HitTestResult(hitTestResult.getType(), hitTestResult.getExtra());
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Nullable
public String getExtra() {
return extra;
}
public void setExtra(@Nullable String extra) {
this.extra = extra;
}
@Nullable
public Map<String, Object> toMap() {
Map<String, Object> hitTestResultMap = new HashMap<>();
hitTestResultMap.put("type", type);
hitTestResultMap.put("extra", extra);
return hitTestResultMap;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HitTestResult that = (HitTestResult) o;
if (type != that.type) return false;
return extra != null ? extra.equals(that.extra) : that.extra == null;
}
@Override
public int hashCode() {
int result = type;
result = 31 * result + (extra != null ? extra.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "HitTestResultMap{" +
"type=" + type +
", extra='" + extra + '\'' +
'}';
}
}
@@ -0,0 +1,102 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
public class HttpAuthResponse {
@NonNull
private String username;
@NonNull
private String password;
boolean permanentPersistence;
@Nullable
private Integer action;
public HttpAuthResponse(@NonNull String username, @NonNull String password, boolean permanentPersistence, @Nullable Integer action) {
this.username = username;
this.password = password;
this.permanentPersistence = permanentPersistence;
this.action = action;
}
@Nullable
public static HttpAuthResponse fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String username = (String) map.get("username");
String password = (String) map.get("password");
boolean permanentPersistence = (boolean) map.get("permanentPersistence");
Integer action = (Integer) map.get("action");
return new HttpAuthResponse(username, password, permanentPersistence, action);
}
@NonNull
public String getUsername() {
return username;
}
public void setUsername(@NonNull String username) {
this.username = username;
}
@NonNull
public String getPassword() {
return password;
}
public void setPassword(@NonNull String password) {
this.password = password;
}
public boolean isPermanentPersistence() {
return permanentPersistence;
}
public void setPermanentPersistence(boolean permanentPersistence) {
this.permanentPersistence = permanentPersistence;
}
@Nullable
public Integer getAction() {
return action;
}
public void setAction(@Nullable Integer action) {
this.action = action;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HttpAuthResponse that = (HttpAuthResponse) o;
if (permanentPersistence != that.permanentPersistence) return false;
if (!username.equals(that.username)) return false;
if (!password.equals(that.password)) return false;
return action != null ? action.equals(that.action) : that.action == null;
}
@Override
public int hashCode() {
int result = username.hashCode();
result = 31 * result + password.hashCode();
result = 31 * result + (permanentPersistence ? 1 : 0);
result = 31 * result + (action != null ? action.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "HttpAuthResponse{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", permanentPersistence=" + permanentPersistence +
", action=" + action +
'}';
}
}
@@ -0,0 +1,71 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.Nullable;
import java.util.Map;
public class HttpAuthenticationChallenge extends URLAuthenticationChallenge {
private int previousFailureCount;
@Nullable
URLCredential proposedCredential;
public HttpAuthenticationChallenge(URLProtectionSpace protectionSpace, int previousFailureCount, @Nullable URLCredential proposedCredential) {
super(protectionSpace);
this.previousFailureCount = previousFailureCount;
this.proposedCredential = proposedCredential;
}
public Map<String, Object> toMap() {
Map<String, Object> challengeMap = super.toMap();
challengeMap.put("previousFailureCount", previousFailureCount);
challengeMap.put("proposedCredential", (proposedCredential != null) ? proposedCredential.toMap() : null);
challengeMap.put("failureResponse", null);
challengeMap.put("error", null);
return challengeMap;
}
public int getPreviousFailureCount() {
return previousFailureCount;
}
public void setPreviousFailureCount(int previousFailureCount) {
this.previousFailureCount = previousFailureCount;
}
@Nullable
public URLCredential getProposedCredential() {
return proposedCredential;
}
public void setProposedCredential(@Nullable URLCredential proposedCredential) {
this.proposedCredential = proposedCredential;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
HttpAuthenticationChallenge that = (HttpAuthenticationChallenge) o;
if (previousFailureCount != that.previousFailureCount) return false;
return proposedCredential != null ? proposedCredential.equals(that.proposedCredential) : that.proposedCredential == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + previousFailureCount;
result = 31 * result + (proposedCredential != null ? proposedCredential.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "HttpAuthenticationChallenge{" +
"previousFailureCount=" + previousFailureCount +
", proposedCredential=" + proposedCredential +
"} " + super.toString();
}
}
@@ -0,0 +1,13 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.MethodChannel;
public interface ICallbackResult<T> extends MethodChannel.Result {
boolean nonNullSuccess(@NonNull T result);
boolean nullSuccess();
void defaultBehaviour(@Nullable T result);
@Nullable T decodeResult(@Nullable Object obj);
}
@@ -0,0 +1,10 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.MethodChannel;
public interface IChannelDelegate extends MethodChannel.MethodCallHandler, Disposable {
@Nullable
MethodChannel getChannel();
}
@@ -0,0 +1,146 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview_android.Util;
import java.util.Map;
import java.util.Objects;
public class InAppBrowserMenuItem {
private int id;
@NonNull
private String title;
@Nullable
private Integer order;
@Nullable
private Object icon;
@Nullable
private String iconColor;
private boolean showAsAction;
public InAppBrowserMenuItem(int id, @NonNull String title, @Nullable Integer order, @Nullable Object icon,
@Nullable String iconColor, boolean showAsAction) {
this.id = id;
this.title = title;
this.order = order;
this.icon = icon;
this.iconColor = iconColor;
this.showAsAction = showAsAction;
}
@Nullable
public static InAppBrowserMenuItem fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
int id = (int) map.get("id");
String title = (String) map.get("title");
Integer order = (Integer) map.get("order");
Object icon = map.get("icon");
if (icon instanceof Map) {
icon = AndroidResource.fromMap((Map<String, Object>) map.get("icon"));
} else if (!(icon instanceof byte[])) {
icon = null;
}
String iconColor = (String) map.get("iconColor");
boolean showAsAction = Util.getOrDefault( map, "showAsAction", false);
return new InAppBrowserMenuItem(id, title, order, icon, iconColor, showAsAction);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NonNull
public String getTitle() {
return title;
}
public void setTitle(@NonNull String title) {
this.title = title;
}
@Nullable
public Integer getOrder() {
return order;
}
public void setOrder(@Nullable Integer order) {
this.order = order;
}
@Nullable
public Object getIcon() {
return icon;
}
public void setIcon(@Nullable Object icon) {
this.icon = icon;
}
@Nullable
public String getIconColor() {
return iconColor;
}
public void setIconColor(@Nullable String iconColor) {
this.iconColor = iconColor;
}
public boolean isShowAsAction() {
return showAsAction;
}
public void setShowAsAction(boolean showAsAction) {
this.showAsAction = showAsAction;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InAppBrowserMenuItem that = (InAppBrowserMenuItem) o;
if (id != that.id) return false;
if (showAsAction != that.showAsAction) return false;
if (!title.equals(that.title)) return false;
if (!Objects.equals(order, that.order)) return false;
if (!Objects.equals(icon, that.icon)) return false;
return Objects.equals(iconColor, that.iconColor);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + title.hashCode();
result = 31 * result + (order != null ? order.hashCode() : 0);
result = 31 * result + (icon != null ? icon.hashCode() : 0);
result = 31 * result + (iconColor != null ? iconColor.hashCode() : 0);
result = 31 * result + (showAsAction ? 1 : 0);
return result;
}
@Override
public String toString() {
return "InAppBrowserMenuItem{" +
"id=" + id +
", title='" + title + '\'' +
", order=" + order +
", icon=" + icon +
", iconColor='" + iconColor + '\'' +
", showAsAction=" + showAsAction +
'}';
}
}
@@ -0,0 +1,98 @@
package com.pichillilorenzo.flutter_inappwebview_android.types;
import androidx.annotation.Nullable;
import java.util.Map;
public class JsAlertResponse {
private String message;
private String confirmButtonTitle;
private boolean handledByClient;
@Nullable
private Integer action;
public JsAlertResponse(String message, String confirmButtonTitle, boolean handledByClient, @Nullable Integer action) {
this.message = message;
this.confirmButtonTitle = confirmButtonTitle;
this.handledByClient = handledByClient;
this.action = action;
}
@Nullable
public static JsAlertResponse fromMap(@Nullable Map<String, Object> map) {
if (map == null) {
return null;
}
String message = (String) map.get("message");
String confirmButtonTitle = (String) map.get("confirmButtonTitle");
boolean handledByClient = (boolean) map.get("handledByClient");
Integer action = (Integer) map.get("action");
return new JsAlertResponse(message, confirmButtonTitle, handledByClient, action);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getConfirmButtonTitle() {
return confirmButtonTitle;
}
public void setConfirmButtonTitle(String confirmButtonTitle) {
this.confirmButtonTitle = confirmButtonTitle;
}
public boolean isHandledByClient() {
return handledByClient;
}
public void setHandledByClient(boolean handledByClient) {
this.handledByClient = handledByClient;
}
@Nullable
public Integer getAction() {
return action;
}
public void setAction(@Nullable Integer action) {
this.action = action;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JsAlertResponse that = (JsAlertResponse) o;
if (handledByClient != that.handledByClient) return false;
if (message != null ? !message.equals(that.message) : that.message != null) return false;
if (confirmButtonTitle != null ? !confirmButtonTitle.equals(that.confirmButtonTitle) : that.confirmButtonTitle != null)
return false;
return action != null ? action.equals(that.action) : that.action == null;
}
@Override
public int hashCode() {
int result = message != null ? message.hashCode() : 0;
result = 31 * result + (confirmButtonTitle != null ? confirmButtonTitle.hashCode() : 0);
result = 31 * result + (handledByClient ? 1 : 0);
result = 31 * result + (action != null ? action.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "JsAlertResponse{" +
"message='" + message + '\'' +
", confirmButtonTitle='" + confirmButtonTitle + '\'' +
", handledByClient=" + handledByClient +
", action=" + action +
'}';
}
}
Loaded 100 of 556 files, more files were not shown because too many files have changed in this diff. Show more