mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-22 00:32:55 +08:00
CB-8510 Create a new abstraction for sharing common logic of WebView engines
Having CordovaWebViewImpl separate from CordovaWebViewEngine is helpful because now each webview doesn't have to re-implement non-webview-specific featrues. e.g.: 1. load timeout 2. keyboard events 3. showCustomView 4. lifecycle events Moved AndroidWebView into its own package to ensure that it doesn't use any package-private symbols (since plugins cannot use them).
This commit is contained in:
parent
00c0a84e4e
commit
087ec11e6a
@ -1,791 +0,0 @@
|
|||||||
/*
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.webkit.WebBackForwardList;
|
|
||||||
import android.webkit.WebHistoryItem;
|
|
||||||
import android.webkit.WebChromeClient;
|
|
||||||
import android.webkit.WebSettings;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.WebSettings.LayoutAlgorithm;
|
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This class is our web view.
|
|
||||||
*
|
|
||||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
|
||||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
|
|
||||||
*/
|
|
||||||
public class AndroidWebView extends WebView implements CordovaWebView {
|
|
||||||
|
|
||||||
public static final String TAG = "AndroidWebView";
|
|
||||||
|
|
||||||
private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
|
|
||||||
|
|
||||||
PluginManager pluginManager;
|
|
||||||
AndroidCookieManager cookieManager;
|
|
||||||
|
|
||||||
private BroadcastReceiver receiver;
|
|
||||||
|
|
||||||
|
|
||||||
/** Activities and other important classes **/
|
|
||||||
private CordovaInterface cordova;
|
|
||||||
AndroidWebViewClient viewClient;
|
|
||||||
private AndroidChromeClient chromeClient;
|
|
||||||
|
|
||||||
// Flag to track that a loadUrl timeout occurred
|
|
||||||
int loadUrlTimeout = 0;
|
|
||||||
|
|
||||||
private long lastMenuEventTime = 0;
|
|
||||||
|
|
||||||
private NativeToJsMessageQueue nativeToJsMessageQueue;
|
|
||||||
CordovaBridge bridge;
|
|
||||||
|
|
||||||
/** custom view created by the browser (a video player for example) */
|
|
||||||
private View mCustomView;
|
|
||||||
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
|
||||||
|
|
||||||
private CordovaResourceApi resourceApi;
|
|
||||||
private CordovaPreferences preferences;
|
|
||||||
private CoreAndroid appPlugin;
|
|
||||||
private CordovaUriHelper helper;
|
|
||||||
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
|
||||||
String loadedUrl;
|
|
||||||
|
|
||||||
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
|
|
||||||
new FrameLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
Gravity.CENTER);
|
|
||||||
|
|
||||||
/** Used when created via reflection. */
|
|
||||||
public AndroidWebView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Required to allow view to be used within XML layouts. */
|
|
||||||
public AndroidWebView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use two-phase init so that the control will work with XML layouts.
|
|
||||||
@Override
|
|
||||||
public void init(final CordovaInterface cordova, List<PluginEntry> pluginEntries,
|
|
||||||
CordovaPreferences preferences) {
|
|
||||||
if (this.cordova != null) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
this.cordova = cordova;
|
|
||||||
this.preferences = preferences;
|
|
||||||
this.helper = new CordovaUriHelper(cordova, this);
|
|
||||||
|
|
||||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
|
||||||
cookieManager = new AndroidCookieManager(this);
|
|
||||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
|
||||||
nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
|
||||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
|
||||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(this, cordova));
|
|
||||||
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
|
|
||||||
@Override
|
|
||||||
public void setNetworkAvailable(boolean value) {
|
|
||||||
AndroidWebView.this.setNetworkAvailable(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runOnUiThread(Runnable r) {
|
|
||||||
cordova.getActivity().runOnUiThread(r);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
|
|
||||||
initWebViewSettings();
|
|
||||||
pluginManager.addService(CoreAndroid.PLUGIN_NAME, CoreAndroid.class.getCanonicalName());
|
|
||||||
pluginManager.init();
|
|
||||||
|
|
||||||
if (this.viewClient == null) {
|
|
||||||
setWebViewClient(new AndroidWebViewClient(cordova, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.chromeClient == null) {
|
|
||||||
setWebChromeClient(new AndroidChromeClient(cordova, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
exposeJsInterface();
|
|
||||||
|
|
||||||
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
|
||||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private void initWebViewSettings() {
|
|
||||||
this.setInitialScale(0);
|
|
||||||
this.setVerticalScrollBarEnabled(false);
|
|
||||||
final WebSettings settings = this.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 = android.os.Build.MANUFACTURER;
|
|
||||||
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
|
||||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
|
|
||||||
android.os.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 (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
|
||||||
Level16Apis.enableUniversalAccess(settings);
|
|
||||||
}
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
Level17Apis.setMediaPlaybackRequiresUserGesture(settings, false);
|
|
||||||
}
|
|
||||||
// Enable database
|
|
||||||
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
|
|
||||||
String databasePath = 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 = getContext().getApplicationContext().getApplicationInfo();
|
|
||||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&
|
|
||||||
android.os.Build.VERSION.SDK_INT >= android.os.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
|
|
||||||
settings.getUserAgentString();
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exposeJsInterface() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
AndroidExposedJsApi exposedJsApi = new AndroidExposedJsApi(bridge);
|
|
||||||
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWebViewClient(WebViewClient client) {
|
|
||||||
this.viewClient = (AndroidWebViewClient)client;
|
|
||||||
super.setWebViewClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWebChromeClient(WebChromeClient client) {
|
|
||||||
this.chromeClient = (AndroidChromeClient)client;
|
|
||||||
super.setWebChromeClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the url into the webview.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void loadUrl(String url) {
|
|
||||||
this.loadUrlIntoView(url, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the url into the webview.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
|
||||||
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
|
||||||
this.loadUrlNow(url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
|
||||||
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
|
||||||
|
|
||||||
if (recreatePlugins) {
|
|
||||||
// Don't re-initialize on first load.
|
|
||||||
if (loadedUrl != null) {
|
|
||||||
this.pluginManager.init();
|
|
||||||
}
|
|
||||||
this.loadedUrl = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a timeout timer for loadUrl
|
|
||||||
final AndroidWebView me = this;
|
|
||||||
final int currentLoadUrlTimeout = me.loadUrlTimeout;
|
|
||||||
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
|
||||||
|
|
||||||
// Timeout error method
|
|
||||||
final Runnable loadError = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
me.stopLoading();
|
|
||||||
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
|
||||||
if (viewClient != null) {
|
|
||||||
viewClient.onReceivedError(AndroidWebView.this, -6, "The connection to the server was unsuccessful.", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Timeout timer method
|
|
||||||
final Runnable timeoutCheck = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
synchronized (this) {
|
|
||||||
wait(loadUrlTimeoutValue);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If timeout, then stop loading and handle error
|
|
||||||
if (me.loadUrlTimeout == currentLoadUrlTimeout) {
|
|
||||||
me.cordova.getActivity().runOnUiThread(loadError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load url
|
|
||||||
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
cordova.getThreadPool().execute(timeoutCheck);
|
|
||||||
me.loadUrlNow(url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load URL in webview.
|
|
||||||
*/
|
|
||||||
private void loadUrlNow(String url) {
|
|
||||||
if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
|
|
||||||
LOG.d(TAG, ">>> loadUrlNow()");
|
|
||||||
}
|
|
||||||
if (url.startsWith("javascript:") || pluginManager.shouldAllowNavigation(url)) {
|
|
||||||
super.loadUrl(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopLoading() {
|
|
||||||
//viewClient.isCurrentlyLoading = false;
|
|
||||||
super.stopLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onScrollChanged(int l, int t, int oldl, int oldt)
|
|
||||||
{
|
|
||||||
super.onScrollChanged(l, t, oldl, oldt);
|
|
||||||
//We should post a message that the scroll changed
|
|
||||||
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
|
|
||||||
pluginManager.postMessage("onScrollChanged", myEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send JavaScript statement back to JavaScript.
|
|
||||||
* (This is a convenience method)
|
|
||||||
*/
|
|
||||||
public void sendJavascript(String statement) {
|
|
||||||
nativeToJsMessageQueue.addJavaScript(statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a plugin result back to JavaScript.
|
|
||||||
*/
|
|
||||||
public void sendPluginResult(PluginResult result, String callbackId) {
|
|
||||||
nativeToJsMessageQueue.addPluginResult(result, callbackId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to previous page in history. (We manage our own history)
|
|
||||||
*
|
|
||||||
* @return true if we went back, false if we are already at top
|
|
||||||
*/
|
|
||||||
public boolean backHistory() {
|
|
||||||
|
|
||||||
// Check webview first to see if there is a history
|
|
||||||
// This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
|
|
||||||
if (super.canGoBack()) {
|
|
||||||
printBackForwardList();
|
|
||||||
super.goBack();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the specified URL in the Cordova webview or a new browser instance.
|
|
||||||
*
|
|
||||||
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
|
|
||||||
*
|
|
||||||
* @param url The url to load.
|
|
||||||
* @param openExternal Load url in browser instead of Cordova webview.
|
|
||||||
* @param clearHistory Clear the history stack, so new page becomes top of history
|
|
||||||
* @param params Parameters for new app
|
|
||||||
*/
|
|
||||||
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
|
|
||||||
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
|
|
||||||
|
|
||||||
// If clearing history
|
|
||||||
if (clearHistory) {
|
|
||||||
this.clearHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If loading into our webview
|
|
||||||
if (!openExternal) {
|
|
||||||
|
|
||||||
// Make sure url is in whitelist
|
|
||||||
if (pluginManager.shouldAllowNavigation(url)) {
|
|
||||||
// TODO: What about params?
|
|
||||||
// Load new URL
|
|
||||||
loadUrlIntoView(url, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Load in default viewer if not
|
|
||||||
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
|
||||||
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
Uri uri = Uri.parse(url);
|
|
||||||
if ("file".equals(uri.getScheme())) {
|
|
||||||
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
|
||||||
} else {
|
|
||||||
intent.setData(uri);
|
|
||||||
}
|
|
||||||
cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error loading url " + url, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* onKeyDown
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
|
||||||
{
|
|
||||||
if(boundKeyCodes.contains(keyCode))
|
|
||||||
{
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
|
||||||
sendJavascriptEvent("volumedownbutton");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
|
||||||
sendJavascriptEvent("volumeupbutton");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(keyCode == KeyEvent.KEYCODE_BACK)
|
|
||||||
{
|
|
||||||
return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(keyCode == KeyEvent.KEYCODE_MENU)
|
|
||||||
{
|
|
||||||
//How did we get here? Is there a childView?
|
|
||||||
View childView = this.getFocusedChild();
|
|
||||||
if(childView != null)
|
|
||||||
{
|
|
||||||
//Make sure we close the keyboard if it's present
|
|
||||||
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
|
|
||||||
cordova.getActivity().openOptionsMenu();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event)
|
|
||||||
{
|
|
||||||
// If back key
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
// A custom view is currently displayed (e.g. playing a video)
|
|
||||||
if(mCustomView != null) {
|
|
||||||
this.hideCustomView();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// The webview is currently displayed
|
|
||||||
// If back key is bound, then send event to JavaScript
|
|
||||||
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
|
|
||||||
sendJavascriptEvent("backbutton");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// If not bound
|
|
||||||
// Go to previous page in webview if it is possible to go back
|
|
||||||
if (this.backHistory()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If not, then invoke default behavior
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Legacy
|
|
||||||
else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
|
||||||
if (this.lastMenuEventTime < event.getEventTime()) {
|
|
||||||
sendJavascriptEvent("menubutton");
|
|
||||||
}
|
|
||||||
this.lastMenuEventTime = event.getEventTime();
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
// If search key
|
|
||||||
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
|
||||||
sendJavascriptEvent("searchbutton");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Does webkit change this behavior?
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendJavascriptEvent(String event) {
|
|
||||||
if (appPlugin == null) {
|
|
||||||
appPlugin = (CoreAndroid)this.pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appPlugin == null) {
|
|
||||||
LOG.w(TAG, "Unable to fire event without existing plugin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
appPlugin.fireJavascriptEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
|
||||||
switch (keyCode) {
|
|
||||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
||||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
|
||||||
case KeyEvent.KEYCODE_BACK:
|
|
||||||
// TODO: Why are search and menu buttons handled separately?
|
|
||||||
if (override) {
|
|
||||||
boundKeyCodes.add(keyCode);
|
|
||||||
} else {
|
|
||||||
boundKeyCodes.remove(keyCode);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isButtonPlumbedToJs(int keyCode)
|
|
||||||
{
|
|
||||||
return boundKeyCodes.contains(keyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePause(boolean keepRunning)
|
|
||||||
{
|
|
||||||
LOG.d(TAG, "Handle the pause");
|
|
||||||
// Send pause event to JavaScript
|
|
||||||
sendJavascriptEvent("pause");
|
|
||||||
|
|
||||||
// Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onPause(keepRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If app doesn't want to run in background
|
|
||||||
if (!keepRunning) {
|
|
||||||
// Pause JavaScript timers. This affects all webviews within the app!
|
|
||||||
this.pauseTimers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleResume(boolean keepRunning)
|
|
||||||
{
|
|
||||||
// Resume JavaScript timers. This affects all webviews within the app!
|
|
||||||
this.resumeTimers();
|
|
||||||
|
|
||||||
sendJavascriptEvent("resume");
|
|
||||||
|
|
||||||
// Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onResume(keepRunning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleDestroy()
|
|
||||||
{
|
|
||||||
// Cancel pending timeout timer.
|
|
||||||
loadUrlTimeout++;
|
|
||||||
|
|
||||||
// Load blank page so that JavaScript onunload is called
|
|
||||||
this.loadUrl("about:blank");
|
|
||||||
|
|
||||||
//Remove last AlertDialog
|
|
||||||
this.chromeClient.destroyLastDialog();
|
|
||||||
|
|
||||||
// Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// unregister the receiver
|
|
||||||
if (this.receiver != null) {
|
|
||||||
try {
|
|
||||||
getContext().unregisterReceiver(this.receiver);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNewIntent(Intent intent)
|
|
||||||
{
|
|
||||||
//Forward to plugins
|
|
||||||
if (this.pluginManager != null) {
|
|
||||||
this.pluginManager.onNewIntent(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapping these functions in their own class prevents warnings in adb like:
|
|
||||||
// VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
|
|
||||||
@TargetApi(16)
|
|
||||||
private static class Level16Apis {
|
|
||||||
static void enableUniversalAccess(WebSettings settings) {
|
|
||||||
settings.setAllowUniversalAccessFromFileURLs(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(17)
|
|
||||||
private static final class Level17Apis {
|
|
||||||
static void setMediaPlaybackRequiresUserGesture(WebSettings settings, boolean value) {
|
|
||||||
settings.setMediaPlaybackRequiresUserGesture(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void printBackForwardList() {
|
|
||||||
WebBackForwardList currentList = this.copyBackForwardList();
|
|
||||||
int currentSize = currentList.getSize();
|
|
||||||
for(int i = 0; i < currentSize; ++i)
|
|
||||||
{
|
|
||||||
WebHistoryItem item = currentList.getItemAtIndex(i);
|
|
||||||
String url = item.getUrl();
|
|
||||||
LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Can Go Back is BROKEN!
|
|
||||||
public boolean startOfHistory()
|
|
||||||
{
|
|
||||||
WebBackForwardList currentList = this.copyBackForwardList();
|
|
||||||
WebHistoryItem item = currentList.getItemAtIndex(0);
|
|
||||||
if( item!=null){ // Null-fence in case they haven't called loadUrl yet (CB-2458)
|
|
||||||
String url = item.getUrl();
|
|
||||||
String currentUrl = this.getUrl();
|
|
||||||
LOG.d(TAG, "The current URL is: " + currentUrl);
|
|
||||||
LOG.d(TAG, "The URL at item 0 is: " + url);
|
|
||||||
return currentUrl.equals(url);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
|
||||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
|
||||||
Log.d(TAG, "showing Custom View");
|
|
||||||
// if a view already exists then immediately terminate the new one
|
|
||||||
if (mCustomView != null) {
|
|
||||||
callback.onCustomViewHidden();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the view and its callback for later (to kill it properly)
|
|
||||||
mCustomView = view;
|
|
||||||
mCustomViewCallback = callback;
|
|
||||||
|
|
||||||
// Add the custom view to its container.
|
|
||||||
ViewGroup parent = (ViewGroup) this.getParent();
|
|
||||||
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
|
|
||||||
|
|
||||||
// Hide the content view.
|
|
||||||
this.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// Finally show the custom view container.
|
|
||||||
parent.setVisibility(View.VISIBLE);
|
|
||||||
parent.bringToFront();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideCustomView() {
|
|
||||||
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
|
||||||
Log.d(TAG, "Hiding Custom View");
|
|
||||||
if (mCustomView == null) return;
|
|
||||||
|
|
||||||
// Hide the custom view.
|
|
||||||
mCustomView.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// Remove the custom view from its container.
|
|
||||||
ViewGroup parent = (ViewGroup) this.getParent();
|
|
||||||
parent.removeView(mCustomView);
|
|
||||||
mCustomView = null;
|
|
||||||
mCustomViewCallback.onCustomViewHidden();
|
|
||||||
|
|
||||||
// Show the content view.
|
|
||||||
this.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if the video overlay is showing then we need to know
|
|
||||||
* as it effects back button handling
|
|
||||||
*
|
|
||||||
* @return true if custom view is showing
|
|
||||||
*/
|
|
||||||
public boolean isCustomViewShowing() {
|
|
||||||
return mCustomView != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebBackForwardList restoreState(Bundle savedInstanceState)
|
|
||||||
{
|
|
||||||
WebBackForwardList myList = super.restoreState(savedInstanceState);
|
|
||||||
Log.d(TAG, "WebView restoration crew now restoring!");
|
|
||||||
//Initialize the plugin manager once more
|
|
||||||
this.pluginManager.init();
|
|
||||||
return myList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CordovaResourceApi getResourceApi() {
|
|
||||||
return resourceApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onPageReset() {
|
|
||||||
boundKeyCodes.clear();
|
|
||||||
pluginManager.onReset();
|
|
||||||
bridge.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PluginManager getPluginManager() {
|
|
||||||
return this.pluginManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CordovaPreferences getPreferences() {
|
|
||||||
return preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ICordovaCookieManager getCookieManager() {
|
|
||||||
return cookieManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object postMessage(String id, Object data) {
|
|
||||||
return pluginManager.postMessage(id, data);
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,16 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@ -97,7 +96,6 @@ public class CordovaActivity extends Activity {
|
|||||||
protected ArrayList<PluginEntry> pluginEntries;
|
protected ArrayList<PluginEntry> pluginEntries;
|
||||||
protected CordovaInterfaceImpl cordovaInterface;
|
protected CordovaInterfaceImpl cordovaInterface;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is first created.
|
* Called when the activity is first created.
|
||||||
*/
|
*/
|
||||||
@ -138,8 +136,9 @@ public class CordovaActivity extends Activity {
|
|||||||
protected void init() {
|
protected void init() {
|
||||||
appView = makeWebView();
|
appView = makeWebView();
|
||||||
createViews();
|
createViews();
|
||||||
//TODO: Add null check against CordovaInterfaceImpl, since this can be fragile
|
if (!appView.isInitialized()) {
|
||||||
appView.init(cordovaInterface, pluginEntries, preferences);
|
appView.init(cordovaInterface, pluginEntries, preferences);
|
||||||
|
}
|
||||||
cordovaInterface.setPluginManager(appView.getPluginManager());
|
cordovaInterface.setPluginManager(appView.getPluginManager());
|
||||||
|
|
||||||
// Wire the hardware volume controls to control media if desired.
|
// Wire the hardware volume controls to control media if desired.
|
||||||
@ -198,16 +197,12 @@ public class CordovaActivity extends Activity {
|
|||||||
* Override this to customize the webview that is used.
|
* Override this to customize the webview that is used.
|
||||||
*/
|
*/
|
||||||
protected CordovaWebView makeWebView() {
|
protected CordovaWebView makeWebView() {
|
||||||
String webViewClassName = preferences.getString("webView", AndroidWebView.class.getCanonicalName());
|
return new CordovaWebViewImpl(this, makeWebViewEngine());
|
||||||
CordovaWebView ret;
|
}
|
||||||
try {
|
|
||||||
Class<?> webViewClass = Class.forName(webViewClassName);
|
protected CordovaWebViewEngine makeWebViewEngine() {
|
||||||
Constructor<?> constructor = webViewClass.getConstructor(Context.class);
|
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
|
||||||
ret = (CordovaWebView) constructor.newInstance((Context)this);
|
return CordovaWebViewImpl.createEngine(className, this, preferences);
|
||||||
return ret;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to create webview. ", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CordovaInterfaceImpl makeCordovaInterface() {
|
protected CordovaInterfaceImpl makeCordovaInterface() {
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
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;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
public class CordovaUriHelper {
|
|
||||||
|
|
||||||
private static final String TAG = "CordovaUriHelper";
|
|
||||||
|
|
||||||
private CordovaWebView appView;
|
|
||||||
private CordovaInterface cordova;
|
|
||||||
|
|
||||||
public CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
|
|
||||||
{
|
|
||||||
appView = webView;
|
|
||||||
cordova = cdv;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Give the host application a chance to take over the control when a new url
|
|
||||||
* is about to be loaded in the current WebView.
|
|
||||||
*
|
|
||||||
* This method implements the default whitelist policy when no plugins override
|
|
||||||
* the whitelist methods:
|
|
||||||
* Internal urls on file:// or data:// that do not contain "app_webview" are allowed for navigation
|
|
||||||
* External urls are not allowed.
|
|
||||||
*
|
|
||||||
* @param url The url to be loaded.
|
|
||||||
* @return true to override, false for default behavior
|
|
||||||
*/
|
|
||||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
|
||||||
public boolean shouldOverrideUrlLoading(String url) {
|
|
||||||
// Give plugins the chance to handle the url
|
|
||||||
if (appView.getPluginManager().shouldAllowNavigation(url)) {
|
|
||||||
// Allow internal navigation
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (appView.getPluginManager().shouldOpenExternalUrl(url)) {
|
|
||||||
// Do nothing other than what the plugins wanted.
|
|
||||||
// If any returned false, then the request was either blocked
|
|
||||||
// completely, or handled out-of-band by the plugin. If they all
|
|
||||||
// returned true, then we should open the URL here.
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
|
||||||
intent.setComponent(null);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
|
||||||
intent.setSelector(null);
|
|
||||||
}
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
return true;
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
Log.e(TAG, "Error loading url " + url, e);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Block by default
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,19 +16,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
|
||||||
|
* This is an interface so that it can be easily mocked in tests.
|
||||||
|
* Methods may be added to this interface without a major version bump, as plugins & embedders
|
||||||
|
* are not expected to implement it.
|
||||||
|
*/
|
||||||
public interface CordovaWebView {
|
public interface CordovaWebView {
|
||||||
public static final String CORDOVA_VERSION = "4.0.0-dev";
|
public static final String CORDOVA_VERSION = "4.0.0-dev";
|
||||||
|
|
||||||
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
|
||||||
|
|
||||||
|
boolean isInitialized();
|
||||||
|
|
||||||
View getView();
|
View getView();
|
||||||
|
|
||||||
void loadUrlIntoView(String url, boolean recreatePlugins);
|
void loadUrlIntoView(String url, boolean recreatePlugins);
|
||||||
@ -37,6 +45,10 @@ public interface CordovaWebView {
|
|||||||
|
|
||||||
boolean canGoBack();
|
boolean canGoBack();
|
||||||
|
|
||||||
|
void clearCache();
|
||||||
|
|
||||||
|
/** Use parameter-less overload */
|
||||||
|
@Deprecated
|
||||||
void clearCache(boolean b);
|
void clearCache(boolean b);
|
||||||
|
|
||||||
void clearHistory();
|
void clearHistory();
|
||||||
@ -75,7 +87,17 @@ public interface CordovaWebView {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
void sendJavascript(String statememt);
|
void sendJavascript(String statememt);
|
||||||
|
|
||||||
void showWebPage(String errorUrl, boolean b, boolean c, Map<String, Object> params);
|
/**
|
||||||
|
* Load the specified URL in the Cordova webview or a new browser instance.
|
||||||
|
*
|
||||||
|
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
|
||||||
|
*
|
||||||
|
* @param url The url to load.
|
||||||
|
* @param openExternal Load url in browser instead of Cordova webview.
|
||||||
|
* @param clearHistory Clear the history stack, so new page becomes top of history
|
||||||
|
* @param params Parameters for new app
|
||||||
|
*/
|
||||||
|
void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
* Deprecated in 4.0.0. Use your own View-toggling logic.
|
||||||
@ -103,12 +125,10 @@ public interface CordovaWebView {
|
|||||||
void sendPluginResult(PluginResult cr, String callbackId);
|
void sendPluginResult(PluginResult cr, String callbackId);
|
||||||
|
|
||||||
PluginManager getPluginManager();
|
PluginManager getPluginManager();
|
||||||
|
CordovaWebViewEngine getEngine();
|
||||||
CordovaPreferences getPreferences();
|
CordovaPreferences getPreferences();
|
||||||
ICordovaCookieManager getCookieManager();
|
ICordovaCookieManager getCookieManager();
|
||||||
|
|
||||||
void setNetworkAvailable(boolean online);
|
|
||||||
|
|
||||||
String getUrl();
|
String getUrl();
|
||||||
|
|
||||||
// TODO: Work on deleting these by removing refs from plugins.
|
// TODO: Work on deleting these by removing refs from plugins.
|
||||||
|
82
framework/src/org/apache/cordova/CordovaWebViewEngine.java
Normal file
82
framework/src/org/apache/cordova/CordovaWebViewEngine.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interfcae for all Cordova engines.
|
||||||
|
* No methods will be added to this class (in order to be compatible with existing engines).
|
||||||
|
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
|
||||||
|
*/
|
||||||
|
public interface CordovaWebViewEngine {
|
||||||
|
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
|
||||||
|
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||||
|
NativeToJsMessageQueue nativeToJsMessageQueue);
|
||||||
|
|
||||||
|
CordovaWebView getCordovaWebView();
|
||||||
|
ICordovaCookieManager getCookieManager();
|
||||||
|
View getView();
|
||||||
|
|
||||||
|
void loadUrl(String url, boolean clearNavigationStack);
|
||||||
|
|
||||||
|
void stopLoading();
|
||||||
|
|
||||||
|
/** Return the currently loaded URL */
|
||||||
|
String getUrl();
|
||||||
|
|
||||||
|
void clearCache();
|
||||||
|
|
||||||
|
/** After calling clearHistory(), canGoBack() should be false. */
|
||||||
|
void clearHistory();
|
||||||
|
|
||||||
|
boolean canGoBack();
|
||||||
|
|
||||||
|
/** Returns whether a navigation occurred */
|
||||||
|
boolean goBack();
|
||||||
|
|
||||||
|
/** Pauses / resumes the WebView's event loop. */
|
||||||
|
void setPaused(boolean value);
|
||||||
|
|
||||||
|
/** Clean up all resources associated with the WebView. */
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
|
||||||
|
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
|
||||||
|
*/
|
||||||
|
public interface EngineView {
|
||||||
|
CordovaWebView getCordovaWebView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
|
||||||
|
* Methods may be added in future cordova versions, but never removed.
|
||||||
|
*/
|
||||||
|
public interface Client {
|
||||||
|
Boolean onKeyDown(int keyCode, KeyEvent event);
|
||||||
|
Boolean onKeyUp(int keyCode, KeyEvent event);
|
||||||
|
boolean shouldOverrideUrlLoading(String url);
|
||||||
|
void clearLoadTimeoutTimer();
|
||||||
|
void onPageStarted(String newUrl);
|
||||||
|
void onReceivedError(int errorCode, String description, String failingUrl);
|
||||||
|
void onPageFinishedLoading(String url);
|
||||||
|
}
|
||||||
|
}
|
645
framework/src/org/apache/cordova/CordovaWebViewImpl.java
Normal file
645
framework/src/org/apache/cordova/CordovaWebViewImpl.java
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine.
|
||||||
|
* Class uses two-phase initialization. You must call init() before calling any other methods.
|
||||||
|
*/
|
||||||
|
public class CordovaWebViewImpl implements CordovaWebView {
|
||||||
|
|
||||||
|
public static final String TAG = "CordovaWebViewImpl";
|
||||||
|
|
||||||
|
// Public for backwards-compatibility :(
|
||||||
|
public PluginManager pluginManager;
|
||||||
|
|
||||||
|
protected CordovaWebViewEngine engine;
|
||||||
|
private CordovaInterface cordova;
|
||||||
|
|
||||||
|
// Flag to track that a loadUrl timeout occurred
|
||||||
|
private int loadUrlTimeout = 0;
|
||||||
|
|
||||||
|
private CordovaResourceApi resourceApi;
|
||||||
|
private CordovaPreferences preferences;
|
||||||
|
private CoreAndroid appPlugin;
|
||||||
|
private NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||||
|
private EngineClient engineClient = new EngineClient();
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
// The URL passed to loadUrl(), not necessarily the URL of the current page.
|
||||||
|
String loadedUrl;
|
||||||
|
|
||||||
|
/** custom view created by the browser (a video player for example) */
|
||||||
|
private View mCustomView;
|
||||||
|
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||||
|
|
||||||
|
private Set<Integer> boundKeyCodes = new HashSet<Integer>();
|
||||||
|
|
||||||
|
public static CordovaWebViewEngine createEngine(String className, Context context, CordovaPreferences preferences) {
|
||||||
|
try {
|
||||||
|
Class<?> webViewClass = Class.forName(className);
|
||||||
|
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
|
||||||
|
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create webview. ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CordovaWebViewImpl(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
public CordovaWebViewImpl(Context context, CordovaWebViewEngine cordovaWebViewEngine) {
|
||||||
|
this.context = context;
|
||||||
|
this.engine = cordovaWebViewEngine;
|
||||||
|
}
|
||||||
|
// Convenience method for when creating programmatically (not from Config.xml).
|
||||||
|
public void init(CordovaInterface cordova) {
|
||||||
|
init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
|
||||||
|
if (this.cordova != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
// Happens only when not using CordovaActivity. Usually, engine is set in the constructor.
|
||||||
|
if (engine == null) {
|
||||||
|
String className = preferences.getString("webView", SystemWebViewEngine.class.getCanonicalName());
|
||||||
|
engine = createEngine(className, context, preferences);
|
||||||
|
}
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.preferences = preferences;
|
||||||
|
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||||
|
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
|
||||||
|
nativeToJsMessageQueue = new NativeToJsMessageQueue();
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
|
||||||
|
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
|
||||||
|
|
||||||
|
if (preferences.getBoolean("DisallowOverscroll", false)) {
|
||||||
|
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
|
}
|
||||||
|
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
|
||||||
|
// This isn't enforced by the compiler, so assert here.
|
||||||
|
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
|
||||||
|
|
||||||
|
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
|
||||||
|
pluginManager.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return cordova != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUrlIntoView(final String url, boolean recreatePlugins) {
|
||||||
|
LOG.d(TAG, ">>> loadUrl(" + url + ")");
|
||||||
|
if (url.equals("about:blank") || url.startsWith("javascript:")) {
|
||||||
|
engine.loadUrl(url, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recreatePlugins = recreatePlugins || (loadedUrl == null);
|
||||||
|
|
||||||
|
if (recreatePlugins) {
|
||||||
|
// Don't re-initialize on first load.
|
||||||
|
if (loadedUrl != null) {
|
||||||
|
pluginManager.init();
|
||||||
|
}
|
||||||
|
loadedUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a timeout timer for loadUrl
|
||||||
|
final int currentLoadUrlTimeout = loadUrlTimeout;
|
||||||
|
final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000);
|
||||||
|
|
||||||
|
// Timeout error method
|
||||||
|
final Runnable loadError = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
stopLoading();
|
||||||
|
LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
|
||||||
|
|
||||||
|
// Handle other errors by passing them to the webview in JS
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
try {
|
||||||
|
data.put("errorCode", -6);
|
||||||
|
data.put("description", "The connection to the server was unsuccessful.");
|
||||||
|
data.put("url", url);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// Will never happen.
|
||||||
|
}
|
||||||
|
pluginManager.postMessage("onReceivedError", data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Timeout timer method
|
||||||
|
final Runnable timeoutCheck = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
synchronized (this) {
|
||||||
|
wait(loadUrlTimeoutValue);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If timeout, then stop loading and handle error
|
||||||
|
if (loadUrlTimeout == currentLoadUrlTimeout) {
|
||||||
|
cordova.getActivity().runOnUiThread(loadError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final boolean _recreatePlugins = recreatePlugins;
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (loadUrlTimeoutValue > 0) {
|
||||||
|
cordova.getThreadPool().execute(timeoutCheck);
|
||||||
|
}
|
||||||
|
engine.loadUrl(url, _recreatePlugins);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUrl(String url) {
|
||||||
|
loadUrlIntoView(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) {
|
||||||
|
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
|
||||||
|
|
||||||
|
// If clearing history
|
||||||
|
if (clearHistory) {
|
||||||
|
engine.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If loading into our webview
|
||||||
|
if (!openExternal) {
|
||||||
|
// Make sure url is in whitelist
|
||||||
|
if (pluginManager.shouldAllowNavigation(url)) {
|
||||||
|
// TODO: What about params?
|
||||||
|
// Load new URL
|
||||||
|
loadUrlIntoView(url, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Load in default viewer if not
|
||||||
|
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
|
||||||
|
// Adding the MIME type to http: URLs causes them to not be handled by the downloader.
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
if ("file".equals(uri.getScheme())) {
|
||||||
|
intent.setDataAndType(uri, resourceApi.getMimeType(uri));
|
||||||
|
} else {
|
||||||
|
intent.setData(uri);
|
||||||
|
}
|
||||||
|
cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error loading url " + url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||||
|
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||||
|
Log.d(TAG, "showing Custom View");
|
||||||
|
// if a view already exists then immediately terminate the new one
|
||||||
|
if (mCustomView != null) {
|
||||||
|
callback.onCustomViewHidden();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the view and its callback for later (to kill it properly)
|
||||||
|
mCustomView = view;
|
||||||
|
mCustomViewCallback = callback;
|
||||||
|
|
||||||
|
// Add the custom view to its container.
|
||||||
|
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||||
|
parent.addView(view, new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
Gravity.CENTER));
|
||||||
|
|
||||||
|
// Hide the content view.
|
||||||
|
engine.getView().setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Finally show the custom view container.
|
||||||
|
parent.setVisibility(View.VISIBLE);
|
||||||
|
parent.bringToFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void hideCustomView() {
|
||||||
|
// This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
|
||||||
|
Log.d(TAG, "Hiding Custom View");
|
||||||
|
if (mCustomView == null) return;
|
||||||
|
|
||||||
|
// Hide the custom view.
|
||||||
|
mCustomView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Remove the custom view from its container.
|
||||||
|
ViewGroup parent = (ViewGroup) engine.getView().getParent();
|
||||||
|
parent.removeView(mCustomView);
|
||||||
|
mCustomView = null;
|
||||||
|
mCustomViewCallback.onCustomViewHidden();
|
||||||
|
|
||||||
|
// Show the content view.
|
||||||
|
engine.getView().setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public boolean isCustomViewShowing() {
|
||||||
|
return mCustomView != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void sendJavascript(String statement) {
|
||||||
|
nativeToJsMessageQueue.addJavaScript(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendPluginResult(PluginResult cr, String callbackId) {
|
||||||
|
nativeToJsMessageQueue.addPluginResult(cr, callbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PluginManager getPluginManager() {
|
||||||
|
return pluginManager;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CordovaPreferences getPreferences() {
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ICordovaCookieManager getCookieManager() {
|
||||||
|
return engine.getCookieManager();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CordovaResourceApi getResourceApi() {
|
||||||
|
return resourceApi;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CordovaWebViewEngine getEngine() {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return engine.getView();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return engine.getView().getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendJavascriptEvent(String event) {
|
||||||
|
if (appPlugin == null) {
|
||||||
|
appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appPlugin == null) {
|
||||||
|
LOG.w(TAG, "Unable to fire event without existing plugin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appPlugin.fireJavascriptEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setButtonPlumbedToJs(int keyCode, boolean override) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
// TODO: Why are search and menu buttons handled separately?
|
||||||
|
if (override) {
|
||||||
|
boundKeyCodes.add(keyCode);
|
||||||
|
} else {
|
||||||
|
boundKeyCodes.remove(keyCode);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isButtonPlumbedToJs(int keyCode) {
|
||||||
|
return boundKeyCodes.contains(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postMessage(String id, Object data) {
|
||||||
|
return pluginManager.postMessage(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine method proxies:
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return engine.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopLoading() {
|
||||||
|
// Clear timeout flag
|
||||||
|
loadUrlTimeout++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGoBack() {
|
||||||
|
return engine.canGoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
engine.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void clearCache(boolean b) {
|
||||||
|
engine.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHistory() {
|
||||||
|
engine.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean backHistory() {
|
||||||
|
return engine.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////// LifeCycle methods ///////
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handlePause(boolean keepRunning) {
|
||||||
|
LOG.d(TAG, "Handle the pause");
|
||||||
|
// Send pause event to JavaScript
|
||||||
|
sendJavascriptEvent("pause");
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
if (pluginManager != null) {
|
||||||
|
pluginManager.onPause(keepRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If app doesn't want to run in background
|
||||||
|
if (!keepRunning) {
|
||||||
|
// Pause JavaScript timers. This affects all webviews within the app!
|
||||||
|
engine.setPaused(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void handleResume(boolean keepRunning)
|
||||||
|
{
|
||||||
|
// Resume JavaScript timers. This affects all webviews within the app!
|
||||||
|
engine.setPaused(false);
|
||||||
|
|
||||||
|
sendJavascriptEvent("resume");
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onResume(keepRunning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDestroy()
|
||||||
|
{
|
||||||
|
// Cancel pending timeout timer.
|
||||||
|
loadUrlTimeout++;
|
||||||
|
|
||||||
|
// Forward to plugins
|
||||||
|
if (this.pluginManager != null) {
|
||||||
|
this.pluginManager.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load blank page so that JavaScript onunload is called
|
||||||
|
this.loadUrl("about:blank");
|
||||||
|
|
||||||
|
// TODO: Should not destroy webview until after about:blank is done loading.
|
||||||
|
engine.destroy();
|
||||||
|
hideCustomView();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class EngineClient implements CordovaWebViewEngine.Client {
|
||||||
|
private long lastMenuEventTime = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearLoadTimeoutTimer() {
|
||||||
|
loadUrlTimeout++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(String newUrl) {
|
||||||
|
LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")");
|
||||||
|
boundKeyCodes.clear();
|
||||||
|
pluginManager.onReset();
|
||||||
|
pluginManager.postMessage("onPageStarted", newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(int errorCode, String description, String failingUrl) {
|
||||||
|
clearLoadTimeoutTimer();
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
try {
|
||||||
|
data.put("errorCode", errorCode);
|
||||||
|
data.put("description", description);
|
||||||
|
data.put("url", failingUrl);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
pluginManager.postMessage("onReceivedError", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinishedLoading(String url) {
|
||||||
|
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||||
|
|
||||||
|
clearLoadTimeoutTimer();
|
||||||
|
|
||||||
|
// Broadcast message that page has loaded
|
||||||
|
pluginManager.postMessage("onPageFinished", url);
|
||||||
|
|
||||||
|
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
||||||
|
if (engine.getView().getVisibility() != View.VISIBLE) {
|
||||||
|
Thread t = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
cordova.getActivity().runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
pluginManager.postMessage("spinner", "stop");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown if blank loaded
|
||||||
|
if (url.equals("about:blank")) {
|
||||||
|
pluginManager.postMessage("exit", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
if (boundKeyCodes.contains(keyCode))
|
||||||
|
{
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
sendJavascriptEvent("volumedownbutton");
|
||||||
|
return true;
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
|
sendJavascriptEvent("volumeupbutton");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||||
|
{
|
||||||
|
return !engine.canGoBack() || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
|
||||||
|
}
|
||||||
|
else if(keyCode == KeyEvent.KEYCODE_MENU)
|
||||||
|
{
|
||||||
|
//How did we get here? Is there a childView?
|
||||||
|
View childView = ((ViewGroup)engine.getView().getParent()).getFocusedChild();
|
||||||
|
if(childView != null)
|
||||||
|
{
|
||||||
|
//Make sure we close the keyboard if it's present
|
||||||
|
InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
|
||||||
|
cordova.getActivity().openOptionsMenu();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean onKeyUp(int keyCode, KeyEvent event)
|
||||||
|
{
|
||||||
|
// If back key
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
// A custom view is currently displayed (e.g. playing a video)
|
||||||
|
if(mCustomView != null) {
|
||||||
|
hideCustomView();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// The webview is currently displayed
|
||||||
|
// If back key is bound, then send event to JavaScript
|
||||||
|
if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
|
||||||
|
sendJavascriptEvent("backbutton");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// If not bound
|
||||||
|
// Go to previous page in webview if it is possible to go back
|
||||||
|
if (engine.goBack()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If not, then invoke default behavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Legacy
|
||||||
|
else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||||
|
if (lastMenuEventTime < event.getEventTime()) {
|
||||||
|
sendJavascriptEvent("menubutton");
|
||||||
|
}
|
||||||
|
lastMenuEventTime = event.getEventTime();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If search key
|
||||||
|
else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
|
||||||
|
sendJavascriptEvent("searchbutton");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(String url) {
|
||||||
|
// Give plugins the chance to handle the url
|
||||||
|
if (pluginManager.shouldAllowNavigation(url)) {
|
||||||
|
// Allow internal navigation
|
||||||
|
return false;
|
||||||
|
} else if (pluginManager.shouldOpenExternalUrl(url)) {
|
||||||
|
// Do nothing other than what the plugins wanted.
|
||||||
|
// If any returned false, then the request was either blocked
|
||||||
|
// completely, or handled out-of-band by the plugin. If they all
|
||||||
|
// returned true, then we should open the URL here.
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||||
|
intent.setComponent(null);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||||
|
intent.setSelector(null);
|
||||||
|
}
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
return true;
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
Log.e(TAG, "Error loading url " + url, e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Block by default
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -283,11 +283,11 @@ public class NativeToJsMessageQueue {
|
|||||||
|
|
||||||
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
/** Uses webView.loadUrl("javascript:") to execute messages. */
|
||||||
public static class LoadUrlBridgeMode extends BridgeMode {
|
public static class LoadUrlBridgeMode extends BridgeMode {
|
||||||
private final CordovaWebView webView;
|
private final CordovaWebViewEngine engine;
|
||||||
private final CordovaInterface cordova;
|
private final CordovaInterface cordova;
|
||||||
|
|
||||||
public LoadUrlBridgeMode(CordovaWebView webView, CordovaInterface cordova) {
|
public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) {
|
||||||
this.webView = webView;
|
this.engine = engine;
|
||||||
this.cordova = cordova;
|
this.cordova = cordova;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +297,7 @@ public class NativeToJsMessageQueue {
|
|||||||
public void run() {
|
public void run() {
|
||||||
String js = queue.popAndEncodeAsJs();
|
String js = queue.popAndEncodeAsJs();
|
||||||
if (js != null) {
|
if (js != null) {
|
||||||
webView.loadUrl("javascript:" + js);
|
engine.loadUrl("javascript:" + js, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -217,7 +217,7 @@ public class PluginManager {
|
|||||||
*/
|
*/
|
||||||
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
|
||||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
if (plugin != null && plugin.onReceivedHttpAuthRequest(view, handler, host, realm)) {
|
if (plugin != null && plugin.onReceivedHttpAuthRequest(app, handler, host, realm)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,7 +236,7 @@ public class PluginManager {
|
|||||||
*/
|
*/
|
||||||
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
|
||||||
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
for (CordovaPlugin plugin : this.pluginMap.values()) {
|
||||||
if (plugin != null && plugin.onReceivedClientCertRequest(view, request)) {
|
if (plugin != null && plugin.onReceivedClientCertRequest(app, request)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,20 @@
|
|||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.apache.cordova;
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.webkit.CookieManager;
|
import android.webkit.CookieManager;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
class AndroidCookieManager implements ICordovaCookieManager {
|
import org.apache.cordova.ICordovaCookieManager;
|
||||||
|
|
||||||
|
class SystemCookieManager implements ICordovaCookieManager {
|
||||||
|
|
||||||
protected final WebView webView;
|
protected final WebView webView;
|
||||||
private final CookieManager cookieManager;
|
private final CookieManager cookieManager;
|
||||||
|
|
||||||
public AndroidCookieManager(WebView webview) {
|
public SystemCookieManager(WebView webview) {
|
||||||
webView = webview;
|
webView = webview;
|
||||||
cookieManager = CookieManager.getInstance();
|
cookieManager = CookieManager.getInstance();
|
||||||
|
|
@ -16,9 +16,12 @@
|
|||||||
specific language governing permissions and limitations
|
specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
import android.webkit.JavascriptInterface;
|
import android.webkit.JavascriptInterface;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaBridge;
|
||||||
|
import org.apache.cordova.ExposedJsApi;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,10 +29,10 @@ import org.json.JSONException;
|
|||||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||||
*/
|
*/
|
||||||
class AndroidExposedJsApi implements ExposedJsApi {
|
class SystemExposedJsApi implements ExposedJsApi {
|
||||||
private final CordovaBridge bridge;
|
private final CordovaBridge bridge;
|
||||||
|
|
||||||
AndroidExposedJsApi(CordovaBridge bridge) {
|
SystemExposedJsApi(CordovaBridge bridge) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,7 @@
|
|||||||
specific language governing permissions and limitations
|
specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@ -40,29 +40,34 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
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.
|
* 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,
|
* The kind of callbacks that happen here are on the chrome outside the document,
|
||||||
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
||||||
* to but different than CordovaWebViewClient.
|
* to but different than CordovaWebViewClient.
|
||||||
*/
|
*/
|
||||||
public class AndroidChromeClient extends WebChromeClient {
|
public class SystemWebChromeClient extends WebChromeClient {
|
||||||
|
|
||||||
public static final int FILECHOOSER_RESULTCODE = 5173;
|
private static final int FILECHOOSER_RESULTCODE = 5173;
|
||||||
private static final String LOG_TAG = "AndroidChromeClient";
|
private static final String LOG_TAG = "SystemWebChromeClient";
|
||||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||||
protected final CordovaInterface cordova;
|
protected final SystemWebViewEngine parentEngine;
|
||||||
protected final AndroidWebView appView;
|
|
||||||
|
|
||||||
// the video progress view
|
// the video progress view
|
||||||
private View mVideoProgressView;
|
private View mVideoProgressView;
|
||||||
|
|
||||||
private CordovaDialogsHelper dialogsHelper;
|
private CordovaDialogsHelper dialogsHelper;
|
||||||
|
|
||||||
public AndroidChromeClient(CordovaInterface ctx, AndroidWebView webView) {
|
private WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||||
this.cordova = ctx;
|
private View mCustomView;
|
||||||
this.appView = webView;
|
|
||||||
dialogsHelper = new CordovaDialogsHelper(webView.getContext());
|
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
|
||||||
|
this.parentEngine = parentEngine;
|
||||||
|
dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,7 +116,7 @@ public class AndroidChromeClient extends WebChromeClient {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
|
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.
|
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
|
||||||
String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||||
if (handledRet != null) {
|
if (handledRet != null) {
|
||||||
result.confirm(handledRet);
|
result.confirm(handledRet);
|
||||||
} else {
|
} else {
|
||||||
@ -178,14 +183,14 @@ public class AndroidChromeClient extends WebChromeClient {
|
|||||||
// API level 7 is required for this, see if we could lower this using something else
|
// API level 7 is required for this, see if we could lower this using something else
|
||||||
@Override
|
@Override
|
||||||
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||||
this.appView.showCustomView(view, callback);
|
parentEngine.getCordovaWebView().showCustomView(view, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHideCustomView() {
|
public void onHideCustomView() {
|
||||||
this.appView.hideCustomView();
|
parentEngine.getCordovaWebView().hideCustomView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
/**
|
/**
|
||||||
* Ask the host application for a custom progress view to show while
|
* Ask the host application for a custom progress view to show while
|
||||||
@ -198,13 +203,13 @@ public class AndroidChromeClient extends WebChromeClient {
|
|||||||
// Create a new Loading view programmatically.
|
// Create a new Loading view programmatically.
|
||||||
|
|
||||||
// create the linear layout
|
// create the linear layout
|
||||||
LinearLayout layout = new LinearLayout(this.appView.getContext());
|
LinearLayout layout = new LinearLayout(parentEngine.getView().getContext());
|
||||||
layout.setOrientation(LinearLayout.VERTICAL);
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||||
layout.setLayoutParams(layoutParams);
|
layout.setLayoutParams(layoutParams);
|
||||||
// the proress bar
|
// the proress bar
|
||||||
ProgressBar bar = new ProgressBar(this.appView.getContext());
|
ProgressBar bar = new ProgressBar(parentEngine.getView().getContext());
|
||||||
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
barLayoutParams.gravity = Gravity.CENTER;
|
barLayoutParams.gravity = Gravity.CENTER;
|
||||||
bar.setLayoutParams(barLayoutParams);
|
bar.setLayoutParams(barLayoutParams);
|
||||||
@ -231,7 +236,7 @@ public class AndroidChromeClient extends WebChromeClient {
|
|||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
cordova.startActivityForResult(new CordovaPlugin() {
|
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData();
|
||||||
@ -246,7 +251,7 @@ public class AndroidChromeClient extends WebChromeClient {
|
|||||||
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
|
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
|
||||||
Intent intent = fileChooserParams.createIntent();
|
Intent intent = fileChooserParams.createIntent();
|
||||||
try {
|
try {
|
||||||
cordova.startActivityForResult(new CordovaPlugin() {
|
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
|
Uri[] result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
|
||||||
@ -264,5 +269,4 @@ public class AndroidChromeClient extends WebChromeClient {
|
|||||||
public void destroyLastDialog(){
|
public void destroyLastDialog(){
|
||||||
dialogsHelper.destroyLastDialog();
|
dialogsHelper.destroyLastDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
88
framework/src/org/apache/cordova/engine/SystemWebView.java
Normal file
88
framework/src/org/apache/cordova/engine/SystemWebView.java
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CordovaWebViewEngine;
|
||||||
|
import org.apache.cordova.ScrollEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom WebView subclass that enables us to capture events needed for Cordova.
|
||||||
|
*/
|
||||||
|
public class SystemWebView extends WebView implements CordovaWebViewEngine.EngineView {
|
||||||
|
private SystemWebViewClient viewClient;
|
||||||
|
SystemWebChromeClient chromeClient;
|
||||||
|
private SystemWebViewEngine parentEngine;
|
||||||
|
private CordovaInterface cordova;
|
||||||
|
|
||||||
|
public SystemWebView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemWebView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package visibility to enforce that only SystemWebViewEngine should call this method.
|
||||||
|
void init(SystemWebViewEngine parentEngine, CordovaInterface cordova) {
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.parentEngine = parentEngine;
|
||||||
|
if (this.viewClient == null) {
|
||||||
|
setWebViewClient(new SystemWebViewClient(parentEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.chromeClient == null) {
|
||||||
|
setWebChromeClient(new SystemWebChromeClient(parentEngine));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CordovaWebView getCordovaWebView() {
|
||||||
|
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebViewClient(WebViewClient client) {
|
||||||
|
viewClient = (SystemWebViewClient)client;
|
||||||
|
super.setWebViewClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWebChromeClient(WebChromeClient client) {
|
||||||
|
chromeClient = (SystemWebChromeClient)client;
|
||||||
|
super.setWebChromeClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollChanged(int l, int t, int oldl, int oldt)
|
||||||
|
{
|
||||||
|
super.onScrollChanged(l, t, oldl, oldt);
|
||||||
|
//We should post a message that the scroll changed
|
||||||
|
ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
|
||||||
|
parentEngine.pluginManager.postMessage("onScrollChanged", myEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
Boolean ret = parentEngine.client.onKeyDown(keyCode, event);
|
||||||
|
if (ret != null) {
|
||||||
|
return ret.booleanValue();
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
|
Boolean ret = parentEngine.client.onKeyUp(keyCode, event);
|
||||||
|
if (ret != null) {
|
||||||
|
return ret.booleanValue();
|
||||||
|
}
|
||||||
|
return super.onKeyUp(keyCode, event);
|
||||||
|
}
|
||||||
|
}
|
@ -16,14 +16,7 @@
|
|||||||
specific language governing permissions and limitations
|
specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Hashtable;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@ -33,7 +26,6 @@ import android.graphics.Bitmap;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.net.http.SslError;
|
import android.net.http.SslError;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.View;
|
|
||||||
import android.webkit.ClientCertRequest;
|
import android.webkit.ClientCertRequest;
|
||||||
import android.webkit.HttpAuthHandler;
|
import android.webkit.HttpAuthHandler;
|
||||||
import android.webkit.SslErrorHandler;
|
import android.webkit.SslErrorHandler;
|
||||||
@ -41,6 +33,17 @@ import android.webkit.WebResourceResponse;
|
|||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import org.apache.cordova.AuthenticationToken;
|
||||||
|
import org.apache.cordova.CordovaClientCertRequest;
|
||||||
|
import org.apache.cordova.CordovaHttpAuthHandler;
|
||||||
|
import org.apache.cordova.CordovaResourceApi;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.apache.cordova.PluginManager;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the WebViewClient that implements callbacks for our web view.
|
* This class is the WebViewClient that implements callbacks for our web view.
|
||||||
@ -48,28 +51,19 @@ import android.webkit.WebViewClient;
|
|||||||
* document instead of the chrome surrounding it, such as onPageStarted(),
|
* document instead of the chrome surrounding it, such as onPageStarted(),
|
||||||
* shouldOverrideUrlLoading(), etc. Related to but different than
|
* shouldOverrideUrlLoading(), etc. Related to but different than
|
||||||
* CordovaChromeClient.
|
* CordovaChromeClient.
|
||||||
*
|
|
||||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebViewClient.html">WebViewClient</a>
|
|
||||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
|
||||||
* @see CordovaChromeClient
|
|
||||||
* @see CordovaWebView
|
|
||||||
*/
|
*/
|
||||||
public class AndroidWebViewClient extends WebViewClient {
|
public class SystemWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
private static final String TAG = "AndroidWebViewClient";
|
private static final String TAG = "SystemWebViewClient";
|
||||||
protected final CordovaInterface cordova;
|
protected final SystemWebViewEngine parentEngine;
|
||||||
protected final AndroidWebView appView;
|
|
||||||
protected final CordovaUriHelper helper;
|
|
||||||
private boolean doClearHistory = false;
|
private boolean doClearHistory = false;
|
||||||
boolean isCurrentlyLoading;
|
boolean isCurrentlyLoading;
|
||||||
|
|
||||||
/** The authorization tokens. */
|
/** The authorization tokens. */
|
||||||
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||||
|
|
||||||
public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) {
|
public SystemWebViewClient(SystemWebViewEngine parentEngine) {
|
||||||
this.cordova = cordova;
|
this.parentEngine = parentEngine;
|
||||||
this.appView = view;
|
|
||||||
helper = new CordovaUriHelper(cordova, view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +76,7 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
return helper.shouldOverrideUrlLoading(url);
|
return parentEngine.client.shouldOverrideUrlLoading(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,9 +94,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is some plugin which can resolve this auth challenge
|
// Check if there is some plugin which can resolve this auth challenge
|
||||||
PluginManager pluginManager = this.appView.pluginManager;
|
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||||
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(this.appView, new CordovaHttpAuthHandler(handler), host, realm)) {
|
if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new CordovaHttpAuthHandler(handler), host, realm)) {
|
||||||
this.appView.loadUrlTimeout++;
|
parentEngine.client.clearLoadTimeoutTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +117,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Check if there is some plugin which can resolve this certificate request
|
// Check if there is some plugin which can resolve this certificate request
|
||||||
PluginManager pluginManager = this.appView.pluginManager;
|
PluginManager pluginManager = this.parentEngine.pluginManager;
|
||||||
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(this.appView, new CordovaClientCertRequest(request))) {
|
if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new CordovaClientCertRequest(request))) {
|
||||||
this.appView.loadUrlTimeout++;
|
parentEngine.client.clearLoadTimeoutTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,12 +140,9 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
super.onPageStarted(view, url, favicon);
|
super.onPageStarted(view, url, favicon);
|
||||||
isCurrentlyLoading = true;
|
isCurrentlyLoading = true;
|
||||||
LOG.d(TAG, "onPageStarted(" + url + ")");
|
|
||||||
// Flush stale messages & reset plugins.
|
// Flush stale messages & reset plugins.
|
||||||
this.appView.onPageReset();
|
parentEngine.bridge.reset();
|
||||||
|
parentEngine.client.onPageStarted(url);
|
||||||
// Broadcast message that page has loaded
|
|
||||||
this.appView.getPluginManager().postMessage("onPageStarted", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,7 +161,6 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isCurrentlyLoading = false;
|
isCurrentlyLoading = false;
|
||||||
LOG.d(TAG, "onPageFinished(" + url + ")");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
||||||
@ -182,35 +172,8 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
view.clearHistory();
|
view.clearHistory();
|
||||||
this.doClearHistory = false;
|
this.doClearHistory = false;
|
||||||
}
|
}
|
||||||
|
parentEngine.client.onPageFinishedLoading(url);
|
||||||
|
|
||||||
// Clear timeout flag
|
|
||||||
appView.loadUrlTimeout++;
|
|
||||||
|
|
||||||
// Broadcast message that page has loaded
|
|
||||||
this.appView.getPluginManager().postMessage("onPageFinished", url);
|
|
||||||
|
|
||||||
// Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
|
|
||||||
if (this.appView.getVisibility() == View.INVISIBLE) {
|
|
||||||
Thread t = new Thread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000);
|
|
||||||
cordova.getActivity().runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
appView.getPluginManager().postMessage("spinner", "stop");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown if blank loaded
|
|
||||||
if (url.equals("about:blank")) {
|
|
||||||
appView.getPluginManager().postMessage("exit", null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,13 +193,12 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
}
|
}
|
||||||
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||||
|
|
||||||
// Clear timeout flag
|
|
||||||
appView.loadUrlTimeout++;
|
|
||||||
|
|
||||||
// If this is a "Protocol Not Supported" error, then revert to the previous
|
// 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
|
// 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)
|
// is likely incorrect (start page set to sms: or something like that)
|
||||||
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
|
if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
|
||||||
|
parentEngine.client.clearLoadTimeoutTimer();
|
||||||
|
|
||||||
if (view.canGoBack()) {
|
if (view.canGoBack()) {
|
||||||
view.goBack();
|
view.goBack();
|
||||||
return;
|
return;
|
||||||
@ -244,17 +206,7 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
parentEngine.client.onReceivedError(errorCode, description, failingUrl);
|
||||||
// Handle other errors by passing them to the webview in JS
|
|
||||||
JSONObject data = new JSONObject();
|
|
||||||
try {
|
|
||||||
data.put("errorCode", errorCode);
|
|
||||||
data.put("description", description);
|
|
||||||
data.put("url", failingUrl);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
this.appView.getPluginManager().postMessage("onReceivedError", data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -271,8 +223,8 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
@Override
|
@Override
|
||||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
|
||||||
final String packageName = this.cordova.getActivity().getPackageName();
|
final String packageName = parentEngine.cordova.getActivity().getPackageName();
|
||||||
final PackageManager pm = this.cordova.getActivity().getPackageManager();
|
final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
|
||||||
|
|
||||||
ApplicationInfo appInfo;
|
ApplicationInfo appInfo;
|
||||||
try {
|
try {
|
||||||
@ -370,13 +322,13 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
try {
|
try {
|
||||||
// Check the against the whitelist and lock out access to the WebView directory
|
// Check the against the whitelist and lock out access to the WebView directory
|
||||||
// Changing this will cause problems for your application
|
// Changing this will cause problems for your application
|
||||||
if (!appView.getPluginManager().shouldAllowRequest(url)) {
|
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
|
||||||
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
||||||
// Results in a 404.
|
// Results in a 404.
|
||||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
CordovaResourceApi resourceApi = appView.getResourceApi();
|
CordovaResourceApi resourceApi = parentEngine.resourceApi;
|
||||||
Uri origUri = Uri.parse(url);
|
Uri origUri = Uri.parse(url);
|
||||||
// Allow plugins to intercept WebView requests.
|
// Allow plugins to intercept WebView requests.
|
||||||
Uri remappedUri = resourceApi.remapUri(origUri);
|
Uri remappedUri = resourceApi.remapUri(origUri);
|
||||||
@ -389,7 +341,7 @@ public class AndroidWebViewClient extends WebViewClient {
|
|||||||
return null;
|
return null;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (!(e instanceof FileNotFoundException)) {
|
if (!(e instanceof FileNotFoundException)) {
|
||||||
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file (returning a 404).", e);
|
LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
|
||||||
}
|
}
|
||||||
// Results in a 404.
|
// Results in a 404.
|
||||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
return new WebResourceResponse("text/plain", "UTF-8", null);
|
312
framework/src/org/apache/cordova/engine/SystemWebViewEngine.java
Executable file
312
framework/src/org/apache/cordova/engine/SystemWebViewEngine.java
Executable file
@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
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.engine;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebSettings.LayoutAlgorithm;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaBridge;
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.CordovaPreferences;
|
||||||
|
import org.apache.cordova.CordovaResourceApi;
|
||||||
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.CordovaWebViewEngine;
|
||||||
|
import org.apache.cordova.ICordovaCookieManager;
|
||||||
|
import org.apache.cordova.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 SystemWebViewEngine implements CordovaWebViewEngine {
|
||||||
|
public static final String TAG = "SystemWebViewEngine";
|
||||||
|
|
||||||
|
protected final SystemWebView webView;
|
||||||
|
protected final SystemCookieManager cookieManager;
|
||||||
|
protected CordovaBridge bridge;
|
||||||
|
protected CordovaWebViewEngine.Client client;
|
||||||
|
protected CordovaWebView parentWebView;
|
||||||
|
protected CordovaInterface cordova;
|
||||||
|
protected PluginManager pluginManager;
|
||||||
|
protected CordovaResourceApi resourceApi;
|
||||||
|
protected NativeToJsMessageQueue nativeToJsMessageQueue;
|
||||||
|
private BroadcastReceiver receiver;
|
||||||
|
|
||||||
|
/** Used when created via reflection. */
|
||||||
|
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
|
||||||
|
this(new SystemWebView(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemWebViewEngine(SystemWebView webView) {
|
||||||
|
this.webView = webView;
|
||||||
|
cookieManager = new SystemCookieManager(webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
|
||||||
|
CordovaResourceApi resourceApi, PluginManager pluginManager,
|
||||||
|
NativeToJsMessageQueue nativeToJsMessageQueue) {
|
||||||
|
if (this.cordova != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
webView.setNetworkAvailable(value);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void runOnUiThread(Runnable r) {
|
||||||
|
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
|
||||||
|
exposeJsInterface(webView, bridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CordovaWebView getCordovaWebView() {
|
||||||
|
return parentWebView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICordovaCookieManager getCookieManager() {
|
||||||
|
return cookieManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return webView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("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 = android.os.Build.MANUFACTURER;
|
||||||
|
Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
|
||||||
|
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
|
||||||
|
android.os.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 (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
settings.setAllowUniversalAccessFromFileURLs(true);
|
||||||
|
}
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.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 &&
|
||||||
|
android.os.Build.VERSION.SDK_INT >= android.os.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
|
||||||
|
settings.getUserAgentString();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
|
||||||
|
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the url into the webview.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadUrl(final String url, boolean clearNavigationStack) {
|
||||||
|
webView.loadUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrl() {
|
||||||
|
return webView.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopLoading() {
|
||||||
|
webView.stopLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
webView.clearCache(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHistory() {
|
||||||
|
webView.clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGoBack() {
|
||||||
|
return webView.canGoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to previous page in history. (We manage our own history)
|
||||||
|
*
|
||||||
|
* @return true if we went back, false if we are already at top
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean goBack() {
|
||||||
|
// Check webview first to see if there is a history
|
||||||
|
// This is needed to support curPage#diffLink, since they are added to parentEngine's history, but not our history url array (JQMobile behavior)
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPaused(boolean value) {
|
||||||
|
if (value) {
|
||||||
|
webView.pauseTimers();
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,8 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import org.apache.cordova.AndroidWebView;
|
import org.apache.cordova.CordovaWebViewEngine;
|
||||||
|
import org.apache.cordova.engine.SystemWebView;
|
||||||
|
|
||||||
public class CordovaActivityTest extends BaseCordovaIntegrationTest {
|
public class CordovaActivityTest extends BaseCordovaIntegrationTest {
|
||||||
private ViewGroup innerContainer;
|
private ViewGroup innerContainer;
|
||||||
@ -37,8 +38,9 @@ public class CordovaActivityTest extends BaseCordovaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testBasicLoad() throws Exception {
|
public void testBasicLoad() throws Exception {
|
||||||
assertTrue(testView instanceof AndroidWebView);
|
assertTrue(testView instanceof SystemWebView);
|
||||||
assertTrue(innerContainer instanceof LinearLayout);
|
assertTrue(innerContainer instanceof LinearLayout);
|
||||||
|
assertTrue(((CordovaWebViewEngine.EngineView)testView).getCordovaWebView() != null);
|
||||||
String onPageFinishedUrl = testActivity.onPageFinishedUrl.take();
|
String onPageFinishedUrl = testActivity.onPageFinishedUrl.take();
|
||||||
assertEquals(MainTestActivity.START_URL, onPageFinishedUrl);
|
assertEquals(MainTestActivity.START_URL, onPageFinishedUrl);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import org.apache.cordova.AndroidWebView;
|
import org.apache.cordova.engine.SystemWebView;
|
||||||
|
|
||||||
public class InflateLayoutTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
|
public class InflateLayoutTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ public class InflateLayoutTest extends ActivityInstrumentationTestCase2<CordovaW
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testBasicLoad() throws Exception {
|
public void testBasicLoad() throws Exception {
|
||||||
assertTrue(testView instanceof AndroidWebView);
|
assertTrue(testView instanceof SystemWebView);
|
||||||
assertTrue(innerContainer instanceof LinearLayout);
|
assertTrue(innerContainer instanceof LinearLayout);
|
||||||
String onPageFinishedUrl = testActivity.onPageFinishedUrl.take();
|
String onPageFinishedUrl = testActivity.onPageFinishedUrl.take();
|
||||||
assertEquals(CordovaWebViewTestActivity.START_URL, onPageFinishedUrl);
|
assertEquals(CordovaWebViewTestActivity.START_URL, onPageFinishedUrl);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<org.apache.cordova.AndroidWebView
|
<org.apache.cordova.engine.SystemWebView
|
||||||
android:id="@+id/cordovaWebView"
|
android:id="@+id/cordovaWebView"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent" />
|
android:layout_height="fill_parent" />
|
||||||
|
@ -21,15 +21,12 @@ package org.apache.cordova.test;
|
|||||||
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
|
||||||
import org.apache.cordova.AndroidChromeClient;
|
|
||||||
import org.apache.cordova.AndroidWebView;
|
|
||||||
import org.apache.cordova.AndroidWebViewClient;
|
|
||||||
import org.apache.cordova.Config;
|
import org.apache.cordova.Config;
|
||||||
import org.apache.cordova.CordovaInterfaceImpl;
|
import org.apache.cordova.CordovaInterfaceImpl;
|
||||||
import org.apache.cordova.CordovaWebView;
|
import org.apache.cordova.CordovaWebView;
|
||||||
import org.apache.cordova.CordovaInterface;
|
import org.apache.cordova.CordovaWebViewImpl;
|
||||||
import org.apache.cordova.CordovaPlugin;
|
import org.apache.cordova.engine.SystemWebView;
|
||||||
import org.apache.cordova.test.R;
|
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -61,8 +58,8 @@ public class CordovaWebViewTestActivity extends Activity {
|
|||||||
//CB-7238: This has to be added now, because it got removed from somewhere else
|
//CB-7238: This has to be added now, because it got removed from somewhere else
|
||||||
Config.init(this);
|
Config.init(this);
|
||||||
|
|
||||||
AndroidWebView webView = (AndroidWebView) findViewById(R.id.cordovaWebView);
|
SystemWebView webView = (SystemWebView) findViewById(R.id.cordovaWebView);
|
||||||
cordovaWebView = webView;
|
cordovaWebView = new CordovaWebViewImpl(this, new SystemWebViewEngine(webView));
|
||||||
cordovaWebView.init(cordovaInterface, Config.getPluginEntries(), Config.getPreferences());
|
cordovaWebView.init(cordovaInterface, Config.getPluginEntries(), Config.getPreferences());
|
||||||
|
|
||||||
cordovaWebView.loadUrl(START_URL);
|
cordovaWebView.loadUrl(START_URL);
|
||||||
|
@ -23,6 +23,9 @@ import android.webkit.WebView;
|
|||||||
import android.webkit.GeolocationPermissions.Callback;
|
import android.webkit.GeolocationPermissions.Callback;
|
||||||
|
|
||||||
import org.apache.cordova.*;
|
import org.apache.cordova.*;
|
||||||
|
import org.apache.cordova.engine.SystemWebChromeClient;
|
||||||
|
import org.apache.cordova.engine.SystemWebViewClient;
|
||||||
|
import org.apache.cordova.engine.SystemWebViewEngine;
|
||||||
|
|
||||||
public class userwebview extends MainTestActivity {
|
public class userwebview extends MainTestActivity {
|
||||||
|
|
||||||
@ -32,17 +35,19 @@ public class userwebview extends MainTestActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
testViewClient = new TestViewClient(cordovaInterface, ((AndroidWebView)appView));
|
SystemWebViewEngine engine = (SystemWebViewEngine)appView.getEngine();
|
||||||
testChromeClient = new TestChromeClient(cordovaInterface, ((AndroidWebView)appView));
|
testViewClient = new TestViewClient(engine);
|
||||||
|
testChromeClient = new TestChromeClient(engine);
|
||||||
super.init();
|
super.init();
|
||||||
((AndroidWebView)appView).setWebViewClient(testViewClient);
|
WebView webView = (WebView)engine.getView();
|
||||||
((AndroidWebView)appView).setWebChromeClient(testChromeClient);
|
webView.setWebViewClient(testViewClient);
|
||||||
|
webView.setWebChromeClient(testChromeClient);
|
||||||
super.loadUrl("file:///android_asset/www/userwebview/index.html");
|
super.loadUrl("file:///android_asset/www/userwebview/index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestChromeClient extends AndroidChromeClient {
|
public class TestChromeClient extends SystemWebChromeClient {
|
||||||
public TestChromeClient(CordovaInterface ctx, AndroidWebView app) {
|
public TestChromeClient(SystemWebViewEngine parentEngine) {
|
||||||
super(ctx, app);
|
super(parentEngine);
|
||||||
LOG.d("userwebview", "TestChromeClient()");
|
LOG.d("userwebview", "TestChromeClient()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,9 +62,9 @@ public class userwebview extends MainTestActivity {
|
|||||||
/**
|
/**
|
||||||
* This class can be used to override the GapViewClient and receive notification of webview events.
|
* This class can be used to override the GapViewClient and receive notification of webview events.
|
||||||
*/
|
*/
|
||||||
public class TestViewClient extends AndroidWebViewClient {
|
public class TestViewClient extends SystemWebViewClient {
|
||||||
public TestViewClient(CordovaInterface ctx, AndroidWebView app) {
|
public TestViewClient(SystemWebViewEngine parentEngine) {
|
||||||
super(ctx, app);
|
super(parentEngine);
|
||||||
LOG.d("userwebview", "TestViewClient()");
|
LOG.d("userwebview", "TestViewClient()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user