Commit 04b5b6a3 by BJQ

first commit

parents
*.iml
**/.DS_Store
# Default ignored files
/workspace.xml
<?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/cordova-plugin-x5engine-webview.iml" filepath="$PROJECT_DIR$/.idea/cordova-plugin-x5engine-webview.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
/************************************************/
This product bundles CordovaXWalkCoreExtensionBridge.java as well
as the XWalk.pak and the Crosswalk JSApi which is available under a
"3-clause BSD" license. For details, see below:
Copyright (c) 2013 Intel Corporation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# cordova-plugin-x5engine-webview
Makes your Cordova application use the [TBS X5 WebView](http://x5.tencent.com/index)
instead of the System WebView. Requires cordova-android 4.0 or greater.
## 腾讯浏览服务X5SDK
[腾讯浏览服务X5SDK](http://x5.tencent.com/index)是通过调用微信/手机QQ/空间的X5内核,解决系统webview兼容性差、加载速度慢、功能缺陷等问题,开发接入便捷,大小只有253K,仅需几行代码,即可解决一切令开发者们头疼的问题,为用户提供最优秀的浏览体验。
腾讯浏览服务官方只提供了如何把系统Webview替换成X5的接入文档,并没有提供Cordova集成的方法。现在Hybrid App项目经常使用很多Cordoca插件提供拍照,扫二维码,App支付宝支付等功能,因此就需要把cordova和x5结合起来。
Cordova框架现在已经很完善,Cordova的Web Engine也是基于接口开发的,因此我参考系统engine的实现,写了一个x5engine插件,解决了Cordova调用X5内核的问题。
## 问题背景
熟悉Cordova生态圈的朋友可能听说过Crosswalk,[Crosswalk](https://crosswalk-project.org/documentation/cordova.html)是Intel维护的Webkit开源项目,可以通过插件安装命令` cordova plugin add cordova-plugin-crosswalk-webview` 安装,它的缺点就是太大了,集成后apk会增加20M,安装后会占用50M空间,因此一般不推荐普通App使用Crosswalk。
### Benefits
* 同时享受Cordova平台完善的生态系统和腾讯X5内核的兼容性和稳定性,大量的Cordova插件仍然可用, Apk size只增加250k。
* 微信/手机QQ/空间装机量很大,足够覆盖大多数国内用户
* 使用X5内核的播放器增强H5视频播放能力
### Drawbacks
* Increased APK size (about 250Kb)
### Install
The following directions are for cordova-cli (most people).
* Open an existing cordova project, with cordova-android 4.0.0+, and using the latest CLI. TBS X5 variables can be configured as an option when installing the plugin
* Add this plugin
```
$ cordova plugin add https://github.com/offbye/cordova-plugin-x5engine-webview.git
```
* Build
```
$ cordova build android
```
### Known issue
1. 64位手机上加载包含x5 so文件的插件报错
`TBS:initX5Core -- loadSucc: false; exception: java.lang.reflect.InvocationTargetException; cause: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/data/com.tencent.mm/app_tbs/core_share/libmttwebview.so" is 32-bit instead of 64-bit
`
解决办法是在libs/armeabi目录下增加一个32位的JNI so, 随便弄个小一点的so加上就可以,如果已经用了其它的JNI so应该不会有这个错误。
2. X5内核不支持file://本地域url,但支持本地相对路径。
{
"name": "cordova-plugin-x5engine-webview",
"version": "2.0.0",
"description": "Changes the default WebView to QQ X5 WebView",
"cordova": {
"id": "cordova-plugin-x5engine-webview",
"platforms": [
"android"
]
},
"repository": {
"type": "git",
"url": "https://github.com/offbye/cordova-plugin-x5engine-webview.git"
},
"keywords": [
"cordova",
"chromium",
"x5engine",
"webview",
"engine",
"ecosystem:cordova",
"cordova-android"
],
"engines": [{
"name": "cordova-android",
"version": ">=4"
}, {
"name": "cordova-plugman",
"version": ">=4.2.0"
}],
"author": "",
"license": "Apache 2.0",
"bugs": {
"url": "https://github.com/offbye/cordova-plugin-x5engine-webview"
},
"homepage": "https://github.com/offbye/cordova-plugin-x5engine-webview"
}
package org.apache.cordova.x5engine;
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(X5WebViewLocalServer.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() + X5WebViewLocalServer.contentStart, "content:/");
} else {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + X5WebViewLocalServer.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 org.apache.cordova.x5engine;
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;
}
/*
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 org.apache.cordova.x5engine;
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;
class X5CookieManager 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 X5CookieManager(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);
}
public void clearCookies() {
cookieManager.removeAllCookie();
}
public void flush() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
}
}
};
package org.apache.cordova.x5engine;
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;
/**
* Implementation of the ICordovaClientCertRequest for Android WebView.
*
* Created by zhangxitao on 16/9/14.
*/
public class X5CordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public X5CordovaClientCertRequest(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 org.apache.cordova.x5engine;
/**
* Created by zhangxitao on 16/9/14.
*/
import com.tencent.smtt.export.external.interfaces.HttpAuthHandler;
import org.apache.cordova.ICordovaHttpAuthHandler;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public class X5CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final HttpAuthHandler handler;
public X5CordovaHttpAuthHandler(HttpAuthHandler handler) {
this.handler = handler;
}
/**
* Instructs the XWalkView to cancel the authentication request.
*/
public void cancel() {
handler.cancel();
}
/**
* Instructs the XWalkView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed(String username, String password) {
handler.proceed(username, password);
}
}
/*
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 org.apache.cordova.x5engine;
import android.webkit.JavascriptInterface;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.ExposedJsApi;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
class X5ExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
X5ExposedJsApi(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);
}
}
/*
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 org.apache.cordova.x5engine;
import java.util.Arrays;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
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 android.webkit.PermissionRequest;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import org.apache.cordova.CordovaDialogsHelper;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.LOG;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
* The kind of callbacks that happen here are on the chrome outside the document,
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
* to but different than CordovaWebViewClient.
*/
public class X5WebChromeClient extends WebChromeClient {
private static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "X5WebChromeClient";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected final X5WebViewEngine parentEngine;
// the video progress view
private View mVideoProgressView;
private CordovaDialogsHelper dialogsHelper;
private Context appContext;
private IX5WebChromeClient.CustomViewCallback mCustomViewCallback;
private View mCustomView;
public X5WebChromeClient(X5WebViewEngine 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
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);
}
@TargetApi(8)
@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
/**
* 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, GeolocationPermissionsCallback 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);
}
}
//TODO API level 7 is required for this, see if we could lower this using something else
// @Override
// public void onShowCustomView(View view, IX5WebChromeClient.CustomViewCallback callback) {
// parentEngine.getCordovaWebView().showCustomView(view, callback);
// }
public void onHideCustomView() {
parentEngine.getCordovaWebView().hideCustomView();
}
/**
* 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(LayoutParams.WRAP_CONTENT, 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(LayoutParams.WRAP_CONTENT, 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 FileChooserParams fileChooserParams) {
Intent intent = fileChooserParams.createIntent();
try {
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Uri[] result = FileChooserParams.parseResult(resultCode, intent);
Log.d(LOG_TAG, "Receive file chooser URL: " + result);
filePathsCallback.onReceiveValue(result);
}
}, intent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
Log.w(LOG_TAG, "No activity found to handle file chooser intent." + e);
filePathsCallback.onReceiveValue(null);
}
return true;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onPermissionRequest(final PermissionRequest request) {
Log.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
request.grant(request.getResources());
}
public void destroyLastDialog(){
dialogsHelper.destroyLastDialog();
}
}
/*
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 org.apache.cordova.x5engine;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
/**
* Custom WebView subclass that enables us to capture events needed for Cordova.
*/
public class X5WebView extends WebView implements CordovaWebViewEngine.EngineView {
private X5WebViewClient viewClient;
X5WebChromeClient chromeClient;
private X5WebViewEngine parentEngine;
private CordovaInterface cordova;
public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
public static final String CDV_SERVER_PATH = "serverBasePath";
private boolean status = false;
private String url = "";
private final String TAG = "X5WebView";
//reflection级别的构造函数....
public X5WebView(Context context) {
this(context, null);
}
public X5WebView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG , "creating x5webview ");
}
// Package visibility to enforce that only X5WebViewEngine should call this method.
void init(X5WebViewEngine parentEngine, CordovaInterface cordova) {
this.cordova = cordova;
this.parentEngine = parentEngine;
if (this.viewClient == null) {
setWebViewClient(new X5WebViewClient(parentEngine));
}
if (this.chromeClient == null) {
setWebChromeClient(new X5WebChromeClient(parentEngine));
}
}
@Override
public CordovaWebView getCordovaWebView() {
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
}
@Override
public void setWebViewClient(WebViewClient client) {
Log.d(TAG , " =============== setWebViewClient " + client + " =================");
viewClient = (X5WebViewClient)client;
super.setWebViewClient(client);
}
@Override
public void setWebChromeClient(WebChromeClient client) {
Log.d(TAG , " =============== setWebViewChromeClient " + client + " =================");
chromeClient = (X5WebChromeClient)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);
}
@Override
public void loadUrl(String s) {
Log.d(TAG , " =============== LOADING THE URL " + s + " =================");
super.loadUrl(s);
}
}
/*
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 org.apache.cordova.x5engine;
import android.annotation.TargetApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import com.tencent.smtt.export.external.interfaces.ClientCertRequest;
import com.tencent.smtt.export.external.interfaces.HttpAuthHandler;
import com.tencent.smtt.export.external.interfaces.SslError;
import com.tencent.smtt.export.external.interfaces.SslErrorHandler;
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.CordovaResourceApi;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;
/**
* This class is the WebViewClient that implements callbacks for our web view.
* The kind of callbacks that happen here are regarding the rendering of the
* document instead of the chrome surrounding it, such as onPageStarted(),
* shouldOverrideUrlLoading(), etc. Related to but different than
* CordovaChromeClient.
*/
public class X5WebViewClient extends WebViewClient {
private static final String TAG = "X5WebViewClient";
protected final X5WebViewEngine parentEngine;
private boolean doClearHistory = false;
boolean isCurrentlyLoading;
/** The authorization tokens. */
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
public X5WebViewClient(X5WebViewEngine 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
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) {
// 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 X5CordovaHttpAuthHandler(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
*/
@TargetApi(21)
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 X5CordovaClientCertRequest(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
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.
*/
@TargetApi(8)
@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 (NameNotFoundException e) {
// When it doubt, lock it out!
super.onReceivedSslError(view, handler, error);
}
}
/**
* 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.
*
* 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();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
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 Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && "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;
}
switch(Build.VERSION.SDK_INT){
case Build.VERSION_CODES.ICE_CREAM_SANDWICH:
case Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
return true;
}
return false;
}
}
/*
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 org.apache.cordova.x5engine;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.SeekBar;
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.QbSdk;
import com.tencent.smtt.sdk.TbsDownloader;
import com.tencent.smtt.sdk.TbsListener;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebSettings.LayoutAlgorithm;
import com.tencent.smtt.sdk.WebView;
import org.apache.cordova.ConfigXmlParser;
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.NativeToJsMessageQueue;
import org.apache.cordova.PluginManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Glue class between CordovaWebView (main Cordova logic) and SystemWebView (the actual View).
* We make the Engine separate from the actual View so that:
* A) We don't need to worry about WebView methods clashing with CordovaWebViewEngine methods
* (e.g.: goBack() is void for WebView, and boolean for CordovaWebViewEngine)
* B) Separating the actual View from the Engine makes API surfaces smaller.
* Class uses two-phase initialization. However, CordovaWebView is responsible for calling .init().
*/
public class X5WebViewEngine implements CordovaWebViewEngine {
public static final String TAG = "X5WebViewEngine";
protected final X5WebView webView;
protected final X5CookieManager cookieManager;
protected CordovaPreferences preferences;
protected CordovaBridge bridge;
protected Client client;
protected CordovaWebView parentWebView;
protected CordovaInterface cordova;
protected PluginManager pluginManager;
protected CordovaResourceApi resourceApi;
protected NativeToJsMessageQueue nativeToJsMessageQueue;
private BroadcastReceiver receiver;
private X5WebViewLocalServer localServer;
private String CDV_LOCAL_SERVER;
private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";
private Context mContext;
/**
* Used when created via reflection.
*/
public X5WebViewEngine(Context context, CordovaPreferences preferences) {
this(new X5WebView(context), preferences);
}
public X5WebViewEngine(X5WebView webView) {
this(webView, null);
}
public X5WebViewEngine(X5WebView webView, CordovaPreferences preferences) {
this.preferences = preferences;
this.webView = webView;
// global context holder in this class
this.mContext = webView.getContext();
cookieManager = new X5CookieManager(webView);
}
/*
my codes under this line
*/
// runTime permission request code , no user here
private int PERMISSION_REQUEST = 100;
private boolean threadFlag = true;
// a dialog to show message include status,download progress and finally result
AlertDialog downloadDialog, finalDownload;
SeekBar seekBar;
// init views
private void initViews() {
seekBar = new SeekBar(mContext);
seekBar.setMax(100);
seekBar.setEnabled(false);
downloadDialog = new AlertDialog.Builder(mContext)
.setTitle("必要组件正在下载")
.setCancelable(false)
.setView(seekBar)
.create();
}
private void showDownloadDialog() {
this.downloadDialog.show();
}
private void dismissDownloadDialog() {
if (downloadDialog != null) {
downloadDialog.cancel();
downloadDialog.dismiss();
}
}
private void showFinalDialog(String msg) {
finalDownload = new AlertDialog.Builder(mContext)
.setTitle(msg)
.setCancelable(false)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finalDownload.dismiss();
restartActivity();
}
}).create();
finalDownload.show();
}
private void setDownloadProgress(int progress) {
if (seekBar != null) {
seekBar.setProgress(progress);
}
}
// 7.0+动态申请读写权限
private void permissionRequest() {
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
// a thread waiting for check if we got the runTime permission
Thread waitThread = new Thread(new Runnable() {
@SuppressLint("WrongConstant")
@Override
public void run() {
while (threadFlag) {
// -1:no; 0:yes;
if (mContext.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == 0) {
downloadTbs();
threadFlag = false; // 停止循环,待其自己回收
}
}
}
});
waitThread.start();
} else {
downloadTbs();
}
}
/*
when user download x5core or system has checked there is no need to download(TbsListener.onDownloadFinish() returns 110),
we should tell user to restart the app by using this function
*/
private void restartActivity() {
Log.e(TAG, "-------------------------restartActivity: ");
// mContext.startActivity(new Intent(mContext, MainActivity.class));
System.exit(0);
}
// function to set tbs download listener and start to download
private void downloadTbs() {
initViews();
showDownloadDialog();
QbSdk.setTbsListener(new TbsListener() {
@Override
public void onDownloadFinish(int i) {
Log.e(TAG, "-----------------------------onDownloadFinish: " + i);
dismissDownloadDialog();
if (i == 110) {
showFinalDialog("已检测到本地文件,请重启软件");
}
}
@Override
public void onInstallFinish(int i) {
Log.e(TAG, "----------------------------------onInstallFinish: " + i);
if (i == ErrorCode.DOWNLOAD_INSTALL_SUCCESS || i == ErrorCode.COPY_INSTALL_SUCCESS) {
// mContext.startActivity(new Intent(mContext, MainActivity.class));
// DialogUtils.dialogProgressDismiss();
// initQbs();
showFinalDialog("组件安装完成,请重启软件");
} else {
}
}
@Override
public void onDownloadProgress(int i) {
Log.e(TAG, "-------------------------------------onDownloadProgress: " + i);
setDownloadProgress(i);
}
});
TbsDownloader.startDownload(mContext, TbsDownloader.DOWNLOAD_OVERSEA_TBS);
}
private void initX5Webview() {
QbSdk.setDownloadWithoutWifi(true);
AlertDialog dialog = new AlertDialog.Builder(mContext)
.setMessage("初始化中...")
.setCancelable(false)
.create();
dialog.show();
QbSdk.initX5Environment(mContext, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
}
@Override
public void onViewInitFinished(boolean b) {
dialog.dismiss();
if (b) {
Log.e(TAG, "-----------------------------------------onViewInitFinished: success");
} else {
Log.e(TAG, "-----------------------------------------onViewInitFinished: failure");
// 初始化x5Webview失败
boolean ifNeedDownload = TbsDownloader.needDownload(mContext, TbsDownloader.DOWNLOAD_OVERSEA_TBS);
Log.e(TAG, "----------------------------------------ifNeedDownload: " + ifNeedDownload);
permissionRequest();
}
}
});
}
/*
my codes above this line
*/
@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
/*
my codes under this line
*/
initX5Webview();
/*
my codes above this line
*/
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) {
X5WebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
exposeJsInterface(webView, bridge);
//level2: 级别2 系统级设置.....
//level2: 级别2 系统级设置.....
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(cordova.getActivity());
String hostname = preferences.getString("Hostname", "localhost");
String scheme = preferences.getString("Scheme", "http");
CDV_LOCAL_SERVER = scheme + "://" + hostname;
localServer = new X5WebViewLocalServer(cordova.getActivity(), hostname, true, parser, scheme);
localServer.hostAssets("www");
webView.setWebViewClient(new ServerClient(this, parser));
//super.init(parentWebView, cordova, client, resourceApi, pluginManager, nativeToJsMessageQueue);
if (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(X5WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
String path = prefs.getString(X5WebView.CDV_SERVER_PATH, null);
if (!isDeployDisabled() && !isNewBinary() && path != null && !path.isEmpty()) {
setServerBasePath(path);
}
}
/**
* init 使用函数
*
* @return
*/
private boolean isNewBinary() {
String versionCode = "";
String versionName = "";
SharedPreferences prefs = cordova.getActivity().getApplicationContext().getSharedPreferences(X5WebView.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(X5WebView.CDV_SERVER_PATH, "");
editor.apply();
return true;
}
return false;
}
/**
* init 使用函数 。。。。
*
* @param path
*/
public void setServerBasePath(String path) {
localServer.hostFiles(path);
webView.loadUrl(CDV_LOCAL_SERVER);
}
public String getServerBasePath() {
return this.localServer.getBasePath();
}
/**
* init 使用函数 。。。
*
* @return
*/
private boolean isDeployDisabled() {
return preferences.getBoolean("DisableDeploy", false);
}
@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(LayoutAlgorithm.NORMAL);
// Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
try {
Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[]{boolean.class});
String manufacturer = Build.MANUFACTURER;
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
Build.MANUFACTURER.contains("HTC")) {
gingerbread_getMethod.invoke(settings, true);
}
} catch (NoSuchMethodException e) {
Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
} catch (IllegalArgumentException e) {
Log.d(TAG, "Doing the NavDump failed with bad arguments");
} catch (IllegalAccessException e) {
Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
} catch (InvocationTargetException e) {
Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
}
//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
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowUniversalAccessFromFileURLs(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
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 &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
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
}
@TargetApi(Build.VERSION_CODES.KITKAT)
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();
}
}
@SuppressLint("AddJavascriptInterface")
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
}
X5ExposedJsApi exposedJsApi = new X5ExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
//server级webviewclient
private class ServerClient extends X5WebViewClient {
private ConfigXmlParser parser;
public ServerClient(X5WebViewEngine parentEngine, ConfigXmlParser parser) {
super(parentEngine);
this.parser = parser;
}
//@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return localServer.shouldInterceptRequest(request.getUrl(), request);
//return super.shouldInterceptRequest(webView , request);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return localServer.shouldInterceptRequest(Uri.parse(url), null);
//return super.shouldInterceptRequest(webView , url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
String launchUrl = parser.getLaunchUrl();
if (!launchUrl.contains(X5WebViewLocalServer.httpsScheme) && !launchUrl.contains(X5WebViewLocalServer.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 (!CDV_LOCAL_SERVER.startsWith(X5WebViewLocalServer.httpsScheme) && !CDV_LOCAL_SERVER.startsWith(X5WebViewLocalServer.httpScheme)) {
startUrl += "/";
}
view.loadUrl(startUrl);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
/* super.onPageFinished(view, url);
view.loadUrl("javascript:(function() { " +
"window.WEBVIEW_SERVER_URL = '" + CDV_LOCAL_SERVER + "';" +
"})()");*/
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
Log.d("aaa", "bbb");
}
@Override
public void onReceivedError(WebView webView, WebResourceRequest webResourceRequest, WebResourceError webResourceError) {
Log.d("asdf", "asdf");
}
}
@Override
public CordovaWebView getCordovaWebView() {
return parentWebView;
}
@Override
public ICordovaCookieManager getCookieManager() {
return cookieManager;
}
@Override
public View getView() {
return webView;
}
@Override
public void loadUrl(String url, boolean clearNavigationStack) {
webView.loadUrl(url);
}
@Override
public void stopLoading() {
webView.stopLoading();
}
@Override
public String getUrl() {
return webView.getUrl();
}
@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() {
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, android.webkit.ValueCallback<String> callback) {
//@todo 强跳会不会有bug~~
this.evaluateJavascript2(js, (ValueCallback<String>) callback);
}
public void evaluateJavascript2(String js, ValueCallback<String> callback) {
webView.evaluateJavascript(js, callback);
}
}
package org.apache.cordova.x5engine;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Message;
import android.util.Log;
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;
//import android.webkit.WebResourceRequest;
//import android.webkit.WebResourceResponse;
public class X5WebViewLocalServer {
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 UriMatcher uriMatcher;
private AndroidProtocolHandler protocolHandler;
private String authority;
private 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 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;
}
}//class AssetHostingDetials End....
public X5WebViewLocalServer(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;
Log.d("ServerServer" , "I am establishing the server ok....");
}
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,
* android.webkit.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 {
Log.d("SERVER" , "走的是代理");
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("Error", "We shouldn't be here");
}
if (mimeType == null) {
if (path.endsWith(".js")) {
// 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;
}
}
/*
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.
*/
apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
}
}
// Allow plugins to declare Maven dependencies via build-extras.gradle.
allprojects {
repositories {
mavenCentral()
jcenter()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '4.10.3'
}
// Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
// Refer to: http://www.gradle.org/docs/current/userguide/tutorial_this_and_that.html
ext {
apply from: '../CordovaLib/cordova.gradle'
// The value for android.compileSdkVersion.
if (!project.hasProperty('cdvCompileSdkVersion')) {
cdvCompileSdkVersion = null;
}
// The value for android.buildToolsVersion.
if (!project.hasProperty('cdvBuildToolsVersion')) {
cdvBuildToolsVersion = null;
}
// Sets the versionCode to the given value.
if (!project.hasProperty('cdvVersionCode')) {
cdvVersionCode = null
}
// Sets the minSdkVersion to the given value.
if (!project.hasProperty('cdvMinSdkVersion')) {
cdvMinSdkVersion = null
}
// Sets the maxSdkVersion to the given value.
if (!project.hasProperty('cdvMaxSdkVersion')) {
cdvMaxSdkVersion = null
}
// The value for android.targetSdkVersion.
if (!project.hasProperty('cdvTargetSdkVersion')) {
cdvTargetSdkVersion = null;
}
// Whether to build architecture-specific APKs.
if (!project.hasProperty('cdvBuildMultipleApks')) {
cdvBuildMultipleApks = null
}
// Whether to append a 0 "abi digit" to versionCode when only a single APK is build
if (!project.hasProperty('cdvVersionCodeForceAbiDigit')) {
cdvVersionCodeForceAbiDigit = null
}
// .properties files to use for release signing.
if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) {
cdvReleaseSigningPropertiesFile = null
}
// .properties files to use for debug signing.
if (!project.hasProperty('cdvDebugSigningPropertiesFile')) {
cdvDebugSigningPropertiesFile = null
}
// Set by build.js script.
if (!project.hasProperty('cdvBuildArch')) {
cdvBuildArch = null
}
// Plugin gradle extensions can append to this to have code run at the end.
cdvPluginPostBuildExtras = []
}
// PLUGIN GRADLE EXTENSIONS START
// PLUGIN GRADLE EXTENSIONS END
def hasBuildExtras1 = file('build-extras.gradle').exists()
if (hasBuildExtras1) {
apply from: 'build-extras.gradle'
}
def hasBuildExtras2 = file('../build-extras.gradle').exists()
if (hasBuildExtras2) {
apply from: '../build-extras.gradle'
}
// Set property defaults after extension .gradle files.
ext.cdvCompileSdkVersion = cdvCompileSdkVersion == null ? (
defaultCompileSdkVersion == null
? privateHelpers.getProjectTarget()
: defaultCompileSdkVersion
) : Integer.parseInt('' + cdvCompileSdkVersion);
if (ext.cdvBuildToolsVersion == null) {
ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
//ext.cdvBuildToolsVersion = project.ext.defaultBuildToolsVersion
}
if (ext.cdvDebugSigningPropertiesFile == null && file('../debug-signing.properties').exists()) {
ext.cdvDebugSigningPropertiesFile = '../debug-signing.properties'
}
if (ext.cdvReleaseSigningPropertiesFile == null && file('../release-signing.properties').exists()) {
ext.cdvReleaseSigningPropertiesFile = '../release-signing.properties'
}
// Cast to appropriate types.
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
ext.cdvVersionCodeForceAbiDigit = cdvVersionCodeForceAbiDigit == null ? false : cdvVersionCodeForceAbiDigit.toBoolean();
// minSdkVersion, maxSdkVersion and targetSdkVersion
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? defaultMinSdkVersion : Integer.parseInt('' + cdvMinSdkVersion)
if (cdvMaxSdkVersion != null) {
ext.cdvMaxSdkVersion = Integer.parseInt('' + cdvMaxSdkVersion)
}
ext.cdvTargetSdkVersion = cdvTargetSdkVersion == null ? defaultTargetSdkVersion : Integer.parseInt('' + cdvTargetSdkVersion)
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
def computeBuildTargetName(debugBuild) {
def ret = 'assemble'
if (cdvBuildMultipleApks && cdvBuildArch) {
def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch
ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1);
}
return ret + (debugBuild ? 'Debug' : 'Release')
}
// Make cdvBuild a task that depends on the debug/arch-sepecific task.
task cdvBuildDebug
cdvBuildDebug.dependsOn {
return computeBuildTargetName(true)
}
task cdvBuildRelease
cdvBuildRelease.dependsOn {
return computeBuildTargetName(false)
}
task cdvPrintProps {
doLast {
println('cdvCompileSdkVersion=' + cdvCompileSdkVersion)
println('cdvBuildToolsVersion=' + cdvBuildToolsVersion)
println('cdvVersionCode=' + cdvVersionCode)
println('cdvVersionCodeForceAbiDigit=' + cdvVersionCodeForceAbiDigit)
println('cdvMinSdkVersion=' + cdvMinSdkVersion)
println('cdvMaxSdkVersion=' + cdvMaxSdkVersion)
println('cdvTargetSdkVersion=' + cdvTargetSdkVersion)
println('cdvBuildMultipleApks=' + cdvBuildMultipleApks)
println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile)
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
println('cdvBuildArch=' + cdvBuildArch)
println('computedVersionCode=' + android.defaultConfig.versionCode)
android.productFlavors.each { flavor ->
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
}
}
}
android {
defaultConfig {
versionCode cdvVersionCode ?: new BigInteger("" + privateHelpers.extractIntFromManifest("versionCode"))
applicationId privateHelpers.extractStringFromManifest("package")
if (cdvMinSdkVersion != null) {
minSdkVersion cdvMinSdkVersion
}
if (cdvMaxSdkVersion != null) {
maxSdkVersion cdvMaxSdkVersion
}
if (cdvTargetSdkVersion != null) {
targetSdkVersion cdvTargetSdkVersion
}
ndk { abiFilters "armeabi" }
}
lintOptions {
abortOnError false;
}
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion
// This code exists for Crosswalk and other Native APIs.
// By default, we multiply the existing version code in the
// Android Manifest by 10 and add a number for each architecture.
// If you are not using Crosswalk or SQLite, you can
// ignore this chunk of code, and your version codes will be respected.
if (Boolean.valueOf(cdvBuildMultipleApks)) {
flavorDimensions "default"
productFlavors {
armeabi {
versionCode defaultConfig.versionCode * 10 + 1
ndk {
abiFilters = ["armeabi"]
}
}
armv7 {
versionCode defaultConfig.versionCode * 10 + 2
ndk {
abiFilters = ["armeabi-v7a"]
}
}
arm64 {
versionCode defaultConfig.versionCode * 10 + 3
ndk {
abiFilters = ["arm64-v8a"]
}
}
x86 {
versionCode defaultConfig.versionCode * 10 + 4
ndk {
abiFilters = ["x86"]
}
}
x86_64 {
versionCode defaultConfig.versionCode * 10 + 5
ndk {
abiFilters = ["x86_64"]
}
}
}
} else if (Boolean.valueOf(cdvVersionCodeForceAbiDigit)) {
// This provides compatibility to the default logic for versionCode before cordova-android 5.2.0
defaultConfig {
versionCode defaultConfig.versionCode * 10
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
if (cdvReleaseSigningPropertiesFile) {
signingConfigs {
release {
// These must be set or Gradle will complain (even if they are overridden).
keyAlias = ""
keyPassword = "__unset"
// And these must be set to non-empty in order to have the signing step added to the task graph.
storeFile = null
storePassword = "__unset"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
addSigningProps(cdvReleaseSigningPropertiesFile, signingConfigs.release)
}
if (cdvDebugSigningPropertiesFile) {
addSigningProps(cdvDebugSigningPropertiesFile, signingConfigs.debug)
}
}
/*
* WARNING: Cordova Lib and platform scripts do management inside of this code here,
* if you are adding the dependencies manually, do so outside the comments, otherwise
* the Cordova tools will overwrite them
*/
dependencies {
// implementation fileTree(dir: 'libs', include: '*.jar')
implementation fileTree(include: '*.jar', dir: 'libs')
// implementation fileTree(include: '*.jar', dir: 'jniLibs')
// SUB-PROJECT DEPENDENCIES START
implementation project(path: ':CordovaLib')
// SUB-PROJECT DEPENDENCIES END
implementation files('libs/tbs_sdk_thirdapp_v4.3.0.1148_43697_sharewithdownloadwithfile_withoutGame_obfs_20190805_175505.jar')
// implementation 'com.android.support:support-compat:28.0.0'
}
def promptForReleaseKeyPassword() {
if (!cdvReleaseSigningPropertiesFile) {
return;
}
if ('__unset'.equals(android.signingConfigs.release.storePassword)) {
android.signingConfigs.release.storePassword = privateHelpers.promptForPassword('Enter key store password: ')
}
if ('__unset'.equals(android.signingConfigs.release.keyPassword)) {
android.signingConfigs.release.keyPassword = privateHelpers.promptForPassword('Enter key password: ');
}
}
gradle.taskGraph.whenReady { taskGraph ->
taskGraph.getAllTasks().each() { task ->
if (['validateReleaseSigning', 'validateSigningRelease', 'validateSigningArmv7Release', 'validateSigningX76Release'].contains(task.name)) {
promptForReleaseKeyPassword()
}
}
}
def addSigningProps(propsFilePath, signingConfig) {
def propsFile = file(propsFilePath)
def props = new Properties()
propsFile.withReader { reader ->
props.load(reader)
}
def storeFile = new File(props.get('key.store') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'storeFile'))
if (!storeFile.isAbsolute()) {
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
}
if (!storeFile.exists()) {
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
}
signingConfig.keyAlias = props.get('key.alias') ?: privateHelpers.ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword', props.get('key.alias.password', signingConfig.keyPassword))
signingConfig.storeFile = storeFile
signingConfig.storePassword = props.get('storePassword', props.get('key.store.password', signingConfig.storePassword))
def storeType = props.get('storeType', props.get('key.store.type', ''))
if (!storeType) {
def filename = storeFile.getName().toLowerCase();
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
storeType = 'pkcs12'
} else {
storeType = signingConfig.storeType // "jks"
}
}
signingConfig.storeType = storeType
}
for (def func : cdvPluginPostBuildExtras) {
func()
}
// This can be defined within build-extras.gradle as:
// ext.postBuildExtras = { ... code here ... }
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-x5engine-webview"
version="1.0.0">
<name>x5engine WebView Engine</name>
<description>Changes the default WebView to x5engine</description>
<license>Apache 2.0</license>
<keywords>cordova,chromium,x5engine,webview</keywords>
<repo>https://github.com/offbye/cordova-plugin-x5engine-webview</repo>
<issue>https://github.com/offbye/cordova-plugin-x5engine-webview</issue>
<engines>
<engine name="cordova-android" version=">=4"/>
<engine name="cordova-plugman" version=">=5.2.0"/><!-- needed for gradleReference support -->
</engines>
<!-- android -->
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<preference name="webView" value="org.apache.cordova.x5engine.X5WebViewEngine"/>
<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="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>
<info>
</info>
</plugin>
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