Commit 0a1a1afe by BJQ

1st commit

parents
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/TbsWebView.iml" filepath="$PROJECT_DIR$/.idea/TbsWebView.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="d6154041-0374-4796-8a4b-5959ca085465" name="Default Changelist" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ProjectId" id="1WNQf87c7BgBPJEN4o9h0jdKv5T" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/android/src" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="I:\WorkSpaces\WebstromWorkSpace\TbsWebView\src\android\src" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="I:\WorkSpaces\WebstromWorkSpace\TbsWebView\src\android\src" />
<recent name="I:\WorkSpaces\WebstromWorkSpace\TbsWebView\src\android" />
</key>
</component>
<component name="ServiceViewManager">
<option name="viewStates">
<list>
<serviceView>
<treeState>
<expand />
<select />
</treeState>
</serviceView>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="d6154041-0374-4796-8a4b-5959ca085465" name="Default Changelist" comment="" />
<created>1578987672813</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1578987672813</updated>
<workItem from="1578987676026" duration="2202000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="WindowStateProjectService">
<state x="-1180" y="275" key="FileChooserDialogImpl" timestamp="1578988592003">
<screen x="-1920" y="0" width="1920" height="1040" />
</state>
<state x="-1180" y="275" key="FileChooserDialogImpl/0.0.1366.728/-1920.0.1920.1040@-1920.0.1920.1040" timestamp="1578988592003" />
</component>
</project>
\ No newline at end of file
{
"name": "tbswebview",
"version": "1.0.0",
"description": "a cordova plugin for android to replace corcova webview to tencent tbs",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Bai Jinqiu",
"license": "ISC"
}
<?xml version='1.0' encoding='utf-8'?>
<plugin id="com.webview" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android">
<name>TbsWebView</name>
<js-module name="TbsWebView" src="www/TbsWebView.js">
<clobbers target="cordova.plugins.TbsWebView"/>
</js-module>
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<!-- <preference name="webView" value="org.apache.cordova.x5engine.X5WebViewEngine"/>-->
<preference name="webView" value="com.webview.TestWebViewEngine" />
<feature name="TestWebView">
<param name="android-package" value="com.webview.TestWebView" />
</feature>
<!-- <preference name="loadUrlTimeoutValue" value="700000" />-->
<!-- <preference name="appUserAgent" value="appX5"/>-->
<preference name="android-minSdkVersion" value="21" />
</config-file>
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
</config-file>
<source-file src="src/android/libs/tbs_sdk_thirdapp_v4.3.0.1148_43697_sharewithdownloadwithfile_withoutGame_obfs_20190805_175505.jar" target-dir="libs"/>
<source-file src="src/android/src/jniLibs/armeabi/liblbs.so" target-dir="jniLibs/armeabi"/>
<source-file src="src/android/src/webview/AndroidProtocolHandler.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestCookieManager.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestCordovaClientCertRequest.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestCordovaHttpAuthHandler.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestSystemExposedJsApi.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestSystemWebView.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestSystemWebViewEngine.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestWebChromeClient.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestWebView.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestWebViewClient.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/TestWebViewEngine.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/UriMatcher.java" target-dir="src/com/webview"/>
<source-file src="src/android/src/webview/WebViewLocalServer.java" target-dir="src/com/webview"/>
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5CookieManager.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5CordovaClientCertRequest.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5CordovaHttpAuthHandler.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5ExposedJsApi.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebChromeClient.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebView.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebViewClient.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebViewEngine.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/UriMatcher.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/AndroidProtocolHandler.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebViewLocalServer.java" target-dir="src/org/apache/cordova/x5engine"/>-->
<!-- <source-file src="platforms/android/src/jniLibs/armeabi/liblbs.so" target-dir="jniLibs/armeabi"/>-->
<!-- <source-file src="platforms/android/libs/tbs_sdk_thirdapp_v4.3.0.1148_43697_sharewithdownloadwithfile_withoutGame_obfs_20190805_175505.jar" target-dir="libs"/>-->
</platform>
</plugin>
package com.webview;
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class AndroidProtocolHandler {
private static final String TAG = "AndroidProtocolHandler";
private Context context;
public AndroidProtocolHandler(Context context) {
this.context = context;
}
public InputStream openAsset(String path) throws IOException {
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
}
public InputStream openResource(Uri uri) {
assert uri.getPath() != null;
// The path must be of the form ".../asset_type/asset_name.ext".
List<String> pathSegments = uri.getPathSegments();
String assetType = pathSegments.get(pathSegments.size() - 2);
String assetName = pathSegments.get(pathSegments.size() - 1);
// Drop the file extension.
assetName = assetName.split("\\.")[0];
try {
// Use the application context for resolving the resource package name so that we do
// not use the browser's own resources. Note that if 'context' here belongs to the
// test suite, it does not have a separate application context. In that case we use
// the original context object directly.
if (context.getApplicationContext() != null) {
context = context.getApplicationContext();
}
int fieldId = getFieldId(context, assetType, assetName);
int valueType = getValueType(context, fieldId);
if (valueType == TypedValue.TYPE_STRING) {
return context.getResources().openRawResource(fieldId);
} else {
Log.e(TAG, "Asset not of type string: " + uri);
return null;
}
} catch (ClassNotFoundException e) {
Log.e(TAG, "Unable to open resource URL: " + uri, e);
return null;
} catch (NoSuchFieldException e) {
Log.e(TAG, "Unable to open resource URL: " + uri, e);
return null;
} catch (IllegalAccessException e) {
Log.e(TAG, "Unable to open resource URL: " + uri, e);
return null;
}
}
public InputStream openFile(String filePath) throws IOException {
String realPath = filePath.replace(WebViewLocalServer.fileStart, "");
File localFile = new File(realPath);
return new FileInputStream(localFile);
}
public InputStream openContentUrl(Uri uri) throws IOException {
Integer port = uri.getPort();
String realPath;
if (port == -1) {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + WebViewLocalServer.contentStart, "content:/");
} else {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + WebViewLocalServer.contentStart, "content:/");
}
InputStream stream = null;
try {
stream = context.getContentResolver().openInputStream(Uri.parse(realPath));
} catch (SecurityException e) {
Log.e(TAG, "Unable to open content URL: " + uri, e);
}
return stream;
}
private static int getFieldId(Context context, String assetType, String assetName)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> d = context.getClassLoader()
.loadClass(context.getPackageName() + ".R$" + assetType);
java.lang.reflect.Field field = d.getField(assetName);
int id = field.getInt(null);
return id;
}
private static int getValueType(Context context, int fieldId) {
TypedValue value = new TypedValue();
context.getResources().getValue(fieldId, value, true);
return value.type;
}
}
package com.webview;
import android.annotation.TargetApi;
import android.os.Build;
import com.tencent.smtt.sdk.CookieManager;
import com.tencent.smtt.sdk.WebView;
import org.apache.cordova.ICordovaCookieManager;
public class TestCookieManager implements ICordovaCookieManager {
protected final WebView webView;
private final CookieManager cookieManager;
//Added because lint can't see the conditional RIGHT ABOVE this
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TestCookieManager(WebView webview) {
webView = webview;
cookieManager = CookieManager.getInstance();
//REALLY? Nobody has seen this UNTIL NOW?
// cookieManager.setAcceptFileSchemeCookies(true);
cookieManager.setAcceptCookie(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, true);
}
}
public void setCookiesEnabled(boolean accept) {
cookieManager.setAcceptCookie(accept);
}
public void setCookie(final String url, final String value) {
cookieManager.setCookie(url, value);
}
public String getCookie(final String url) {
return cookieManager.getCookie(url);
}
@SuppressWarnings("deprecation")
public void clearCookies() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.removeAllCookies(null);
} else {
cookieManager.removeAllCookie();
}
}
public void flush() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
}
}
};
\ No newline at end of file
package com.webview;
import com.tencent.smtt.export.external.interfaces.ClientCertRequest;
import org.apache.cordova.ICordovaClientCertRequest;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
public class TestCordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public TestCordovaClientCertRequest(ClientCertRequest request) {
this.request = request;
}
/**
* Cancel this request
*/
public void cancel()
{
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost()
{
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes()
{
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort()
{
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals()
{
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore()
{
request.ignore();
}
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
{
request.proceed(privateKey, chain);
}
}
package com.webview;
import android.util.Log;
import com.tencent.smtt.export.external.interfaces.HttpAuthHandler;
import org.apache.cordova.ICordovaHttpAuthHandler;
public class TestCordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final HttpAuthHandler handler;
private static final String TAG = "TestCordovaHttpAuthHand";
public TestCordovaHttpAuthHandler(HttpAuthHandler handler) {
Log.e(TAG, "TestCordovaHttpAuthHandler: " + (handler != null ? "存在" : "不存在"));
this.handler = handler;
}
/**
* Instructs the WebView to cancel the authentication request.
*/
public void cancel() {
this.handler.cancel();
}
/**
* Instructs the WebView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed(String username, String password) {
this.handler.proceed(username, password);
}
}
\ No newline at end of file
package com.webview;
import android.webkit.JavascriptInterface;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.ExposedJsApi;
import org.json.JSONException;
public class TestSystemExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
TestSystemExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
}
\ No newline at end of file
package com.webview;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
import org.apache.cordova.engine.SystemWebViewEngine;
public class TestSystemWebView extends WebView implements CordovaWebViewEngine.EngineView {
private TestWebViewClient viewClient;
TestWebChromeClient chromeClient;
private TestSystemWebViewEngine parentEngine;
private CordovaInterface cordova;
public TestSystemWebView(Context context) {
this(context, null);
}
public TestSystemWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Package visibility to enforce that only SystemWebViewEngine should call this method.
void init(TestSystemWebViewEngine parentEngine, CordovaInterface cordova) {
this.cordova = cordova;
this.parentEngine = parentEngine;
if (this.viewClient == null) {
setWebViewClient(new TestWebViewClient(parentEngine));
}
if (this.chromeClient == null) {
setWebChromeClient(new TestWebChromeClient(parentEngine));
}
}
@Override
public CordovaWebView getCordovaWebView() {
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
}
@Override
public void setWebViewClient(WebViewClient webViewClient) {
// super.setWebViewClient(webViewClient);
viewClient = (TestWebViewClient)webViewClient;
super.setWebViewClient(webViewClient);
}
@Override
public void setWebChromeClient(WebChromeClient webChromeClient) {
chromeClient = (TestWebChromeClient) webChromeClient;
super.setWebChromeClient(webChromeClient);
// super.setWebChromeClient(webChromeClient);
}
// @Override
// public void setWebViewClient(WebViewClient client) {
// viewClient = (TestWebViewClient)client;
// super.setWebViewClient(client);
// }
//
// @Override
// public void setWebChromeClient(WebChromeClient client) {
// chromeClient = (TestWebChromeClient)client;
// super.setWebChromeClient(client);
// }
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
if (ret != null) {
return ret.booleanValue();
}
return super.dispatchKeyEvent(event);
}
}
package com.webview;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.view.View;
import android.webkit.ValueCallback;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebView;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPreferences;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
import org.apache.cordova.ICordovaCookieManager;
import org.apache.cordova.LOG;
import org.apache.cordova.NativeToJsMessageQueue;
import org.apache.cordova.PluginManager;
public class TestSystemWebViewEngine implements CordovaWebViewEngine {
public static final String TAG = "TestSystemWebViewEngine";
protected final TestSystemWebView webView;
protected final TestCookieManager cookieManager;
protected CordovaPreferences preferences;
protected CordovaBridge bridge;
protected CordovaWebViewEngine.Client client;
protected CordovaWebView parentWebView;
protected CordovaInterface cordova;
protected PluginManager pluginManager;
protected CordovaResourceApi resourceApi;
protected NativeToJsMessageQueue nativeToJsMessageQueue;
private BroadcastReceiver receiver;
/**
* Used when created via reflection.
*/
public TestSystemWebViewEngine(Context context, CordovaPreferences preferences) {
this(new TestSystemWebView(context), preferences);
}
public TestSystemWebViewEngine(TestSystemWebView webView) {
this(webView, null);
}
public TestSystemWebViewEngine(TestSystemWebView webView, CordovaPreferences preferences) {
this.preferences = preferences;
this.webView = webView;
cookieManager = new TestCookieManager(webView);
}
@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
if (this.cordova != null) {
throw new IllegalStateException();
}
// Needed when prefs are not passed by the constructor
if (preferences == null) {
preferences = parentWebView.getPreferences();
}
this.parentWebView = parentWebView;
this.cordova = cordova;
this.client = client;
this.resourceApi = resourceApi;
this.pluginManager = pluginManager;
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
webView.init(this, cordova);
initWebViewSettings();
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
@Override
public void setNetworkAvailable(boolean value) {
//sometimes this can be called after calling webview.destroy() on destroy()
//thus resulting in a NullPointerException
if (webView != null) {
webView.setNetworkAvailable(value);
}
}
@Override
public void runOnUiThread(Runnable r) {
TestSystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
exposeJsInterface(webView, bridge);
}
@Override
public CordovaWebView getCordovaWebView() {
return parentWebView;
}
@Override
public ICordovaCookieManager getCookieManager() {
return cookieManager;
}
@Override
public View getView() {
return webView;
}
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
@SuppressWarnings("deprecation")
private void initWebViewSettings() {
webView.setInitialScale(0);
webView.setVerticalScrollBarEnabled(false);
// Enable JavaScript
final WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
String manufacturer = android.os.Build.MANUFACTURER;
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
//We don't save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
// while we do this
settings.setAllowUniversalAccessFromFileURLs(true);
settings.setMediaPlaybackRequiresUserGesture(false);
// Enable database
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
enableRemoteDebugging();
}
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
settings.setDomStorageEnabled(true);
// Enable built-in geolocation
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
String defaultUserAgent = settings.getUserAgentString();
// Fix for CB-3360
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
if (overrideUserAgent != null) {
settings.setUserAgentString(overrideUserAgent);
} else {
String appendUserAgent = preferences.getString("AppendUserAgent", null);
if (appendUserAgent != null) {
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
}
}
// End CB-3360
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
settings.getUserAgentString();
}
};
webView.getContext().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
}
private void enableRemoteDebugging() {
try {
WebView.setWebContentsDebuggingEnabled(true);
} catch (IllegalArgumentException e) {
LOG.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
e.printStackTrace();
}
}
// Yeah, we know. It'd be great if lint was just a little smarter.
@SuppressLint("AddJavascriptInterface")
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
TestSystemExposedJsApi exposedJsApi = new TestSystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
/**
* Load the url into the webview.
*/
@Override
public void loadUrl(final String url, boolean clearNavigationStack) {
webView.loadUrl(url);
}
@Override
public String getUrl() {
return webView.getUrl();
}
@Override
public void stopLoading() {
webView.stopLoading();
}
@Override
public void clearCache() {
webView.clearCache(true);
}
@Override
public void clearHistory() {
webView.clearHistory();
}
@Override
public boolean canGoBack() {
return webView.canGoBack();
}
/**
* Go to previous page in history. (We manage our own history)
*
* @return true if we went back, false if we are already at top
*/
@Override
public boolean goBack() {
// Check webview first to see if there is a history
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
if (webView.canGoBack()) {
webView.goBack();
return true;
}
return false;
}
@Override
public void setPaused(boolean value) {
if (value) {
webView.onPause();
webView.pauseTimers();
} else {
webView.onResume();
webView.resumeTimers();
}
}
@Override
public void destroy() {
// tbs SDK中的chromeClient无此方法
// webView.chromeClient.destroyLastDialog();
webView.destroy();
// unregister the receiver
if (receiver != null) {
try {
webView.getContext().unregisterReceiver(receiver);
} catch (Exception e) {
LOG.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
}
}
}
// @Override
// public void evaluateJavascript(String js, ValueCallback<String> callback) {
// webView.evaluateJavascript(js, callback);
// }
@Override
public void evaluateJavascript(String js, ValueCallback<String> callback) {
webView.evaluateJavascript(js, (com.tencent.smtt.sdk.ValueCallback<String>) callback);
}
}
\ No newline at end of file
package com.webview;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.PermissionRequest;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import com.tencent.smtt.export.external.interfaces.ConsoleMessage;
import com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback;
import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient;
import com.tencent.smtt.export.external.interfaces.JsPromptResult;
import com.tencent.smtt.export.external.interfaces.JsResult;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebStorage;
import com.tencent.smtt.sdk.WebView;
import org.apache.cordova.CordovaDialogsHelper;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
import org.apache.cordova.engine.SystemWebViewEngine;
import java.util.Arrays;
public class TestWebChromeClient extends WebChromeClient {
private static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "SystemWebChromeClient";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected final TestSystemWebViewEngine parentEngine;
// the video progress view
private View mVideoProgressView;
private CordovaDialogsHelper dialogsHelper;
private Context appContext;
private android.webkit.WebChromeClient.CustomViewCallback mCustomViewCallback;
private View mCustomView;
public TestWebChromeClient(TestSystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
appContext = parentEngine.webView.getContext();
dialogsHelper = new CordovaDialogsHelper(appContext);
}
/**
* Tell the client to display a javascript alert dialog.
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
dialogsHelper.showAlert(message, new CordovaDialogsHelper.Result() {
@Override public void gotResult(boolean success, String value) {
if (success) {
result.confirm();
} else {
result.cancel();
}
}
});
return true;
}
/**
* Tell the client to display a confirm dialog to the user.
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
dialogsHelper.showConfirm(message, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm();
} else {
result.cancel();
}
}
});
return true;
}
/**
* Tell the client to display a prompt dialog to the user.
* If the client returns true, WebView will assume that the client will
* handle the prompt dialog and call the appropriate JsPromptResult method.
*
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*/
@Override
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
if (handledRet != null) {
result.confirm(handledRet);
} else {
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
@Override
public void gotResult(boolean success, String value) {
if (success) {
result.confirm(value);
} else {
result.cancel();
}
}
});
}
return true;
}
/**
* Handle database quota exceeded notification.
*/
@Override
@SuppressWarnings("deprecation")
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
{
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
quotaUpdater.updateQuota(MAX_QUOTA);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if (consoleMessage.message() != null)
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissionsCallback callback) {
// super.onGeolocationPermissionsShowPrompt(s, geolocationPermissionsCallback);
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
//Get the plugin, it should be loaded
CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
if(geolocation != null && !geolocation.hasPermisssion())
{
geolocation.requestPermissions(0);
}
}
// @Override
// /**
// * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
// *
// * This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
// *
// * @param origin
// * @param callback
// */
// public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
// super.onGeolocationPermissionsShowPrompt(origin, callback);
// callback.invoke(origin, true, false);
// //Get the plugin, it should be loaded
// CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
// if(geolocation != null && !geolocation.hasPermisssion())
// {
// geolocation.requestPermissions(0);
// }
//
// }
@Override
@SuppressWarnings("deprecation")
public void onShowCustomView(View view, IX5WebChromeClient.CustomViewCallback customViewCallback) {
// super.onShowCustomView(view, customViewCallback);
parentEngine.getCordovaWebView().showCustomView(view, (android.webkit.WebChromeClient.CustomViewCallback) customViewCallback);
}
// // API level 7 is required for this, see if we could lower this using something else
// @Override
// @SuppressWarnings("deprecation")
// public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// parentEngine.getCordovaWebView().showCustomView(view, callback);
// }
@Override
@SuppressWarnings("deprecation")
public void onHideCustomView() {
parentEngine.getCordovaWebView().hideCustomView();
}
@Override
/**
* Ask the host application for a custom progress view to show while
* a <video> is loading.
* @return View The progress view.
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
// <input type=file> support:
// openFileChooser() is for pre KitKat and in KitKat mr1 (it's known broken in KitKat).
// For Lollipop, we use onShowFileChooser().
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}
public void openFileChooser(final ValueCallback<Uri> uploadMsg, String acceptType, String capture)
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
uploadMsg.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
// Check if multiple-select is specified
Boolean selectMultiple = false;
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
selectMultiple = true;
}
Intent intent = fileChooserParams.createIntent();
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
try {
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri[] result = null;
if (resultCode == Activity.RESULT_OK && intent != null) {
if (intent.getClipData() != null) {
// handle multiple-selected files
final int numSelectedFiles = intent.getClipData().getItemCount();
result = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
result[i] = intent.getClipData().getItemAt(i).getUri();
LOG.d(LOG_TAG, "Receive file chooser URL: " + result[i]);
}
}
else if (intent.getData() != null) {
// handle single-selected file
result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
}
}
filePathsCallback.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
LOG.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
}
//
//
// @TargetApi(Build.VERSION_CODES.LOLLIPOP)
// @Override
// public void onPermissionRequest(final PermissionRequest request) {
// LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
// request.grant(request.getResources());
// }
public void destroyLastDialog(){
dialogsHelper.destroyLastDialog();
}
}
\ No newline at end of file
package com.webview;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.AttributeSet;
import android.util.Log;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
public class TestWebView extends CordovaPlugin {
private TestWebViewClient viewClient;
TestWebChromeClient chromeClient;
private TestWebViewEngine parentEngine;
private CordovaInterface cordova;
private boolean status = false;
private String url = "";
private final String TAG = "TestWebView";
public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
public static final String CDV_SERVER_PATH = "serverBasePath";
public TestWebView(Context context) {
this(context, null);
}
public TestWebView(Context context, AttributeSet attrs) {
// this(context, attrs);
Log.e(TAG, "creating x5webview ");
}
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("setServerBasePath")) {
final String path = args.getString(0);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
((TestWebViewEngine) webView.getEngine()).setServerBasePath(path);
}
});
return true;
} else if (action.equals("getServerBasePath")) {
callbackContext.success(((TestWebViewEngine) webView.getEngine()).getServerBasePath());
return true;
} else if (action.equals("persistServerBasePath")) {
String path = ((TestWebViewEngine) webView.getEngine()).getServerBasePath();
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(CDV_SERVER_PATH, path);
editor.apply();
return true;
}
return false;
}
}
package com.webview;
import android.annotation.TargetApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import com.tencent.smtt.export.external.interfaces.ClientCertRequest;
import com.tencent.smtt.export.external.interfaces.HttpAuthHandler;
import com.tencent.smtt.export.external.interfaces.SslErrorHandler;
import com.tencent.smtt.export.external.interfaces.WebResourceError;
import com.tencent.smtt.export.external.interfaces.WebResourceRequest;
import com.tencent.smtt.export.external.interfaces.WebResourceResponse;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import org.apache.cordova.AuthenticationToken;
import org.apache.cordova.CordovaClientCertRequest;
import org.apache.cordova.CordovaHttpAuthHandler;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import org.apache.cordova.engine.SystemWebViewEngine;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;
public class TestWebViewClient extends WebViewClient {
private static final String TAG = "TestSystemWebViewClient";
protected final TestSystemWebViewEngine parentEngine;
private boolean doClearHistory = false;
boolean isCurrentlyLoading;
/**
* The authorization tokens.
*/
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
public TestWebViewClient(TestSystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return true to override, false for default behavior
*/
@Override
@SuppressWarnings("deprecation")
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return parentEngine.client.onNavigationAttempt(url);
}
/**
* On received http auth request.
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
*/
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
Log.e(TAG, "onReceivedHttpAuthRequest: "+"code run here " );
// Get the authentication token (if specified)
AuthenticationToken token = this.getAuthenticationToken(host, realm);
if (token != null) {
handler.proceed(token.getUserName(), token.getPassword());
return;
}
// Check if there is some plugin which can resolve this auth challenge
PluginManager pluginManager = this.parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new TestCordovaHttpAuthHandler(handler), host, realm)) {
parentEngine.client.clearLoadTimeoutTimer();
return;
}
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
/**
* On received client cert request.
* The method forwards the request to any running plugins before using the default implementation.
*
* @param view
* @param request
*/
@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
// Check if there is some plugin which can resolve this certificate request
PluginManager pluginManager = this.parentEngine.pluginManager;
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new TestCordovaClientCertRequest(request))) {
parentEngine.client.clearLoadTimeoutTimer();
return;
}
// By default pass to WebViewClient
super.onReceivedClientCertRequest(view, request);
}
/**
* Notify the host application that a page has started loading.
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
* embedded frame changes, i.e. clicking a link whose target is an iframe.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
isCurrentlyLoading = true;
// Flush stale messages & reset plugins.
parentEngine.bridge.reset();
parentEngine.client.onPageStarted(url);
}
/**
* Notify the host application that a page has finished loading.
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
*
* @param view The webview initiating the callback.
* @param url The url of the page.
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// Ignore excessive calls, if url is not about:blank (CB-8317).
if (!isCurrentlyLoading && !url.startsWith("about:")) {
return;
}
isCurrentlyLoading = false;
/**
* Because of a timing issue we need to clear this history in onPageFinished as well as
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
* true. You see when you load a url with a # in it which is common in jQuery applications
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
*/
if (this.doClearHistory) {
view.clearHistory();
this.doClearHistory = false;
}
parentEngine.client.onPageFinishedLoading(url);
}
/**
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
@Override
@SuppressWarnings("deprecation")
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// Ignore error due to stopLoading().
if (!isCurrentlyLoading) {
return;
}
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
// If this is a "Protocol Not Supported" error, then revert to the previous
// page. If there was no previous page, then punt. The application's config
// is likely incorrect (start page set to sms: or something like that)
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
parentEngine.client.clearLoadTimeoutTimer();
if (view.canGoBack()) {
view.goBack();
return;
} else {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
}
//
// /**
// * Notify the host application that an SSL error occurred while loading a resource.
// * The host application must call either handler.cancel() or handler.proceed().
// * Note that the decision may be retained for use in response to future SSL errors.
// * The default behavior is to cancel the load.
// *
// * @param view The WebView that is initiating the callback.
// * @param handler An SslErrorHandler object that will handle the user's response.
// * @param error The SSL error object.
// */
//
// @Override
// public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//
// final String packageName = parentEngine.cordova.getActivity().getPackageName();
// final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
//
// ApplicationInfo appInfo;
// try {
// appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
// if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// // debug = true
// handler.proceed();
// return;
// } else {
// // debug = false
// super.onReceivedSslError(view, handler, error);
// }
// } catch (PackageManager.NameNotFoundException e) {
// // When it doubt, lock it out!
// super.onReceivedSslError(view, handler, error);
// }
// }
@Override
public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, com.tencent.smtt.export.external.interfaces.SslError sslError) {
// super.onReceivedSslError(webView, sslErrorHandler, sslError);
final String packageName = parentEngine.cordova.getActivity().getPackageName();
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// debug = true
sslErrorHandler.proceed();
return;
} else {
// debug = false
super.onReceivedSslError(webView, sslErrorHandler, sslError);
}
} catch (PackageManager.NameNotFoundException e) {
// When it doubt, lock it out!
super.onReceivedSslError(webView, sslErrorHandler, sslError);
}
}
/**
* Sets the authentication token.
*
* @param authenticationToken
* @param host
* @param realm
*/
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
if (host == null) {
host = "";
}
if (realm == null) {
realm = "";
}
this.authenticationTokens.put(host.concat(realm), authenticationToken);
}
/**
* Removes the authentication token.
*
* @param host
* @param realm
* @return the authentication token or null if did not exist
*/
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
return this.authenticationTokens.remove(host.concat(realm));
}
/**
* Gets the authentication token.
* <p>
* In order it tries:
* 1- host + realm
* 2- host
* 3- realm
* 4- no host, no realm
*
* @param host
* @param realm
* @return the authentication token
*/
public AuthenticationToken getAuthenticationToken(String host, String realm) {
AuthenticationToken token = null;
token = this.authenticationTokens.get(host.concat(realm));
if (token == null) {
// try with just the host
token = this.authenticationTokens.get(host);
// Try the realm
if (token == null) {
token = this.authenticationTokens.get(realm);
}
// if no host found, just query for default
if (token == null) {
token = this.authenticationTokens.get("");
}
}
return token;
}
/**
* Clear all authentication tokens.
*/
public void clearAuthenticationTokens() {
this.authenticationTokens.clear();
}
@Override
@SuppressWarnings("deprecation")
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
// Check the against the whitelist and lock out access to the WebView directory
// Changing this will cause problems for your application
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
CordovaResourceApi resourceApi = parentEngine.resourceApi;
Uri origUri = Uri.parse(url);
// Allow plugins to intercept WebView requests.
Uri remappedUri = resourceApi.remapUri(origUri);
if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsKitKatContentUrlFix(origUri)) {
CordovaResourceApi.OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
}
// If we don't need to special-case the request, let the browser load it.
return null;
} catch (IOException e) {
if (!(e instanceof FileNotFoundException)) {
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
}
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
}
private static boolean needsKitKatContentUrlFix(Uri uri) {
return "content".equals(uri.getScheme());
}
private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
return false;
}
if (uri.getQuery() != null || uri.getFragment() != null) {
return true;
}
if (!uri.toString().contains("%")) {
return false;
}
return false;
}
// @Override
// public void onLoadResource(WebView webView, String s) {
// super.onLoadResource(webView, s);
// }
//
// @Override
// public boolean shouldOverrideUrlLoading(WebView webView, String s) {
// return super.shouldOverrideUrlLoading(webView, s);
// }
//
// @Override
// public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest webResourceRequest) {
// return super.shouldOverrideUrlLoading(webView, webResourceRequest);
// }
//
// @Override
// public void onPageStarted(WebView webView, String s, Bitmap bitmap) {
// super.onPageStarted(webView, s, bitmap);
// }
//
// @Override
// public void onPageFinished(WebView webView, String s) {
// super.onPageFinished(webView, s);
// }
//
// @Override
// public void onReceivedError(WebView webView, int i, String s, String s1) {
// super.onReceivedError(webView, i, s, s1);
// }
//
// @Override
// public void onReceivedError(WebView webView, WebResourceRequest webResourceRequest, WebResourceError webResourceError) {
// super.onReceivedError(webView, webResourceRequest, webResourceError);
// }
//
// @Override
// public void onReceivedHttpError(WebView webView, WebResourceRequest webResourceRequest, WebResourceResponse webResourceResponse) {
// super.onReceivedHttpError(webView, webResourceRequest, webResourceResponse);
// }
//
// @Override
// public WebResourceResponse shouldInterceptRequest(WebView webView, String s) {
// return super.shouldInterceptRequest(webView, s);
// }
//
// @Override
// public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest webResourceRequest) {
// return super.shouldInterceptRequest(webView, webResourceRequest);
// }
//
// @Override
// public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest webResourceRequest, Bundle bundle) {
// return super.shouldInterceptRequest(webView, webResourceRequest, bundle);
// }
//
// @Override
// public void doUpdateVisitedHistory(WebView webView, String s, boolean b) {
// super.doUpdateVisitedHistory(webView, s, b);
// }
//
// @Override
// public void onFormResubmission(WebView webView, Message message, Message message1) {
// super.onFormResubmission(webView, message, message1);
// }
//
// @Override
// public void onReceivedHttpAuthRequest(WebView webView, HttpAuthHandler httpAuthHandler, String s, String s1) {
// super.onReceivedHttpAuthRequest(webView, httpAuthHandler, s, s1);
// }
//
// @Override
// public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
// super.onReceivedSslError(webView, sslErrorHandler, sslError);
// }
//
// @Override
// public void onReceivedClientCertRequest(WebView webView, ClientCertRequest clientCertRequest) {
// super.onReceivedClientCertRequest(webView, clientCertRequest);
// }
//
// @Override
// public void onScaleChanged(WebView webView, float v, float v1) {
// super.onScaleChanged(webView, v, v1);
// }
//
// @Override
// public void onUnhandledKeyEvent(WebView webView, KeyEvent keyEvent) {
// super.onUnhandledKeyEvent(webView, keyEvent);
// }
//
// @Override
// public boolean shouldOverrideKeyEvent(WebView webView, KeyEvent keyEvent) {
// return super.shouldOverrideKeyEvent(webView, keyEvent);
// }
//
// @Override
// public void onTooManyRedirects(WebView webView, Message message, Message message1) {
// super.onTooManyRedirects(webView, message, message1);
// }
//
// @Override
// public void onReceivedLoginRequest(WebView webView, String s, String s1, String s2) {
// super.onReceivedLoginRequest(webView, s, s1, s2);
// }
//
// @Override
// public void onDetectedBlankScreen(String s, int i) {
// super.onDetectedBlankScreen(s, i);
// }
//
// @Override
// public void onPageCommitVisible(WebView webView, String s) {
// super.onPageCommitVisible(webView, s);
// }
}
package com.webview;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.ICordovaCookieManager;
import org.apache.cordova.engine.SystemWebViewEngine;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.View;
import android.webkit.ValueCallback;
//import android.webkit.WebSettings;
//import android.webkit.WebView;
//import com.ionicframework.cordova.webview.IonicWebView;
//import com.ionicframework.cordova.webview.IonicWebViewEngine;
//import com.ionicframework.cordova.webview.WebViewLocalServer;
import com.tencent.smtt.export.external.interfaces.WebResourceRequest;
import com.tencent.smtt.export.external.interfaces.WebResourceResponse;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebView;
import org.apache.cordova.ConfigXmlParser;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPreferences;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
import org.apache.cordova.NativeToJsMessageQueue;
import org.apache.cordova.PluginManager;
import org.apache.cordova.engine.SystemWebViewClient;
import org.apache.cordova.engine.SystemWebViewEngine;
import org.apache.cordova.engine.SystemWebView;
public class TestWebViewEngine extends TestSystemWebViewEngine {
public static final String TAG = "TestWebViewEngine";
private WebViewLocalServer localServer;
private String CDV_LOCAL_SERVER;
private String scheme;
private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";
/**
* Used when created via reflection.
*/
public TestWebViewEngine(Context context, CordovaPreferences preferences) {
super(new TestSystemWebView(context), preferences);
Log.e(TAG, "testSystem Web View Engine Starting Right Up 1...");
}
public TestWebViewEngine(TestSystemWebView webView) {
super(webView, null);
Log.e(TAG, "testSystem Web View Engine Starting Right Up 2...");
}
public TestWebViewEngine(TestSystemWebView webView, CordovaPreferences preferences) {
super(webView, preferences);
Log.e(TAG, "testSystem Web View Engine Starting Right Up 3...");
}
@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, final CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(cordova.getActivity());
String hostname = preferences.getString("Hostname", "localhost");
scheme = preferences.getString("Scheme", "http");
CDV_LOCAL_SERVER = scheme + "://" + hostname;
localServer = new WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme);
localServer.hostAssets("www");
webView.setWebViewClient(new ServerClient(this, parser));
super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final WebSettings settings = webView.getSettings();
int mode = preferences.getInteger("MixedContentMode", 0);
settings.setMixedContentMode(mode);
}
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(TestWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
String path = prefs.getString(TestWebView.CDV_SERVER_PATH, null);
if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) {
setServerBasePath(path);
}
}
private boolean isNewBinary() {
String versionCode = "";
String versionName = "";
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(TestWebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null);
String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null);
try {
PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
versionCode = Integer.toString(pInfo.versionCode);
versionName = pInfo.versionName;
} catch(Exception ex) {
Log.e(TAG, "Unable to get package info", ex);
}
if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(LAST_BINARY_VERSION_CODE, versionCode);
editor.putString(LAST_BINARY_VERSION_NAME, versionName);
editor.putString(TestWebView.CDV_SERVER_PATH, "");
editor.apply();
return true;
}
return false;
}
private boolean isDeployDisabled() {
return preferences.getBoolean("DisableDeploy", false);
}
private class ServerClient extends TestWebViewClient {
private ConfigXmlParser parser;
public ServerClient(TestSystemWebViewEngine parentEngine, ConfigXmlParser parser) {
super(parentEngine);
this.parser = parser;
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest webResourceRequest) {
// return super.shouldInterceptRequest(webView, webResourceRequest);
return localServer.shouldInterceptRequest(webResourceRequest.getUrl(), webResourceRequest);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, String s) {
// return super.shouldInterceptRequest(webView, s);
return localServer.shouldInterceptRequest(Uri.parse(s), null);
}
//
// @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
// @Override
// public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
// return localServer.shouldInterceptRequest(request.getUrl(), request);
// }
//
// @TargetApi(Build.VERSION_CODES.KITKAT)
// @Override
// public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// return localServer.shouldInterceptRequest(Uri.parse(url), null);
// }
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
String launchUrl = parser.getLaunchUrl();
if (!launchUrl.contains(WebViewLocalServer.httpsScheme) && !launchUrl.contains(WebViewLocalServer.httpScheme) && url.equals(launchUrl)) {
view.stopLoading();
// When using a custom scheme the app won't load if server start url doesn't end in /
String startUrl = CDV_LOCAL_SERVER;
if (!scheme.equalsIgnoreCase(WebViewLocalServer.httpsScheme) && !scheme.equalsIgnoreCase(WebViewLocalServer.httpScheme)) {
startUrl += "/";
}
view.loadUrl(startUrl);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.loadUrl("javascript:(function() { " +
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
"})()");
}
}
public void setServerBasePath(String path) {
localServer.hostFiles(path);
webView.loadUrl(CDV_LOCAL_SERVER);
}
public String getServerBasePath() {
return this.localServer.getBasePath();
}
}
package com.webview;
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed 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.google.webviewlocalserver.third_party.android;
import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class UriMatcher {
/**
* Creates the root node of the URI tree.
*
* @param code the code to match for the root URI
*/
public UriMatcher(Object code) {
mCode = code;
mWhich = -1;
mChildren = new ArrayList<UriMatcher>();
mText = null;
}
private UriMatcher() {
mCode = null;
mWhich = -1;
mChildren = new ArrayList<UriMatcher>();
mText = null;
}
/**
* Add a URI to match, and the code to return when this URI is
* matched. URI nodes may be exact match string, the token "*"
* that matches any text, or the token "#" that matches only
* numbers.
* <p>
* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
* this method will accept a leading slash in the path.
*
* @param authority the authority to match
* @param path the path to match. * may be used as a wild card for
* any text, and # may be used as a wild card for numbers.
* @param code the code that is returned when a URI is matched
* against the given components. Must be positive.
*/
public void addURI(String scheme, String authority, String path, Object code) {
if (code == null) {
throw new IllegalArgumentException("Code can't be null");
}
String[] tokens = null;
if (path != null) {
String newPath = path;
// Strip leading slash if present.
if (path.length() > 0 && path.charAt(0) == '/') {
newPath = path.substring(1);
}
tokens = PATH_SPLIT_PATTERN.split(newPath);
}
int numTokens = tokens != null ? tokens.length : 0;
UriMatcher node = this;
for (int i = -2; i < numTokens; i++) {
String token;
if (i == -2)
token = scheme;
else if (i == -1)
token = authority;
else
token = tokens[i];
ArrayList<UriMatcher> children = node.mChildren;
int numChildren = children.size();
UriMatcher child;
int j;
for (j = 0; j < numChildren; j++) {
child = children.get(j);
if (token.equals(child.mText)) {
node = child;
break;
}
}
if (j == numChildren) {
// Child not found, create it
child = new UriMatcher();
if (token.equals("**")) {
child.mWhich = REST;
} else if (token.equals("*")) {
child.mWhich = TEXT;
} else {
child.mWhich = EXACT;
}
child.mText = token;
node.mChildren.add(child);
node = child;
}
}
node.mCode = code;
}
static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
/**
* Try to match against the path in a url.
*
* @param uri The url whose path we will match against.
* @return The code for the matched node (added using addURI),
* or null if there is no matched node.
*/
public Object match(Uri uri) {
final List<String> pathSegments = uri.getPathSegments();
final int li = pathSegments.size();
UriMatcher node = this;
if (li == 0 && uri.getAuthority() == null) {
return this.mCode;
}
for (int i = -2; i < li; i++) {
String u;
if (i == -2)
u = uri.getScheme();
else if (i == -1)
u = uri.getAuthority();
else
u = pathSegments.get(i);
ArrayList<UriMatcher> list = node.mChildren;
if (list == null) {
break;
}
node = null;
int lj = list.size();
for (int j = 0; j < lj; j++) {
UriMatcher n = list.get(j);
which_switch:
switch (n.mWhich) {
case EXACT:
if (n.mText.equals(u)) {
node = n;
}
break;
case TEXT:
node = n;
break;
case REST:
return n.mCode;
}
if (node != null) {
break;
}
}
if (node == null) {
return null;
}
}
return node.mCode;
}
private static final int EXACT = 0;
private static final int TEXT = 1;
private static final int REST = 2;
private Object mCode;
private int mWhich;
private String mText;
private ArrayList<UriMatcher> mChildren;
}
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed 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.webview;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
//import android.webkit.WebResourceRequest;
//import android.webkit.WebResourceResponse;
import com.tencent.smtt.export.external.interfaces.WebResourceRequest;
import com.tencent.smtt.export.external.interfaces.WebResourceResponse;
import org.apache.cordova.ConfigXmlParser;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
/**
* Helper class meant to be used with the android.webkit.WebView class to enable hosting assets,
* resources and other data on 'virtual' http(s):// URL.
* Hosting assets and resources on http(s):// URLs is desirable as it is compatible with the
* Same-Origin policy.
* <p>
* This class is intended to be used from within the
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, String)} and
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
* WebResourceRequest)}
* methods.
*/
public class WebViewLocalServer {
private static String TAG = "WebViewAssetServer";
private String basePath;
public final static String httpScheme = "http";
public final static String httpsScheme = "https";
public final static String fileStart = "/_app_file_";
public final static String contentStart = "/_app_content_";
private final UriMatcher uriMatcher;
private final AndroidProtocolHandler protocolHandler;
private final String authority;
private final String customScheme;
// Whether we're serving local files or proxying (for example, when doing livereload on a
// non-local endpoint (will be false in that case)
private boolean isAsset;
// Whether to route all requests to paths without extensions back to `index.html`
private final boolean html5mode;
private ConfigXmlParser parser;
public String getAuthority() { return authority; }
/**
* A handler that produces responses for paths on the virtual asset server.
* <p>
* Methods of this handler will be invoked on a background thread and care must be taken to
* correctly synchronize access to any shared state.
* <p>
* On Android KitKat and above these methods may be called on more than one thread. This thread
* may be different than the thread on which the shouldInterceptRequest method was invoke.
* This means that on Android KitKat and above it is possible to block in this method without
* blocking other resources from loading. The number of threads used to parallelize loading
* is an internal implementation detail of the WebView and may change between updates which
* means that the amount of time spend blocking in this method should be kept to an absolute
* minimum.
*/
public abstract static class PathHandler {
protected String mimeType;
private String encoding;
private String charset;
private int statusCode;
private String reasonPhrase;
private Map<String, String> responseHeaders;
public PathHandler() {
this(null, null, 200, "OK", null);
}
public PathHandler(String encoding, String charset, int statusCode,
String reasonPhrase, Map<String, String> responseHeaders) {
this.encoding = encoding;
this.charset = charset;
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
Map<String, String> tempResponseHeaders;
if (responseHeaders == null) {
tempResponseHeaders = new HashMap<String, String>();
} else {
tempResponseHeaders = responseHeaders;
}
tempResponseHeaders.put("Cache-Control", "no-cache");
this.responseHeaders = tempResponseHeaders;
}
abstract public InputStream handle(Uri url);
public String getEncoding() {
return encoding;
}
public String getCharset() {
return charset;
}
public int getStatusCode() {
return statusCode;
}
public String getReasonPhrase() {
return reasonPhrase;
}
public Map<String, String> getResponseHeaders() {
return responseHeaders;
}
}
/**
* Information about the URLs used to host the assets in the WebView.
*/
public static class AssetHostingDetails {
private Uri httpPrefix;
private Uri httpsPrefix;
/*package*/ AssetHostingDetails(Uri httpPrefix, Uri httpsPrefix) {
this.httpPrefix = httpPrefix;
this.httpsPrefix = httpsPrefix;
}
/**
* Gets the http: scheme prefix at which assets are hosted.
*
* @return the http: scheme prefix at which assets are hosted. Can return null.
*/
public Uri getHttpPrefix() {
return httpPrefix;
}
/**
* Gets the https: scheme prefix at which assets are hosted.
*
* @return the https: scheme prefix at which assets are hosted. Can return null.
*/
public Uri getHttpsPrefix() {
return httpsPrefix;
}
}
WebViewLocalServer(Context context, String authority, boolean html5mode, ConfigXmlParser parser, String customScheme) {
uriMatcher = new UriMatcher(null);
this.html5mode = html5mode;
this.parser = parser;
this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext());
this.authority = authority;
this.customScheme = customScheme;
}
private static Uri parseAndVerifyUrl(String url) {
if (url == null) {
return null;
}
Uri uri = Uri.parse(url);
if (uri == null) {
Log.e(TAG, "Malformed URL: " + url);
return null;
}
String path = uri.getPath();
if (path == null || path.length() == 0) {
Log.e(TAG, "URL does not have a path: " + url);
return null;
}
return uri;
}
private static WebResourceResponse createWebResourceResponse(String mimeType, String encoding, int statusCode, String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int finalStatusCode = statusCode;
try {
if (data.available() == 0) {
finalStatusCode = 404;
}
} catch (IOException e) {
finalStatusCode = 500;
}
return new WebResourceResponse(mimeType, encoding, finalStatusCode, reasonPhrase, responseHeaders, data);
} else {
return new WebResourceResponse(mimeType, encoding, data);
}
}
/**
* Attempt to retrieve the WebResourceResponse associated with the given <code>request</code>.
* This method should be invoked from within
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
* WebResourceRequest)}.
*
* @param uri the request Uri to process.
* @return a response if the request URL had a matching handler, null if no handler was found.
*/
public WebResourceResponse shouldInterceptRequest(Uri uri, WebResourceRequest request) {
PathHandler handler;
synchronized (uriMatcher) {
handler = (PathHandler) uriMatcher.match(uri);
}
if (handler == null) {
return null;
}
if (isLocalFile(uri) || uri.getAuthority().equals(this.authority)) {
Log.d("SERVER", "Handling local request: " + uri.toString());
return handleLocalRequest(uri, handler, request);
} else {
return handleProxyRequest(uri, handler);
}
}
private boolean isLocalFile(Uri uri) {
String path = uri.getPath();
if (path.startsWith(contentStart) || path.startsWith(fileStart)) {
return true;
}
return false;
}
private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler, WebResourceRequest request) {
String path = uri.getPath();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && request != null && request.getRequestHeaders().get("Range") != null) {
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
String mimeType = getMimeType(path, responseStream);
Map<String, String> tempResponseHeaders = handler.getResponseHeaders();
int statusCode = 206;
try {
int totalRange = responseStream.available();
String rangeString = request.getRequestHeaders().get("Range");
String[] parts = rangeString.split("=");
String[] streamParts = parts[1].split("-");
String fromRange = streamParts[0];
int range = totalRange-1;
if (streamParts.length > 1) {
range = Integer.parseInt(streamParts[1]);
}
tempResponseHeaders.put("Accept-Ranges", "bytes");
tempResponseHeaders.put("Content-Range", "bytes " + fromRange + "-" + range + "/" + totalRange);
} catch (IOException e) {
statusCode = 404;
}
return createWebResourceResponse(mimeType, handler.getEncoding(),
statusCode, handler.getReasonPhrase(), tempResponseHeaders, responseStream);
}
if (isLocalFile(uri)) {
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
String mimeType = getMimeType(path, responseStream);
return createWebResourceResponse(mimeType, handler.getEncoding(),
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
}
if (path.equals("") || path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
InputStream stream;
String launchURL = parser.getLaunchUrl();
String launchFile = launchURL.substring(launchURL.lastIndexOf("/") + 1, launchURL.length());
try {
String startPath = this.basePath + "/" + launchFile;
if (isAsset) {
stream = protocolHandler.openAsset(startPath);
} else {
stream = protocolHandler.openFile(startPath);
}
} catch (IOException e) {
Log.e(TAG, "Unable to open " + launchFile, e);
return null;
}
return createWebResourceResponse("text/html", handler.getEncoding(),
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
}
int periodIndex = path.lastIndexOf(".");
if (periodIndex >= 0) {
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
String mimeType = getMimeType(path, responseStream);
return createWebResourceResponse(mimeType, handler.getEncoding(),
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
}
return null;
}
/**
* Instead of reading files from the filesystem/assets, proxy through to the URL
* and let an external server handle it.
* @param uri
* @param handler
* @return
*/
private WebResourceResponse handleProxyRequest(Uri uri, PathHandler handler) {
try {
String path = uri.getPath();
URL url = new URL(uri.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(30 * 1000);
conn.setConnectTimeout(30 * 1000);
InputStream stream = conn.getInputStream();
if (path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
return createWebResourceResponse("text/html", handler.getEncoding(),
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
}
int periodIndex = path.lastIndexOf(".");
if (periodIndex >= 0) {
String ext = path.substring(path.lastIndexOf("."), path.length());
// TODO: Conjure up a bit more subtlety than this
if (ext.equals(".html")) {
}
String mimeType = getMimeType(path, stream);
return createWebResourceResponse(mimeType, handler.getEncoding(),
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
}
return createWebResourceResponse("", handler.getEncoding(),
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), stream);
} catch (SocketTimeoutException ex) {
// bridge.handleAppUrlLoadError(ex);
} catch (Exception ex) {
// bridge.handleAppUrlLoadError(ex);
}
return null;
}
private String getMimeType(String path, InputStream stream) {
String mimeType = null;
try {
mimeType = URLConnection.guessContentTypeFromName(path); // Does not recognize *.js
if (mimeType != null && path.endsWith(".js") && mimeType.equals("image/x-icon")) {
Log.d(TestWebViewEngine.TAG, "We shouldn't be here");
}
if (mimeType == null) {
if (path.endsWith(".js") || path.endsWith(".mjs")) {
// Make sure JS files get the proper mimetype to support ES modules
mimeType = "application/javascript";
} else if (path.endsWith(".wasm")) {
mimeType = "application/wasm";
} else {
mimeType = URLConnection.guessContentTypeFromStream(stream);
}
}
} catch (Exception ex) {
Log.e(TAG, "Unable to get mime type" + path, ex);
}
return mimeType;
}
/**
* Registers a handler for the given <code>uri</code>. The <code>handler</code> will be invoked
* every time the <code>shouldInterceptRequest</code> method of the instance is called with
* a matching <code>uri</code>.
*
* @param uri the uri to use the handler for. The scheme and authority (domain) will be matched
* exactly. The path may contain a '*' element which will match a single element of
* a path (so a handler registered for /a/* will be invoked for /a/b and /a/c.html
* but not for /a/b/b) or the '**' element which will match any number of path
* elements.
* @param handler the handler to use for the uri.
*/
void register(Uri uri, PathHandler handler) {
synchronized (uriMatcher) {
uriMatcher.addURI(uri.getScheme(), uri.getAuthority(), uri.getPath(), handler);
}
}
/**
* Hosts the application's assets on an http(s):// URL. Assets from the local path
* <code>assetPath/...</code> will be available under
* <code>http(s)://{uuid}.androidplatform.net/assets/...</code>.
*
* @param assetPath the local path in the application's asset folder which will be made
* available by the server (for example "/www").
*/
public void hostAssets(String assetPath) {
hostAssets(authority, assetPath);
}
/**
* Hosts the application's assets on an http(s):// URL. Assets from the local path
* <code>assetPath/...</code> will be available under
* <code>http(s)://{domain}/{virtualAssetPath}/...</code>.
*
* @param domain custom domain on which the assets should be hosted (for example "example.com").
* @param assetPath the local path in the application's asset folder which will be made
* available by the server (for example "/www").
* @return prefixes under which the assets are hosted.
*/
public void hostAssets(final String domain,
final String assetPath) {
this.isAsset = true;
this.basePath = assetPath;
createHostingDetails();
}
private void createHostingDetails() {
final String assetPath = this.basePath;
if (assetPath.indexOf('*') != -1) {
throw new IllegalArgumentException("assetPath cannot contain the '*' character.");
}
PathHandler handler = new PathHandler() {
@Override
public InputStream handle(Uri url) {
InputStream stream = null;
String path = url.getPath();
try {
if (path.startsWith(contentStart)) {
stream = protocolHandler.openContentUrl(url);
} else if (path.startsWith(fileStart) || !isAsset) {
if (!path.startsWith(fileStart)) {
path = basePath + url.getPath();
}
stream = protocolHandler.openFile(path);
} else {
stream = protocolHandler.openAsset(assetPath + path);
}
} catch (IOException e) {
Log.e(TAG, "Unable to open asset URL: " + url);
return null;
}
return stream;
}
};
registerUriForScheme(httpScheme, handler, authority);
registerUriForScheme(httpsScheme, handler, authority);
if (!customScheme.equals(httpScheme) && !customScheme.equals(httpsScheme)) {
registerUriForScheme(customScheme, handler, authority);
}
}
private void registerUriForScheme(String scheme, PathHandler handler, String authority) {
Uri.Builder uriBuilder = new Uri.Builder();
uriBuilder.scheme(scheme);
uriBuilder.authority(authority);
uriBuilder.path("");
Uri uriPrefix = uriBuilder.build();
register(Uri.withAppendedPath(uriPrefix, "/"), handler);
register(Uri.withAppendedPath(uriPrefix, "**"), handler);
}
/**
* Hosts the application's resources on an http(s):// URL. Resources
* <code>http(s)://{uuid}.androidplatform.net/res/{resource_type}/{resource_name}</code>.
*
* @return prefixes under which the resources are hosted.
*/
public AssetHostingDetails hostResources() {
return hostResources(authority, "/res", true, true);
}
/**
* Hosts the application's resources on an http(s):// URL. Resources
* <code>http(s)://{uuid}.androidplatform.net/{virtualResourcesPath}/{resource_type}/{resource_name}</code>.
*
* @param virtualResourcesPath the path on the local server under which the resources
* should be hosted.
* @param enableHttp whether to enable hosting using the http scheme.
* @param enableHttps whether to enable hosting using the https scheme.
* @return prefixes under which the resources are hosted.
*/
public AssetHostingDetails hostResources(final String virtualResourcesPath, boolean enableHttp,
boolean enableHttps) {
return hostResources(authority, virtualResourcesPath, enableHttp, enableHttps);
}
/**
* Hosts the application's resources on an http(s):// URL. Resources
* <code>http(s)://{domain}/{virtualResourcesPath}/{resource_type}/{resource_name}</code>.
*
* @param domain custom domain on which the assets should be hosted (for example "example.com").
* If untrusted content is to be loaded into the WebView it is advised to make
* this random.
* @param virtualResourcesPath the path on the local server under which the resources
* should be hosted.
* @param enableHttp whether to enable hosting using the http scheme.
* @param enableHttps whether to enable hosting using the https scheme.
* @return prefixes under which the resources are hosted.
*/
public AssetHostingDetails hostResources(final String domain,
final String virtualResourcesPath, boolean enableHttp,
boolean enableHttps) {
if (virtualResourcesPath.indexOf('*') != -1) {
throw new IllegalArgumentException(
"virtualResourcesPath cannot contain the '*' character.");
}
Uri.Builder uriBuilder = new Uri.Builder();
uriBuilder.scheme(httpScheme);
uriBuilder.authority(domain);
uriBuilder.path(virtualResourcesPath);
Uri httpPrefix = null;
Uri httpsPrefix = null;
PathHandler handler = new PathHandler() {
@Override
public InputStream handle(Uri url) {
InputStream stream = protocolHandler.openResource(url);
String mimeType = null;
try {
mimeType = URLConnection.guessContentTypeFromStream(stream);
} catch (Exception ex) {
Log.e(TAG, "Unable to get mime type" + url);
}
return stream;
}
};
if (enableHttp) {
httpPrefix = uriBuilder.build();
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
}
if (enableHttps) {
uriBuilder.scheme(httpsScheme);
httpsPrefix = uriBuilder.build();
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
}
return new AssetHostingDetails(httpPrefix, httpsPrefix);
}
/**
* Hosts the application's files on an http(s):// URL. Files from the basePath
* <code>basePath/...</code> will be available under
* <code>http(s)://{uuid}.androidplatform.net/...</code>.
*
* @param basePath the local path in the application's data folder which will be made
* available by the server (for example "/www").
*/
public void hostFiles(final String basePath) {
this.isAsset = false;
this.basePath = basePath;
createHostingDetails();
}
/**
* The KitKat WebView reads the InputStream on a separate threadpool. We can use that to
* parallelize loading.
*/
private static abstract class LazyInputStream extends InputStream {
protected final PathHandler handler;
private InputStream is = null;
public LazyInputStream(PathHandler handler) {
this.handler = handler;
}
private InputStream getInputStream() {
if (is == null) {
is = handle();
}
return is;
}
protected abstract InputStream handle();
@Override
public int available() throws IOException {
InputStream is = getInputStream();
return (is != null) ? is.available() : 0;
}
@Override
public int read() throws IOException {
InputStream is = getInputStream();
return (is != null) ? is.read() : -1;
}
@Override
public int read(byte b[]) throws IOException {
InputStream is = getInputStream();
return (is != null) ? is.read(b) : -1;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
InputStream is = getInputStream();
return (is != null) ? is.read(b, off, len) : -1;
}
@Override
public long skip(long n) throws IOException {
InputStream is = getInputStream();
return (is != null) ? is.skip(n) : 0;
}
}
// For L and above.
private static class LollipopLazyInputStream extends LazyInputStream {
private Uri uri;
private InputStream is;
public LollipopLazyInputStream(PathHandler handler, Uri uri) {
super(handler);
this.uri = uri;
}
@Override
protected InputStream handle() {
return handler.handle(uri);
}
}
public String getBasePath(){
return this.basePath;
}
}
var exec = require('cordova/exec');
exports.coolMethod = function (arg0, success, error) {
exec(success, error, 'TbsWebView', 'coolMethod', [arg0]);
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment