mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-22 00:32:55 +08:00
Hurray! It runs! Now that we have the default WebView working, it's time
to make things a little more pluggable.
This commit is contained in:
parent
25c8b2fabb
commit
19f76d34db
397
framework/src/org/apache/cordova/AndroidChromeClient.java
Executable file
397
framework/src/org/apache/cordova/AndroidChromeClient.java
Executable file
@ -0,0 +1,397 @@
|
|||||||
|
/*
|
||||||
|
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 org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.webkit.ConsoleMessage;
|
||||||
|
import android.webkit.JsPromptResult;
|
||||||
|
import android.webkit.JsResult;
|
||||||
|
import android.webkit.ValueCallback;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebStorage;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.GeolocationPermissions.Callback;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||||
|
* The kind of callbacks that happen here are on the chrome outside the document,
|
||||||
|
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
||||||
|
* to but different than CordovaWebViewClient.
|
||||||
|
*
|
||||||
|
* @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
|
||||||
|
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
||||||
|
* @see CordovaWebViewClient
|
||||||
|
* @see CordovaWebView
|
||||||
|
*/
|
||||||
|
public class AndroidChromeClient extends WebChromeClient implements CordovaChromeClient {
|
||||||
|
|
||||||
|
public static final int FILECHOOSER_RESULTCODE = 5173;
|
||||||
|
private static final String LOG_TAG = "CordovaChromeClient";
|
||||||
|
private String TAG = "CordovaLog";
|
||||||
|
private long MAX_QUOTA = 100 * 1024 * 1024;
|
||||||
|
protected CordovaInterface cordova;
|
||||||
|
protected CordovaWebView appView;
|
||||||
|
|
||||||
|
// the video progress view
|
||||||
|
private View mVideoProgressView;
|
||||||
|
|
||||||
|
// File Chooser
|
||||||
|
public ValueCallback<Uri> mUploadMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param cordova
|
||||||
|
*/
|
||||||
|
public AndroidChromeClient(CordovaInterface cordova) {
|
||||||
|
this.cordova = cordova;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param ctx
|
||||||
|
* @param app
|
||||||
|
*/
|
||||||
|
public AndroidChromeClient(CordovaInterface ctx, CordovaWebView app) {
|
||||||
|
this.cordova = ctx;
|
||||||
|
this.appView = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
*/
|
||||||
|
public void setWebView(CordovaWebView view) {
|
||||||
|
this.appView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a javascript alert dialog.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* @param url
|
||||||
|
* @param message
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||||
|
dlg.setMessage(message);
|
||||||
|
dlg.setTitle("Alert");
|
||||||
|
//Don't let alerts break the back button
|
||||||
|
dlg.setCancelable(true);
|
||||||
|
dlg.setPositiveButton(android.R.string.ok,
|
||||||
|
new AlertDialog.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.confirm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnCancelListener(
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||||
|
//DO NOTHING
|
||||||
|
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||||
|
{
|
||||||
|
result.confirm();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a confirm dialog to the user.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* @param url
|
||||||
|
* @param message
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||||
|
dlg.setMessage(message);
|
||||||
|
dlg.setTitle("Confirm");
|
||||||
|
dlg.setCancelable(true);
|
||||||
|
dlg.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.confirm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnCancelListener(
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||||
|
//DO NOTHING
|
||||||
|
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||||
|
{
|
||||||
|
result.cancel();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the client to display a prompt dialog to the user.
|
||||||
|
* If the client returns true, WebView will assume that the client will
|
||||||
|
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
||||||
|
*
|
||||||
|
* Since we are hacking prompts for our own purposes, we should not be using them for
|
||||||
|
* this purpose, perhaps we should hack console.log to do this instead!
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* @param url
|
||||||
|
* @param message
|
||||||
|
* @param defaultValue
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
||||||
|
|
||||||
|
// Security check to make sure any requests are coming from the page initially
|
||||||
|
// loaded in webview and not another loaded in an iframe.
|
||||||
|
boolean reqOk = false;
|
||||||
|
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
|
||||||
|
reqOk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling PluginManager.exec() to call a native service using
|
||||||
|
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
|
||||||
|
if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
|
||||||
|
JSONArray array;
|
||||||
|
try {
|
||||||
|
array = new JSONArray(defaultValue.substring(4));
|
||||||
|
String service = array.getString(0);
|
||||||
|
String action = array.getString(1);
|
||||||
|
String callbackId = array.getString(2);
|
||||||
|
|
||||||
|
//String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
|
||||||
|
String r = this.appView.exec(service, action, callbackId, message);
|
||||||
|
result.confirm(r == null ? "" : r);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the native->JS bridge mode.
|
||||||
|
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
|
||||||
|
try {
|
||||||
|
//this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
|
||||||
|
this.appView.setNativeToJsBridgeMode(Integer.parseInt(message));
|
||||||
|
result.confirm("");
|
||||||
|
} catch (NumberFormatException e){
|
||||||
|
result.confirm("");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polling for JavaScript messages
|
||||||
|
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
|
||||||
|
//String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message));
|
||||||
|
String r = this.appView.retrieveJsMessages("1".equals(message));
|
||||||
|
result.confirm(r == null ? "" : r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do NO-OP so older code doesn't display dialog
|
||||||
|
else if (defaultValue != null && defaultValue.equals("gap_init:")) {
|
||||||
|
result.confirm("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show dialog
|
||||||
|
else {
|
||||||
|
final JsPromptResult res = result;
|
||||||
|
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||||
|
dlg.setMessage(message);
|
||||||
|
final EditText input = new EditText(this.cordova.getActivity());
|
||||||
|
if (defaultValue != null) {
|
||||||
|
input.setText(defaultValue);
|
||||||
|
}
|
||||||
|
dlg.setView(input);
|
||||||
|
dlg.setCancelable(false);
|
||||||
|
dlg.setPositiveButton(android.R.string.ok,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
String usertext = input.getText().toString();
|
||||||
|
res.confirm(usertext);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.setNegativeButton(android.R.string.cancel,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
res.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dlg.show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle database quota exceeded notification.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
||||||
|
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
||||||
|
{
|
||||||
|
LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||||
|
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
|
||||||
|
// Expect this to not compile in a future Android release!
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public void onConsoleMessage(String message, int lineNumber, String sourceID)
|
||||||
|
{
|
||||||
|
//This is only for Android 2.1
|
||||||
|
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
|
||||||
|
{
|
||||||
|
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
||||||
|
super.onConsoleMessage(message, lineNumber, sourceID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(8)
|
||||||
|
@Override
|
||||||
|
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
||||||
|
{
|
||||||
|
if (consoleMessage.message() != null)
|
||||||
|
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||||
|
return super.onConsoleMessage(consoleMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
|
||||||
|
*
|
||||||
|
* @param origin
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
|
||||||
|
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
||||||
|
callback.invoke(origin, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API level 7 is required for this, see if we could lower this using something else
|
||||||
|
@Override
|
||||||
|
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
||||||
|
this.appView.showCustomView(view, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHideCustomView() {
|
||||||
|
this.appView.hideCustomView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* Ask the host application for a custom progress view to show while
|
||||||
|
* a <video> is loading.
|
||||||
|
* @return View The progress view.
|
||||||
|
*/
|
||||||
|
public View getVideoLoadingProgressView() {
|
||||||
|
|
||||||
|
if (mVideoProgressView == null) {
|
||||||
|
// Create a new Loading view programmatically.
|
||||||
|
|
||||||
|
// create the linear layout
|
||||||
|
LinearLayout layout = new LinearLayout(this.appView.getContext());
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||||
|
layout.setLayoutParams(layoutParams);
|
||||||
|
// the proress bar
|
||||||
|
ProgressBar bar = new ProgressBar(this.appView.getContext());
|
||||||
|
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
barLayoutParams.gravity = Gravity.CENTER;
|
||||||
|
bar.setLayoutParams(barLayoutParams);
|
||||||
|
layout.addView(bar);
|
||||||
|
|
||||||
|
mVideoProgressView = layout;
|
||||||
|
}
|
||||||
|
return mVideoProgressView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
||||||
|
this.openFileChooser(uploadMsg, "*/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
||||||
|
this.openFileChooser(uploadMsg, acceptType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
|
||||||
|
{
|
||||||
|
mUploadMessage = uploadMsg;
|
||||||
|
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
i.setType("*/*");
|
||||||
|
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
|
||||||
|
FILECHOOSER_RESULTCODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueCallback<Uri> getValueCallback() {
|
||||||
|
return this.mUploadMessage;
|
||||||
|
}
|
||||||
|
}
|
1074
framework/src/org/apache/cordova/AndroidWebView.java
Executable file
1074
framework/src/org/apache/cordova/AndroidWebView.java
Executable file
File diff suppressed because it is too large
Load Diff
496
framework/src/org/apache/cordova/AndroidWebViewClient.java
Executable file
496
framework/src/org/apache/cordova/AndroidWebViewClient.java
Executable file
@ -0,0 +1,496 @@
|
|||||||
|
/*
|
||||||
|
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.io.ByteArrayInputStream;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
import org.apache.cordova.CordovaInterface;
|
||||||
|
import org.apache.cordova.LOG;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.net.http.SslError;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.webkit.HttpAuthHandler;
|
||||||
|
import android.webkit.SslErrorHandler;
|
||||||
|
import android.webkit.WebResourceResponse;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the WebViewClient that implements callbacks for our web view.
|
||||||
|
* The kind of callbacks that happen here are regarding the rendering of the
|
||||||
|
* document instead of the chrome surrounding it, such as onPageStarted(),
|
||||||
|
* shouldOverrideUrlLoading(), etc. Related to but different than
|
||||||
|
* CordovaChromeClient.
|
||||||
|
*
|
||||||
|
* @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 implements CordovaWebViewClient{
|
||||||
|
|
||||||
|
private static final String TAG = "CordovaWebViewClient";
|
||||||
|
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
|
||||||
|
CordovaInterface cordova;
|
||||||
|
CordovaWebView appView;
|
||||||
|
private boolean doClearHistory = false;
|
||||||
|
boolean isCurrentlyLoading;
|
||||||
|
|
||||||
|
/** The authorization tokens. */
|
||||||
|
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param cordova
|
||||||
|
*/
|
||||||
|
public AndroidWebViewClient(CordovaInterface cordova) {
|
||||||
|
this.cordova = cordova;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param cordova
|
||||||
|
* @param view
|
||||||
|
*/
|
||||||
|
public AndroidWebViewClient(CordovaInterface cordova, CordovaWebView view) {
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.appView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
*/
|
||||||
|
public void setWebView(CordovaWebView view) {
|
||||||
|
this.appView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parses commands sent by setting the webView's URL to:
|
||||||
|
// cdvbrg:service/action/callbackId#jsonArgs
|
||||||
|
private void handleExecUrl(String url) {
|
||||||
|
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
|
||||||
|
int idx2 = url.indexOf('#', idx1 + 1);
|
||||||
|
int idx3 = url.indexOf('#', idx2 + 1);
|
||||||
|
int idx4 = url.indexOf('#', idx3 + 1);
|
||||||
|
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
|
||||||
|
Log.e(TAG, "Could not decode URL command: " + url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String service = url.substring(idx1, idx2);
|
||||||
|
String action = url.substring(idx2 + 1, idx3);
|
||||||
|
String callbackId = url.substring(idx3 + 1, idx4);
|
||||||
|
String jsonArgs = url.substring(idx4 + 1);
|
||||||
|
try {
|
||||||
|
appView.exec(service, action, callbackId, jsonArgs);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the host application a chance to take over the control when a new url
|
||||||
|
* is about to be loaded in the current WebView.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The url to be loaded.
|
||||||
|
* @return true to override, false for default behavior
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
// Check if it's an exec() bridge command message.
|
||||||
|
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
|
||||||
|
handleExecUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give plugins the chance to handle the url
|
||||||
|
else if (this.appView.onOverrideUrlLoading(url)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// If dialing phone (tel:5551212)
|
||||||
|
else if (url.startsWith(WebView.SCHEME_TEL)) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_DIAL);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
this.cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error dialing " + url + ": " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If displaying map (geo:0,0?q=address)
|
||||||
|
else if (url.startsWith("geo:")) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
this.cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error showing map " + url + ": " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If sending email (mailto:abc@corp.com)
|
||||||
|
else if (url.startsWith(WebView.SCHEME_MAILTO)) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
this.cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error sending email " + url + ": " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If sms:5551212?body=This is the message
|
||||||
|
else if (url.startsWith("sms:")) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
|
||||||
|
// Get address
|
||||||
|
String address = null;
|
||||||
|
int parmIndex = url.indexOf('?');
|
||||||
|
if (parmIndex == -1) {
|
||||||
|
address = url.substring(4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
address = url.substring(4, parmIndex);
|
||||||
|
|
||||||
|
// If body, then set sms body
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
String query = uri.getQuery();
|
||||||
|
if (query != null) {
|
||||||
|
if (query.startsWith("body=")) {
|
||||||
|
intent.putExtra("sms_body", query.substring(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intent.setData(Uri.parse("sms:" + address));
|
||||||
|
intent.putExtra("address", address);
|
||||||
|
intent.setType("vnd.android-dir/mms-sms");
|
||||||
|
this.cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error sending sms " + url + ":" + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Android Market
|
||||||
|
else if(url.startsWith("market:")) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
this.cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error loading Google Play Store: " + url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All else
|
||||||
|
else {
|
||||||
|
|
||||||
|
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
|
||||||
|
// Our app continues to run. When BACK is pressed, our app is redisplayed.
|
||||||
|
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not our application, let default viewer handle
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(url));
|
||||||
|
this.cordova.getActivity().startActivity(intent);
|
||||||
|
} catch (android.content.ActivityNotFoundException e) {
|
||||||
|
LOG.e(TAG, "Error loading url " + url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On received http auth request.
|
||||||
|
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* @param handler
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||||
|
|
||||||
|
// Get the authentication token
|
||||||
|
AuthenticationToken token = this.getAuthenticationToken(host, realm);
|
||||||
|
if (token != null) {
|
||||||
|
handler.proceed(token.getUserName(), token.getPassword());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Handle 401 like we'd normally do!
|
||||||
|
super.onReceivedHttpAuthRequest(view, handler, host, realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a page has started loading.
|
||||||
|
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
|
||||||
|
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
|
||||||
|
* embedded frame changes, i.e. clicking a link whose target is an iframe.
|
||||||
|
*
|
||||||
|
* @param view The webview initiating the callback.
|
||||||
|
* @param url The url of the page.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
super.onPageStarted(view, url, favicon);
|
||||||
|
isCurrentlyLoading = true;
|
||||||
|
LOG.d(TAG, "onPageStarted(" + url + ")");
|
||||||
|
// Flush stale messages.
|
||||||
|
this.appView.resetJsMessageQueue();
|
||||||
|
|
||||||
|
// Broadcast message that page has loaded
|
||||||
|
this.appView.postMessage("onPageStarted", url);
|
||||||
|
|
||||||
|
// Notify all plugins of the navigation, so they can clean up if necessary.
|
||||||
|
this.appView.onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a page has finished loading.
|
||||||
|
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param view The webview initiating the callback.
|
||||||
|
* @param url The url of the page.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
super.onPageFinished(view, url);
|
||||||
|
// Ignore excessive calls.
|
||||||
|
if (!isCurrentlyLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isCurrentlyLoading = false;
|
||||||
|
LOG.d(TAG, "onPageFinished(" + url + ")");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
||||||
|
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
|
||||||
|
* true. You see when you load a url with a # in it which is common in jQuery applications
|
||||||
|
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
|
||||||
|
*/
|
||||||
|
if (this.doClearHistory) {
|
||||||
|
view.clearHistory();
|
||||||
|
this.doClearHistory = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear timeout flag
|
||||||
|
this.appView.incUrlTimeout();
|
||||||
|
|
||||||
|
// Broadcast message that page has loaded
|
||||||
|
this.appView.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.postMessage("spinner", "stop");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown if blank loaded
|
||||||
|
if (url.equals("about:blank")) {
|
||||||
|
appView.postMessage("exit", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
||||||
|
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||||
|
* @param description A String describing the error.
|
||||||
|
* @param failingUrl The url that failed to load.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||||
|
// Ignore error due to stopLoading().
|
||||||
|
if (!isCurrentlyLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
||||||
|
|
||||||
|
// Clear timeout flag
|
||||||
|
this.appView.incUrlTimeout();
|
||||||
|
|
||||||
|
// Handle error
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
try {
|
||||||
|
data.put("errorCode", errorCode);
|
||||||
|
data.put("description", description);
|
||||||
|
data.put("url", failingUrl);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
this.appView.postMessage("onReceivedError", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that an SSL error occurred while loading a resource.
|
||||||
|
* The host application must call either handler.cancel() or handler.proceed().
|
||||||
|
* Note that the decision may be retained for use in response to future SSL errors.
|
||||||
|
* The default behavior is to cancel the load.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param handler An SslErrorHandler object that will handle the user's response.
|
||||||
|
* @param error The SSL error object.
|
||||||
|
*/
|
||||||
|
@TargetApi(8)
|
||||||
|
@Override
|
||||||
|
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||||
|
|
||||||
|
final String packageName = this.cordova.getActivity().getPackageName();
|
||||||
|
final PackageManager pm = this.cordova.getActivity().getPackageManager();
|
||||||
|
|
||||||
|
ApplicationInfo appInfo;
|
||||||
|
try {
|
||||||
|
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||||
|
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||||
|
// debug = true
|
||||||
|
handler.proceed();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// debug = false
|
||||||
|
super.onReceivedSslError(view, handler, error);
|
||||||
|
}
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
// When it doubt, lock it out!
|
||||||
|
super.onReceivedSslError(view, handler, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token.
|
||||||
|
*
|
||||||
|
* @param authenticationToken
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*/
|
||||||
|
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
||||||
|
if (host == null) {
|
||||||
|
host = "";
|
||||||
|
}
|
||||||
|
if (realm == null) {
|
||||||
|
realm = "";
|
||||||
|
}
|
||||||
|
this.authenticationTokens.put(host.concat(realm), authenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the authentication token.
|
||||||
|
*
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*
|
||||||
|
* @return the authentication token or null if did not exist
|
||||||
|
*/
|
||||||
|
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
||||||
|
return this.authenticationTokens.remove(host.concat(realm));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the authentication token.
|
||||||
|
*
|
||||||
|
* In order it tries:
|
||||||
|
* 1- host + realm
|
||||||
|
* 2- host
|
||||||
|
* 3- realm
|
||||||
|
* 4- no host, no realm
|
||||||
|
*
|
||||||
|
* @param host
|
||||||
|
* @param realm
|
||||||
|
*
|
||||||
|
* @return the authentication token
|
||||||
|
*/
|
||||||
|
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
||||||
|
AuthenticationToken token = null;
|
||||||
|
token = this.authenticationTokens.get(host.concat(realm));
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
// try with just the host
|
||||||
|
token = this.authenticationTokens.get(host);
|
||||||
|
|
||||||
|
// Try the realm
|
||||||
|
if (token == null) {
|
||||||
|
token = this.authenticationTokens.get(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no host found, just query for default
|
||||||
|
if (token == null) {
|
||||||
|
token = this.authenticationTokens.get("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all authentication tokens.
|
||||||
|
*/
|
||||||
|
public void clearAuthenticationTokens() {
|
||||||
|
this.authenticationTokens.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(CordovaWebView me, int i, String string,
|
||||||
|
String url) {
|
||||||
|
// Only deal with this if we're dealing with a proper classic webview.
|
||||||
|
if(WebView.class.isInstance(me))
|
||||||
|
{
|
||||||
|
this.onReceivedError(me, i, string, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -147,63 +147,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
|
|
||||||
private Object LOG_TAG;
|
private Object LOG_TAG;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the authentication token.
|
|
||||||
*
|
|
||||||
* @param authenticationToken
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*/
|
|
||||||
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
this.appView.viewClient.setAuthenticationToken(authenticationToken, host, realm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the authentication token.
|
|
||||||
*
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*
|
|
||||||
* @return the authentication token or null if did not exist
|
|
||||||
*/
|
|
||||||
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
return this.appView.viewClient.removeAuthenticationToken(host, realm);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the authentication token.
|
|
||||||
*
|
|
||||||
* In order it tries:
|
|
||||||
* 1- host + realm
|
|
||||||
* 2- host
|
|
||||||
* 3- realm
|
|
||||||
* 4- no host, no realm
|
|
||||||
*
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*
|
|
||||||
* @return the authentication token
|
|
||||||
*/
|
|
||||||
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
return this.appView.viewClient.getAuthenticationToken(host, realm);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all authentication tokens.
|
|
||||||
*/
|
|
||||||
public void clearAuthenticationTokens() {
|
|
||||||
if (this.appView != null && this.appView.viewClient != null) {
|
|
||||||
this.appView.viewClient.clearAuthenticationTokens();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is first created.
|
* Called when the activity is first created.
|
||||||
@ -269,7 +212,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
* require a more specialized web view.
|
* require a more specialized web view.
|
||||||
*/
|
*/
|
||||||
protected CordovaWebView makeWebView() {
|
protected CordovaWebView makeWebView() {
|
||||||
return new CordovaWebView(CordovaActivity.this);
|
return (CordovaWebView) new AndroidWebView(CordovaActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -282,9 +225,9 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
*/
|
*/
|
||||||
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
|
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
|
||||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||||
return new CordovaWebViewClient(this, webView);
|
return (CordovaWebViewClient) new AndroidWebViewClient(this, webView);
|
||||||
} else {
|
} else {
|
||||||
return new IceCreamCordovaWebViewClient(this, webView);
|
return (CordovaWebViewClient) new IceCreamCordovaWebViewClient(this, webView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +240,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
* @param webView the default constructed web view object
|
* @param webView the default constructed web view object
|
||||||
*/
|
*/
|
||||||
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
|
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
|
||||||
return new CordovaChromeClient(this, webView);
|
return (CordovaChromeClient) new AndroidChromeClient(this, webView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -335,13 +278,14 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
|
|
||||||
if (this.getBooleanProperty("DisallowOverscroll", false)) {
|
if (this.getBooleanProperty("DisallowOverscroll", false)) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
|
||||||
this.appView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER);
|
//Note: We're using the parent class, because all we know is that this will be a view
|
||||||
|
this.appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add web view but make it invisible while loading URL
|
// Add web view but make it invisible while loading URL
|
||||||
this.appView.setVisibility(View.INVISIBLE);
|
this.appView.setVisibility(View.INVISIBLE);
|
||||||
this.root.addView(this.appView);
|
this.root.addView((View) this.appView);
|
||||||
setContentView(this.root);
|
setContentView(this.root);
|
||||||
|
|
||||||
// Clear cancel flag
|
// Clear cancel flag
|
||||||
@ -408,17 +352,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
this.splashscreenTime = time;
|
this.splashscreenTime = time;
|
||||||
this.loadUrl(url);
|
this.loadUrl(url);
|
||||||
|
|
||||||
/*
|
|
||||||
// Init web view if not already done
|
|
||||||
if (this.appView == null) {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.splashscreenTime = time;
|
|
||||||
this.splashscreen = this.getIntegerProperty("SplashScreen", 0);
|
|
||||||
this.showSplashScreen(this.splashscreenTime);
|
|
||||||
this.appView.loadUrl(url, time);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -455,15 +388,6 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel loadUrl before it has been loaded.
|
|
||||||
*/
|
|
||||||
// TODO NO-OP
|
|
||||||
@Deprecated
|
|
||||||
public void cancelLoadUrl() {
|
|
||||||
this.cancelLoadUrl = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the resource cache.
|
* Clear the resource cache.
|
||||||
*/
|
*/
|
||||||
@ -711,31 +635,18 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Add services to res/xml/plugins.xml instead.
|
|
||||||
*
|
|
||||||
* Add a class that implements a service.
|
|
||||||
*
|
|
||||||
* @param serviceType
|
|
||||||
* @param className
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void addService(String serviceType, String className) {
|
|
||||||
if (this.appView != null && this.appView.pluginManager != null) {
|
|
||||||
this.appView.pluginManager.addService(serviceType, className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send JavaScript statement back to JavaScript.
|
* Send JavaScript statement back to JavaScript.
|
||||||
* (This is a convenience method)
|
* (This is a convenience method)
|
||||||
*
|
*
|
||||||
* @param statement
|
* @param statement
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public void sendJavascript(String statement) {
|
public void sendJavascript(String statement) {
|
||||||
if (this.appView != null) {
|
if (this.appView != null) {
|
||||||
this.appView.jsMessageQueue.addJavaScript(statement);
|
this.appView.addJavascript(statement);
|
||||||
|
//this.appView.jsMessageQueue.addJavaScript(statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -814,7 +725,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
super.onActivityResult(requestCode, resultCode, intent);
|
super.onActivityResult(requestCode, resultCode, intent);
|
||||||
Log.d(TAG, "Request code = " + requestCode);
|
Log.d(TAG, "Request code = " + requestCode);
|
||||||
if (appView != null && requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
|
if (appView != null && requestCode == CordovaChromeClient.FILECHOOSER_RESULTCODE) {
|
||||||
ValueCallback<Uri> mUploadMessage = this.appView.getWebChromeClient().getValueCallback();
|
ValueCallback<Uri> mUploadMessage = ((CordovaChromeClient) this.appView.getWebChromeClient()).getValueCallback();
|
||||||
Log.d(TAG, "did we get here?");
|
Log.d(TAG, "did we get here?");
|
||||||
if (null == mUploadMessage)
|
if (null == mUploadMessage)
|
||||||
return;
|
return;
|
||||||
@ -829,7 +740,8 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
if(callback == null && initCallbackClass != null) {
|
if(callback == null && initCallbackClass != null) {
|
||||||
// The application was restarted, but had defined an initial callback
|
// The application was restarted, but had defined an initial callback
|
||||||
// before being shut down.
|
// before being shut down.
|
||||||
this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
//this.activityResultCallback = appView.pluginManager.getPlugin(initCallbackClass);
|
||||||
|
this.activityResultCallback = appView.getPlugin(initCallbackClass);
|
||||||
callback = this.activityResultCallback;
|
callback = this.activityResultCallback;
|
||||||
}
|
}
|
||||||
if(callback != null) {
|
if(callback != null) {
|
||||||
|
369
framework/src/org/apache/cordova/CordovaChromeClient.java
Executable file → Normal file
369
framework/src/org/apache/cordova/CordovaChromeClient.java
Executable file → Normal file
@ -18,376 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import org.apache.cordova.CordovaInterface;
|
|
||||||
import org.apache.cordova.LOG;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup.LayoutParams;
|
|
||||||
import android.webkit.ConsoleMessage;
|
|
||||||
import android.webkit.JsPromptResult;
|
|
||||||
import android.webkit.JsResult;
|
|
||||||
import android.webkit.ValueCallback;
|
import android.webkit.ValueCallback;
|
||||||
import android.webkit.WebChromeClient;
|
|
||||||
import android.webkit.WebStorage;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.GeolocationPermissions.Callback;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
/**
|
public interface CordovaChromeClient {
|
||||||
* This class is the WebChromeClient that implements callbacks for our web view.
|
|
||||||
* The kind of callbacks that happen here are on the chrome outside the document,
|
|
||||||
* such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
|
|
||||||
* to but different than CordovaWebViewClient.
|
|
||||||
*
|
|
||||||
* @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
|
|
||||||
* @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
|
|
||||||
* @see CordovaWebViewClient
|
|
||||||
* @see CordovaWebView
|
|
||||||
*/
|
|
||||||
public class CordovaChromeClient extends WebChromeClient {
|
|
||||||
|
|
||||||
public static final int FILECHOOSER_RESULTCODE = 5173;
|
int FILECHOOSER_RESULTCODE = 0;
|
||||||
private static final String LOG_TAG = "CordovaChromeClient";
|
|
||||||
private String TAG = "CordovaLog";
|
|
||||||
private long MAX_QUOTA = 100 * 1024 * 1024;
|
|
||||||
protected CordovaInterface cordova;
|
|
||||||
protected CordovaWebView appView;
|
|
||||||
|
|
||||||
// the video progress view
|
void setWebView(CordovaWebView appView);
|
||||||
private View mVideoProgressView;
|
|
||||||
|
|
||||||
// File Chooser
|
|
||||||
public ValueCallback<Uri> mUploadMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param cordova
|
|
||||||
*/
|
|
||||||
public CordovaChromeClient(CordovaInterface cordova) {
|
|
||||||
this.cordova = cordova;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
ValueCallback<Uri> getValueCallback();
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param ctx
|
|
||||||
* @param app
|
|
||||||
*/
|
|
||||||
public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
|
|
||||||
this.cordova = ctx;
|
|
||||||
this.appView = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
public void setWebView(CordovaWebView view) {
|
|
||||||
this.appView = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the client to display a javascript alert dialog.
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
* @param url
|
|
||||||
* @param message
|
|
||||||
* @param result
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
|
||||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
|
||||||
dlg.setMessage(message);
|
|
||||||
dlg.setTitle("Alert");
|
|
||||||
//Don't let alerts break the back button
|
|
||||||
dlg.setCancelable(true);
|
|
||||||
dlg.setPositiveButton(android.R.string.ok,
|
|
||||||
new AlertDialog.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
result.confirm();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.setOnCancelListener(
|
|
||||||
new DialogInterface.OnCancelListener() {
|
|
||||||
public void onCancel(DialogInterface dialog) {
|
|
||||||
result.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
|
||||||
//DO NOTHING
|
|
||||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
|
||||||
{
|
|
||||||
result.confirm();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the client to display a confirm dialog to the user.
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
* @param url
|
|
||||||
* @param message
|
|
||||||
* @param result
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
|
||||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
|
||||||
dlg.setMessage(message);
|
|
||||||
dlg.setTitle("Confirm");
|
|
||||||
dlg.setCancelable(true);
|
|
||||||
dlg.setPositiveButton(android.R.string.ok,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
result.confirm();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.setNegativeButton(android.R.string.cancel,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
result.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.setOnCancelListener(
|
|
||||||
new DialogInterface.OnCancelListener() {
|
|
||||||
public void onCancel(DialogInterface dialog) {
|
|
||||||
result.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
|
||||||
//DO NOTHING
|
|
||||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
|
||||||
{
|
|
||||||
result.cancel();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell the client to display a prompt dialog to the user.
|
|
||||||
* If the client returns true, WebView will assume that the client will
|
|
||||||
* handle the prompt dialog and call the appropriate JsPromptResult method.
|
|
||||||
*
|
|
||||||
* Since we are hacking prompts for our own purposes, we should not be using them for
|
|
||||||
* this purpose, perhaps we should hack console.log to do this instead!
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
* @param url
|
|
||||||
* @param message
|
|
||||||
* @param defaultValue
|
|
||||||
* @param result
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
|
||||||
|
|
||||||
// Security check to make sure any requests are coming from the page initially
|
|
||||||
// loaded in webview and not another loaded in an iframe.
|
|
||||||
boolean reqOk = false;
|
|
||||||
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
|
|
||||||
reqOk = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calling PluginManager.exec() to call a native service using
|
|
||||||
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
|
|
||||||
if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
|
|
||||||
JSONArray array;
|
|
||||||
try {
|
|
||||||
array = new JSONArray(defaultValue.substring(4));
|
|
||||||
String service = array.getString(0);
|
|
||||||
String action = array.getString(1);
|
|
||||||
String callbackId = array.getString(2);
|
|
||||||
String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
|
|
||||||
result.confirm(r == null ? "" : r);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the native->JS bridge mode.
|
|
||||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
|
|
||||||
try {
|
|
||||||
this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
|
|
||||||
result.confirm("");
|
|
||||||
} catch (NumberFormatException e){
|
|
||||||
result.confirm("");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polling for JavaScript messages
|
|
||||||
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
|
|
||||||
String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message));
|
|
||||||
result.confirm(r == null ? "" : r);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do NO-OP so older code doesn't display dialog
|
|
||||||
else if (defaultValue != null && defaultValue.equals("gap_init:")) {
|
|
||||||
result.confirm("OK");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show dialog
|
|
||||||
else {
|
|
||||||
final JsPromptResult res = result;
|
|
||||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
|
||||||
dlg.setMessage(message);
|
|
||||||
final EditText input = new EditText(this.cordova.getActivity());
|
|
||||||
if (defaultValue != null) {
|
|
||||||
input.setText(defaultValue);
|
|
||||||
}
|
|
||||||
dlg.setView(input);
|
|
||||||
dlg.setCancelable(false);
|
|
||||||
dlg.setPositiveButton(android.R.string.ok,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
String usertext = input.getText().toString();
|
|
||||||
res.confirm(usertext);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.setNegativeButton(android.R.string.cancel,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
res.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dlg.show();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle database quota exceeded notification.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
|
|
||||||
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
|
|
||||||
{
|
|
||||||
LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
|
||||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
|
|
||||||
// Expect this to not compile in a future Android release!
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
|
||||||
public void onConsoleMessage(String message, int lineNumber, String sourceID)
|
|
||||||
{
|
|
||||||
//This is only for Android 2.1
|
|
||||||
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
|
|
||||||
{
|
|
||||||
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
|
||||||
super.onConsoleMessage(message, lineNumber, sourceID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(8)
|
|
||||||
@Override
|
|
||||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
|
||||||
{
|
|
||||||
if (consoleMessage.message() != null)
|
|
||||||
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
|
||||||
return super.onConsoleMessage(consoleMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
/**
|
|
||||||
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
|
|
||||||
*
|
|
||||||
* @param origin
|
|
||||||
* @param callback
|
|
||||||
*/
|
|
||||||
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
|
|
||||||
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
|
||||||
callback.invoke(origin, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// API level 7 is required for this, see if we could lower this using something else
|
|
||||||
@Override
|
|
||||||
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
|
||||||
this.appView.showCustomView(view, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHideCustomView() {
|
|
||||||
this.appView.hideCustomView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
/**
|
|
||||||
* Ask the host application for a custom progress view to show while
|
|
||||||
* a <video> is loading.
|
|
||||||
* @return View The progress view.
|
|
||||||
*/
|
|
||||||
public View getVideoLoadingProgressView() {
|
|
||||||
|
|
||||||
if (mVideoProgressView == null) {
|
|
||||||
// Create a new Loading view programmatically.
|
|
||||||
|
|
||||||
// create the linear layout
|
|
||||||
LinearLayout layout = new LinearLayout(this.appView.getContext());
|
|
||||||
layout.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
|
||||||
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
|
||||||
layout.setLayoutParams(layoutParams);
|
|
||||||
// the proress bar
|
|
||||||
ProgressBar bar = new ProgressBar(this.appView.getContext());
|
|
||||||
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
|
||||||
barLayoutParams.gravity = Gravity.CENTER;
|
|
||||||
bar.setLayoutParams(barLayoutParams);
|
|
||||||
layout.addView(bar);
|
|
||||||
|
|
||||||
mVideoProgressView = layout;
|
|
||||||
}
|
|
||||||
return mVideoProgressView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
|
|
||||||
this.openFileChooser(uploadMsg, "*/*");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
|
|
||||||
this.openFileChooser(uploadMsg, acceptType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
|
|
||||||
{
|
|
||||||
mUploadMessage = uploadMsg;
|
|
||||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
i.setType("*/*");
|
|
||||||
this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
|
|
||||||
FILECHOOSER_RESULTCODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueCallback<Uri> getValueCallback() {
|
|
||||||
return this.mUploadMessage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
1021
framework/src/org/apache/cordova/CordovaWebView.java
Executable file → Normal file
1021
framework/src/org/apache/cordova/CordovaWebView.java
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
480
framework/src/org/apache/cordova/CordovaWebViewClient.java
Executable file → Normal file
480
framework/src/org/apache/cordova/CordovaWebViewClient.java
Executable file → Normal file
@ -1,483 +1,9 @@
|
|||||||
/*
|
|
||||||
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;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
public interface CordovaWebViewClient {
|
||||||
import java.util.Hashtable;
|
|
||||||
|
|
||||||
import org.apache.cordova.CordovaInterface;
|
void setWebView(CordovaWebView appView);
|
||||||
|
|
||||||
import org.apache.cordova.LOG;
|
void onReceivedError(CordovaWebView me, int i, String string, String url);
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.net.http.SslError;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.webkit.HttpAuthHandler;
|
|
||||||
import android.webkit.SslErrorHandler;
|
|
||||||
import android.webkit.WebResourceResponse;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is the WebViewClient that implements callbacks for our web view.
|
|
||||||
* The kind of callbacks that happen here are regarding the rendering of the
|
|
||||||
* document instead of the chrome surrounding it, such as onPageStarted(),
|
|
||||||
* shouldOverrideUrlLoading(), etc. Related to but different than
|
|
||||||
* CordovaChromeClient.
|
|
||||||
*
|
|
||||||
* @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 CordovaWebViewClient extends WebViewClient {
|
|
||||||
|
|
||||||
private static final String TAG = "CordovaWebViewClient";
|
|
||||||
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
|
|
||||||
CordovaInterface cordova;
|
|
||||||
CordovaWebView appView;
|
|
||||||
private boolean doClearHistory = false;
|
|
||||||
boolean isCurrentlyLoading;
|
|
||||||
|
|
||||||
/** The authorization tokens. */
|
|
||||||
private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param cordova
|
|
||||||
*/
|
|
||||||
public CordovaWebViewClient(CordovaInterface cordova) {
|
|
||||||
this.cordova = cordova;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param cordova
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
public CordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
|
|
||||||
this.cordova = cordova;
|
|
||||||
this.appView = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
public void setWebView(CordovaWebView view) {
|
|
||||||
this.appView = view;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parses commands sent by setting the webView's URL to:
|
|
||||||
// cdvbrg:service/action/callbackId#jsonArgs
|
|
||||||
private void handleExecUrl(String url) {
|
|
||||||
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
|
|
||||||
int idx2 = url.indexOf('#', idx1 + 1);
|
|
||||||
int idx3 = url.indexOf('#', idx2 + 1);
|
|
||||||
int idx4 = url.indexOf('#', idx3 + 1);
|
|
||||||
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
|
|
||||||
Log.e(TAG, "Could not decode URL command: " + url);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String service = url.substring(idx1, idx2);
|
|
||||||
String action = url.substring(idx2 + 1, idx3);
|
|
||||||
String callbackId = url.substring(idx3 + 1, idx4);
|
|
||||||
String jsonArgs = url.substring(idx4 + 1);
|
|
||||||
appView.pluginManager.exec(service, action, callbackId, jsonArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Give the host application a chance to take over the control when a new url
|
|
||||||
* is about to be loaded in the current WebView.
|
|
||||||
*
|
|
||||||
* @param view The WebView that is initiating the callback.
|
|
||||||
* @param url The url to be loaded.
|
|
||||||
* @return true to override, false for default behavior
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
|
||||||
// Check if it's an exec() bridge command message.
|
|
||||||
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
|
|
||||||
handleExecUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give plugins the chance to handle the url
|
|
||||||
else if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// If dialing phone (tel:5551212)
|
|
||||||
else if (url.startsWith(WebView.SCHEME_TEL)) {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_DIAL);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error dialing " + url + ": " + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If displaying map (geo:0,0?q=address)
|
|
||||||
else if (url.startsWith("geo:")) {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error showing map " + url + ": " + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If sending email (mailto:abc@corp.com)
|
|
||||||
else if (url.startsWith(WebView.SCHEME_MAILTO)) {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error sending email " + url + ": " + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If sms:5551212?body=This is the message
|
|
||||||
else if (url.startsWith("sms:")) {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
|
|
||||||
// Get address
|
|
||||||
String address = null;
|
|
||||||
int parmIndex = url.indexOf('?');
|
|
||||||
if (parmIndex == -1) {
|
|
||||||
address = url.substring(4);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
address = url.substring(4, parmIndex);
|
|
||||||
|
|
||||||
// If body, then set sms body
|
|
||||||
Uri uri = Uri.parse(url);
|
|
||||||
String query = uri.getQuery();
|
|
||||||
if (query != null) {
|
|
||||||
if (query.startsWith("body=")) {
|
|
||||||
intent.putExtra("sms_body", query.substring(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
intent.setData(Uri.parse("sms:" + address));
|
|
||||||
intent.putExtra("address", address);
|
|
||||||
intent.setType("vnd.android-dir/mms-sms");
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error sending sms " + url + ":" + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Android Market
|
|
||||||
else if(url.startsWith("market:")) {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error loading Google Play Store: " + url, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All else
|
|
||||||
else {
|
|
||||||
|
|
||||||
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
|
|
||||||
// Our app continues to run. When BACK is pressed, our app is redisplayed.
|
|
||||||
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not our application, let default viewer handle
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
this.cordova.getActivity().startActivity(intent);
|
|
||||||
} catch (android.content.ActivityNotFoundException e) {
|
|
||||||
LOG.e(TAG, "Error loading url " + url, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On received http auth request.
|
|
||||||
* The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
* @param handler
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
|
||||||
|
|
||||||
// Get the authentication token
|
|
||||||
AuthenticationToken token = this.getAuthenticationToken(host, realm);
|
|
||||||
if (token != null) {
|
|
||||||
handler.proceed(token.getUserName(), token.getPassword());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Handle 401 like we'd normally do!
|
|
||||||
super.onReceivedHttpAuthRequest(view, handler, host, realm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the host application that a page has started loading.
|
|
||||||
* This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
|
|
||||||
* one time for the main frame. This also means that onPageStarted will not be called when the contents of an
|
|
||||||
* embedded frame changes, i.e. clicking a link whose target is an iframe.
|
|
||||||
*
|
|
||||||
* @param view The webview initiating the callback.
|
|
||||||
* @param url The url of the page.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
|
||||||
super.onPageStarted(view, url, favicon);
|
|
||||||
isCurrentlyLoading = true;
|
|
||||||
LOG.d(TAG, "onPageStarted(" + url + ")");
|
|
||||||
// Flush stale messages.
|
|
||||||
this.appView.jsMessageQueue.reset();
|
|
||||||
|
|
||||||
// Broadcast message that page has loaded
|
|
||||||
this.appView.postMessage("onPageStarted", url);
|
|
||||||
|
|
||||||
// Notify all plugins of the navigation, so they can clean up if necessary.
|
|
||||||
if (this.appView.pluginManager != null) {
|
|
||||||
this.appView.pluginManager.onReset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the host application that a page has finished loading.
|
|
||||||
* This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param view The webview initiating the callback.
|
|
||||||
* @param url The url of the page.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onPageFinished(WebView view, String url) {
|
|
||||||
super.onPageFinished(view, url);
|
|
||||||
// Ignore excessive calls.
|
|
||||||
if (!isCurrentlyLoading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isCurrentlyLoading = false;
|
|
||||||
LOG.d(TAG, "onPageFinished(" + url + ")");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Because of a timing issue we need to clear this history in onPageFinished as well as
|
|
||||||
* onPageStarted. However we only want to do this if the doClearHistory boolean is set to
|
|
||||||
* true. You see when you load a url with a # in it which is common in jQuery applications
|
|
||||||
* onPageStared is not called. Clearing the history at that point would break jQuery apps.
|
|
||||||
*/
|
|
||||||
if (this.doClearHistory) {
|
|
||||||
view.clearHistory();
|
|
||||||
this.doClearHistory = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear timeout flag
|
|
||||||
this.appView.loadUrlTimeout++;
|
|
||||||
|
|
||||||
// Broadcast message that page has loaded
|
|
||||||
this.appView.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.postMessage("spinner", "stop");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown if blank loaded
|
|
||||||
if (url.equals("about:blank")) {
|
|
||||||
appView.postMessage("exit", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
|
||||||
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
|
||||||
*
|
|
||||||
* @param view The WebView that is initiating the callback.
|
|
||||||
* @param errorCode The error code corresponding to an ERROR_* value.
|
|
||||||
* @param description A String describing the error.
|
|
||||||
* @param failingUrl The url that failed to load.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
|
||||||
// Ignore error due to stopLoading().
|
|
||||||
if (!isCurrentlyLoading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
|
|
||||||
|
|
||||||
// Clear timeout flag
|
|
||||||
this.appView.loadUrlTimeout++;
|
|
||||||
|
|
||||||
// Handle error
|
|
||||||
JSONObject data = new JSONObject();
|
|
||||||
try {
|
|
||||||
data.put("errorCode", errorCode);
|
|
||||||
data.put("description", description);
|
|
||||||
data.put("url", failingUrl);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
this.appView.postMessage("onReceivedError", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the host application that an SSL error occurred while loading a resource.
|
|
||||||
* The host application must call either handler.cancel() or handler.proceed().
|
|
||||||
* Note that the decision may be retained for use in response to future SSL errors.
|
|
||||||
* The default behavior is to cancel the load.
|
|
||||||
*
|
|
||||||
* @param view The WebView that is initiating the callback.
|
|
||||||
* @param handler An SslErrorHandler object that will handle the user's response.
|
|
||||||
* @param error The SSL error object.
|
|
||||||
*/
|
|
||||||
@TargetApi(8)
|
|
||||||
@Override
|
|
||||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
|
||||||
|
|
||||||
final String packageName = this.cordova.getActivity().getPackageName();
|
|
||||||
final PackageManager pm = this.cordova.getActivity().getPackageManager();
|
|
||||||
|
|
||||||
ApplicationInfo appInfo;
|
|
||||||
try {
|
|
||||||
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
|
||||||
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
|
||||||
// debug = true
|
|
||||||
handler.proceed();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// debug = false
|
|
||||||
super.onReceivedSslError(view, handler, error);
|
|
||||||
}
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
// When it doubt, lock it out!
|
|
||||||
super.onReceivedSslError(view, handler, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the authentication token.
|
|
||||||
*
|
|
||||||
* @param authenticationToken
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*/
|
|
||||||
public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
|
|
||||||
if (host == null) {
|
|
||||||
host = "";
|
|
||||||
}
|
|
||||||
if (realm == null) {
|
|
||||||
realm = "";
|
|
||||||
}
|
|
||||||
this.authenticationTokens.put(host.concat(realm), authenticationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the authentication token.
|
|
||||||
*
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*
|
|
||||||
* @return the authentication token or null if did not exist
|
|
||||||
*/
|
|
||||||
public AuthenticationToken removeAuthenticationToken(String host, String realm) {
|
|
||||||
return this.authenticationTokens.remove(host.concat(realm));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the authentication token.
|
|
||||||
*
|
|
||||||
* In order it tries:
|
|
||||||
* 1- host + realm
|
|
||||||
* 2- host
|
|
||||||
* 3- realm
|
|
||||||
* 4- no host, no realm
|
|
||||||
*
|
|
||||||
* @param host
|
|
||||||
* @param realm
|
|
||||||
*
|
|
||||||
* @return the authentication token
|
|
||||||
*/
|
|
||||||
public AuthenticationToken getAuthenticationToken(String host, String realm) {
|
|
||||||
AuthenticationToken token = null;
|
|
||||||
token = this.authenticationTokens.get(host.concat(realm));
|
|
||||||
|
|
||||||
if (token == null) {
|
|
||||||
// try with just the host
|
|
||||||
token = this.authenticationTokens.get(host);
|
|
||||||
|
|
||||||
// Try the realm
|
|
||||||
if (token == null) {
|
|
||||||
token = this.authenticationTokens.get(realm);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no host found, just query for default
|
|
||||||
if (token == null) {
|
|
||||||
token = this.authenticationTokens.get("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all authentication tokens.
|
|
||||||
*/
|
|
||||||
public void clearAuthenticationTokens() {
|
|
||||||
this.authenticationTokens.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ import android.webkit.WebResourceResponse;
|
|||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implements CordovaWebViewClient{
|
||||||
|
|
||||||
private static final String TAG = "IceCreamCordovaWebViewClient";
|
private static final String TAG = "IceCreamCordovaWebViewClient";
|
||||||
|
|
||||||
@ -97,4 +97,10 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(CordovaWebView me, int i, String string,
|
||||||
|
String url) {
|
||||||
|
super.onReceivedError(me, i, string, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,13 +88,13 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
|
|||||||
// gone away.
|
// gone away.
|
||||||
else if (height > oldHeight) {
|
else if (height > oldHeight) {
|
||||||
if (app != null)
|
if (app != null)
|
||||||
app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
|
app.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
|
||||||
}
|
}
|
||||||
// If the height as gotten smaller then we will assume the soft keyboard has
|
// If the height as gotten smaller then we will assume the soft keyboard has
|
||||||
// been displayed.
|
// been displayed.
|
||||||
else if (height < oldHeight) {
|
else if (height < oldHeight) {
|
||||||
if (app != null)
|
if (app != null)
|
||||||
app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
|
app.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the old height for the next event
|
// Update the old height for the next event
|
||||||
|
Loading…
Reference in New Issue
Block a user