mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-22 00:32:55 +08:00
Merge branch 'master' into 4.0.x (Bridge fixes)
Conflicts: framework/src/org/apache/cordova/CordovaChromeClient.java framework/src/org/apache/cordova/CordovaUriHelper.java framework/src/org/apache/cordova/CordovaWebView.java framework/src/org/apache/cordova/CordovaWebViewClient.java framework/src/org/apache/cordova/ExposedJsApi.java framework/src/org/apache/cordova/NativeToJsMessageQueue.java framework/src/org/apache/cordova/PluginManager.java
This commit is contained in:
commit
4ca2305693
@ -44,6 +44,9 @@ import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class is the WebChromeClient that implements callbacks for our web view.
|
||||
@ -60,7 +63,6 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
|
||||
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;
|
||||
@ -201,68 +203,75 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
* 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
|
||||
* @see Other implementation in the Dialogs plugin.
|
||||
*/
|
||||
@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:")) {
|
||||
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
|
||||
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
|
||||
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("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);
|
||||
int bridgeSecret = array.getInt(0);
|
||||
String service = array.getString(1);
|
||||
String action = array.getString(2);
|
||||
String callbackId = array.getString(3);
|
||||
String r = appView.exposedJsApi.exec(bridgeSecret, service, action, callbackId, message);
|
||||
result.confirm(r == null ? "" : r);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
result.cancel();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// 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("");
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
|
||||
try {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
|
||||
appView.exposedJsApi.setNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
|
||||
result.cancel();
|
||||
} catch (NumberFormatException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
result.cancel();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
|
||||
try {
|
||||
String r = appView.exposedJsApi.retrieveJsMessages(bridgeSecret, "1".equals(message));
|
||||
result.confirm(r == null ? "" : r);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Do NO-OP so older code doesn't display dialog
|
||||
else if (defaultValue != null && defaultValue.equals("gap_init:")) {
|
||||
result.confirm("OK");
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
else {
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||
String startUrl = Config.getStartUrl();
|
||||
// Protect against random iframes being able to talk through the bridge.
|
||||
// Trust only file URLs and the start URL's domain.
|
||||
// The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
|
||||
if (origin.startsWith("file:") || (origin.startsWith("http") && startUrl.startsWith(origin))) {
|
||||
// Enable the bridge
|
||||
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||
appView.jsMessageQueue.setBridgeMode(bridgeMode);
|
||||
// Tell JS the bridge secret.
|
||||
int secret = appView.exposedJsApi.generateBridgeSecret();
|
||||
result.confirm(""+secret);
|
||||
} else {
|
||||
Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
|
||||
result.cancel();
|
||||
}
|
||||
} else {
|
||||
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||
final JsPromptResult res = result;
|
||||
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
|
||||
dlg.setMessage(message);
|
||||
@ -297,7 +306,7 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
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);
|
||||
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
|
||||
quotaUpdater.updateQuota(MAX_QUOTA);
|
||||
}
|
||||
|
||||
@ -310,7 +319,7 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
//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);
|
||||
LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
|
||||
super.onConsoleMessage(message, lineNumber, sourceID);
|
||||
}
|
||||
}
|
||||
@ -320,7 +329,7 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
|
||||
{
|
||||
if (consoleMessage.message() != null)
|
||||
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
|
||||
return super.onConsoleMessage(consoleMessage);
|
||||
}
|
||||
|
||||
@ -342,10 +351,10 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
this.appView.showCustomView(view, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
this.appView.hideCustomView();
|
||||
}
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
this.appView.hideCustomView();
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
@ -355,31 +364,31 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
|
||||
*/
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -27,18 +27,20 @@ import org.json.JSONException;
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
|
||||
|
||||
class AndroidExposedJsApi implements ExposedJsApi {
|
||||
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
|
||||
private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||
|
||||
public AndroidExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
verifySecret(bridgeSecret);
|
||||
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
|
||||
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
|
||||
if (arguments == null) {
|
||||
@ -49,7 +51,7 @@ public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
|
||||
try {
|
||||
// Tell the resourceApi what thread the JS is running on.
|
||||
CordovaResourceApi.jsThread = Thread.currentThread();
|
||||
|
||||
|
||||
pluginManager.exec(service, action, callbackId, arguments);
|
||||
String ret = "";
|
||||
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
|
||||
@ -63,14 +65,33 @@ public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
|
||||
jsMessageQueue.setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JavascriptInterface
|
||||
public void setNativeToJsBridgeMode(int value) {
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
verifySecret(bridgeSecret);
|
||||
jsMessageQueue.setBridgeMode(value);
|
||||
}
|
||||
|
||||
|
||||
@JavascriptInterface
|
||||
public String retrieveJsMessages(boolean fromOnlineEvent) {
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
verifySecret(bridgeSecret);
|
||||
return jsMessageQueue.popAndEncode(fromOnlineEvent);
|
||||
}
|
||||
|
||||
private void verifySecret(int value) throws IllegalAccessException {
|
||||
if (bridgeSecret < 0 || value != bridgeSecret) {
|
||||
throw new IllegalAccessException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Called on page transitions */
|
||||
void clearBridgeSecret() {
|
||||
bridgeSecret = -1;
|
||||
}
|
||||
|
||||
/** Called by cordova.js to initialize the bridge. */
|
||||
int generateBridgeSecret() {
|
||||
bridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
|
||||
return bridgeSecret;
|
||||
}
|
||||
}
|
||||
|
@ -412,17 +412,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
|
||||
this.loadUrlNow(url);
|
||||
}
|
||||
else {
|
||||
|
||||
String initUrl = this.getProperty("url", null);
|
||||
|
||||
// If first page of app, then set URL to load to be the one passed in
|
||||
if (initUrl == null) {
|
||||
this.loadUrlIntoView(url);
|
||||
}
|
||||
// Otherwise use the URL specified in the activity's extras bundle
|
||||
else {
|
||||
this.loadUrlIntoView(initUrl);
|
||||
}
|
||||
this.loadUrlIntoView(url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,16 +423,15 @@ public class AndroidWebView extends WebView implements CordovaWebView {
|
||||
* @param url
|
||||
* @param time The number of ms to wait before loading webview
|
||||
*/
|
||||
@Deprecated
|
||||
public void loadUrl(final String url, int time) {
|
||||
String initUrl = this.getProperty("url", null);
|
||||
|
||||
// If first page of app, then set URL to load to be the one passed in
|
||||
if (initUrl == null) {
|
||||
this.loadUrlIntoView(url, time);
|
||||
if(url == null)
|
||||
{
|
||||
this.loadUrlIntoView(Config.getStartUrl());
|
||||
}
|
||||
// Otherwise use the URL specified in the activity's extras bundle
|
||||
else {
|
||||
this.loadUrlIntoView(initUrl);
|
||||
else
|
||||
{
|
||||
this.loadUrlIntoView(url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1027,6 +1016,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
|
||||
boundKeyCodes.clear();
|
||||
pluginManager.onReset();
|
||||
jsMessageQueue.reset();
|
||||
exposedJsApi.clearBridgeSecret();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -21,8 +21,6 @@ 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;
|
||||
|
||||
@ -32,16 +30,15 @@ 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
|
||||
@ -56,8 +53,8 @@ import android.webkit.WebViewClient;
|
||||
*/
|
||||
public class AndroidWebViewClient extends WebViewClient implements CordovaWebViewClient{
|
||||
|
||||
private static final String TAG = "AndroidWebViewClient";
|
||||
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
|
||||
private static final String TAG = "AndroidWebViewClient";
|
||||
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
|
||||
CordovaInterface cordova;
|
||||
AndroidWebView appView;
|
||||
CordovaUriHelper helper;
|
||||
@ -98,30 +95,6 @@ public class AndroidWebViewClient extends WebViewClient implements CordovaWebVie
|
||||
helper = new CordovaUriHelper(cordova, 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.
|
||||
@ -132,115 +105,7 @@ public class AndroidWebViewClient extends WebViewClient implements CordovaWebVie
|
||||
*/
|
||||
@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;
|
||||
*/
|
||||
return helper.shouldOverrideUrlLoading(view, url);
|
||||
return helper.shouldOverrideUrlLoading(view, url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,21 +20,16 @@
|
||||
package org.apache.cordova;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cordova.LOG;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.graphics.Color;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Config {
|
||||
@ -44,6 +39,8 @@ public class Config {
|
||||
private Whitelist whitelist = new Whitelist();
|
||||
private String startUrl;
|
||||
|
||||
private static String errorUrl;
|
||||
|
||||
private static Config self = null;
|
||||
|
||||
public static void init(Activity action) {
|
||||
@ -156,6 +153,10 @@ public class Config {
|
||||
boolean value = xml.getAttributeValue(null, "value").equals("true");
|
||||
action.getIntent().putExtra(name, value);
|
||||
}
|
||||
else if(name.equalsIgnoreCase("errorurl"))
|
||||
{
|
||||
errorUrl = xml.getAttributeValue(null, "value");
|
||||
}
|
||||
else
|
||||
{
|
||||
String value = xml.getAttributeValue(null, "value");
|
||||
@ -230,4 +231,8 @@ public class Config {
|
||||
}
|
||||
return self.startUrl;
|
||||
}
|
||||
|
||||
public static String getErrorUrl() {
|
||||
return errorUrl;
|
||||
}
|
||||
}
|
||||
|
@ -608,7 +608,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
|
||||
|
||||
//Code to test CB-3064
|
||||
String errorUrl = this.getStringProperty("ErrorUrl", null);
|
||||
String errorUrl = Config.getErrorUrl();
|
||||
LOG.d(TAG, "CB-3064: The errorUrl is " + errorUrl);
|
||||
|
||||
if (this.activityState == ACTIVITY_STARTING) {
|
||||
|
@ -22,11 +22,6 @@ import android.net.Uri;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
public interface CordovaChromeClient {
|
||||
|
||||
int FILECHOOSER_RESULTCODE = 0;
|
||||
|
||||
void setWebView(CordovaWebView appView);
|
||||
|
||||
ValueCallback<Uri> getValueCallback();
|
||||
|
||||
}
|
||||
|
@ -19,17 +19,13 @@
|
||||
|
||||
package org.apache.cordova;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
public class CordovaUriHelper {
|
||||
|
||||
private static final String TAG = "CordovaUriHelper";
|
||||
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
|
||||
|
||||
private CordovaWebView appView;
|
||||
private CordovaInterface cordova;
|
||||
@ -40,32 +36,6 @@ public class CordovaUriHelper {
|
||||
cordova = cdv;
|
||||
}
|
||||
|
||||
// Parses commands sent by setting the webView's URL to:
|
||||
// cdvbrg:service/action/callbackId#jsonArgs
|
||||
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);
|
||||
//There is no reason to not send this directly to the pluginManager
|
||||
|
||||
} 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.
|
||||
@ -76,14 +46,10 @@ public class CordovaUriHelper {
|
||||
*/
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
// The WebView should support http and https when going on the Internet
|
||||
if(url.startsWith("http"))
|
||||
if(url.startsWith("http:") || url.startsWith("https:"))
|
||||
{
|
||||
// Check if it's an exec() bridge command message.
|
||||
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
|
||||
handleExecUrl(url);
|
||||
}
|
||||
// We only need to whitelist sites on the Internet!
|
||||
else if(Config.isUrlWhiteListed(url))
|
||||
if(Config.isUrlWhiteListed(url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -94,7 +60,9 @@ public class CordovaUriHelper {
|
||||
}
|
||||
else if(url.startsWith("file://") | url.startsWith("data:"))
|
||||
{
|
||||
return false;
|
||||
//This directory on WebKit/Blink based webviews contains SQLite databases!
|
||||
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
|
||||
return url.contains("app_webview");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -109,5 +77,4 @@ public class CordovaUriHelper {
|
||||
//Default behaviour should be to load the default intent, let's see what happens!
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,16 +2,11 @@ package org.apache.cordova;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.webkit.JavascriptInterface;
|
||||
|
||||
/*
|
||||
* Any exposed Javascript API MUST implement these three things!
|
||||
*/
|
||||
|
||||
public interface ExposedJsApi {
|
||||
|
||||
@JavascriptInterface
|
||||
public String exec(String service, String action, String callbackId, String arguments) throws JSONException;
|
||||
public void setNativeToJsBridgeMode(int value);
|
||||
public String retrieveJsMessages(boolean fromOnlineEvent);
|
||||
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
|
||||
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import android.webkit.WebView;
|
||||
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implements CordovaWebViewClient{
|
||||
|
||||
private static final String TAG = "IceCreamCordovaWebViewClient";
|
||||
private CordovaUriHelper helper;
|
||||
|
||||
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
|
||||
super(cordova);
|
||||
@ -47,8 +48,9 @@ public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implement
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
try {
|
||||
// Check the against the white-list.
|
||||
if ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url)) {
|
||||
// Check the against the whitelist and lock out access to the WebView directory
|
||||
// Changing this will cause problems for your application
|
||||
if (isUrlHarmful(url)) {
|
||||
LOG.w(TAG, "URL blocked by whitelist: " + url);
|
||||
// Results in a 404.
|
||||
return new WebResourceResponse("text/plain", "UTF-8", null);
|
||||
@ -74,6 +76,11 @@ public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implement
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUrlHarmful(String url) {
|
||||
return ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url))
|
||||
|| url.contains("app_webview");
|
||||
}
|
||||
|
||||
private static boolean needsKitKatContentUrlFix(Uri uri) {
|
||||
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
|
||||
}
|
||||
|
@ -35,31 +35,19 @@ import android.webkit.WebView;
|
||||
public class NativeToJsMessageQueue {
|
||||
private static final String LOG_TAG = "JsMessageQueue";
|
||||
|
||||
// This must match the default value in cordova-js/lib/android/exec.js
|
||||
private static final int DEFAULT_BRIDGE_MODE = 2;
|
||||
|
||||
// Set this to true to force plugin results to be encoding as
|
||||
// JS instead of the custom format (useful for benchmarking).
|
||||
private static final boolean FORCE_ENCODE_USING_EVAL = false;
|
||||
|
||||
// Disable URL-based exec() bridge by default since it's a bit of a
|
||||
// security concern.
|
||||
public static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
|
||||
|
||||
// Disable sending back native->JS messages during an exec() when the active
|
||||
// exec() is asynchronous. Set this to true when running bridge benchmarks.
|
||||
public static final boolean DISABLE_EXEC_CHAINING = false;
|
||||
|
||||
static final boolean DISABLE_EXEC_CHAINING = false;
|
||||
|
||||
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
|
||||
// This currently only chops up on message boundaries. It may be useful
|
||||
// to allow it to break up messages.
|
||||
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
|
||||
|
||||
/**
|
||||
* The index into registeredListeners to treat as active.
|
||||
*/
|
||||
private int activeListenerIndex;
|
||||
|
||||
/**
|
||||
* When true, the active listener is not fired upon enqueue. When set to false,
|
||||
* the active listener will be fired if the queue is non-empty.
|
||||
@ -76,6 +64,13 @@ public class NativeToJsMessageQueue {
|
||||
*/
|
||||
private final BridgeMode[] registeredListeners;
|
||||
|
||||
/**
|
||||
* When null, the bridge is disabled. This occurs during page transitions.
|
||||
* When disabled, all callbacks are dropped since they are assumed to be
|
||||
* relevant to the previous page.
|
||||
*/
|
||||
private BridgeMode activeBridgeMode;
|
||||
|
||||
private final CordovaInterface cordova;
|
||||
private final CordovaWebView webView;
|
||||
|
||||
@ -94,17 +89,19 @@ public class NativeToJsMessageQueue {
|
||||
* Changes the bridge mode.
|
||||
*/
|
||||
public void setBridgeMode(int value) {
|
||||
if (value < 0 || value >= registeredListeners.length) {
|
||||
if (value < -1 || value >= registeredListeners.length) {
|
||||
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
|
||||
} else {
|
||||
if (value != activeListenerIndex) {
|
||||
Log.d(LOG_TAG, "Set native->JS mode to " + value);
|
||||
BridgeMode newMode = value < 0 ? null : registeredListeners[value];
|
||||
if (newMode != activeBridgeMode) {
|
||||
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
|
||||
synchronized (this) {
|
||||
activeListenerIndex = value;
|
||||
BridgeMode activeListener = registeredListeners[value];
|
||||
activeListener.reset();
|
||||
if (!paused && !queue.isEmpty()) {
|
||||
activeListener.onNativeToJsMessageAvailable();
|
||||
activeBridgeMode = newMode;
|
||||
if (newMode != null) {
|
||||
newMode.reset();
|
||||
if (!paused && !queue.isEmpty()) {
|
||||
newMode.onNativeToJsMessageAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,8 +114,7 @@ public class NativeToJsMessageQueue {
|
||||
public void reset() {
|
||||
synchronized (this) {
|
||||
queue.clear();
|
||||
setBridgeMode(DEFAULT_BRIDGE_MODE);
|
||||
registeredListeners[activeListenerIndex].reset();
|
||||
setBridgeMode(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +138,10 @@ public class NativeToJsMessageQueue {
|
||||
*/
|
||||
public String popAndEncode(boolean fromOnlineEvent) {
|
||||
synchronized (this) {
|
||||
registeredListeners[activeListenerIndex].notifyOfFlush(fromOnlineEvent);
|
||||
if (activeBridgeMode == null) {
|
||||
return null;
|
||||
}
|
||||
activeBridgeMode.notifyOfFlush(fromOnlineEvent);
|
||||
if (queue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@ -247,16 +246,20 @@ public class NativeToJsMessageQueue {
|
||||
|
||||
enqueueMessage(message);
|
||||
}
|
||||
|
||||
|
||||
private void enqueueMessage(JsMessage message) {
|
||||
synchronized (this) {
|
||||
if (activeBridgeMode == null) {
|
||||
Log.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
|
||||
return;
|
||||
}
|
||||
queue.add(message);
|
||||
if (!paused) {
|
||||
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
|
||||
activeBridgeMode.onNativeToJsMessageAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setPaused(boolean value) {
|
||||
if (paused && value) {
|
||||
// This should never happen. If a use-case for it comes up, we should
|
||||
@ -266,16 +269,12 @@ public class NativeToJsMessageQueue {
|
||||
paused = value;
|
||||
if (!value) {
|
||||
synchronized (this) {
|
||||
if (!queue.isEmpty()) {
|
||||
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
|
||||
if (!queue.isEmpty() && activeBridgeMode != null) {
|
||||
activeBridgeMode.onNativeToJsMessageAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
private abstract class BridgeMode {
|
||||
abstract void onNativeToJsMessageAvailable();
|
||||
@ -308,23 +307,28 @@ public class NativeToJsMessageQueue {
|
||||
/** Uses online/offline events to tell the JS when to poll for messages. */
|
||||
private class OnlineEventsBridgeMode extends BridgeMode {
|
||||
private boolean online;
|
||||
final Runnable runnable = new Runnable() {
|
||||
private boolean ignoreNextFlush;
|
||||
|
||||
final Runnable toggleNetworkRunnable = new Runnable() {
|
||||
public void run() {
|
||||
if (!queue.isEmpty()) {
|
||||
ignoreNextFlush = false;
|
||||
webView.setNetworkAvailable(online);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@Override void reset() {
|
||||
online = false;
|
||||
// If the following call triggers a notifyOfFlush, then ignore it.
|
||||
ignoreNextFlush = true;
|
||||
webView.setNetworkAvailable(true);
|
||||
}
|
||||
@Override void onNativeToJsMessageAvailable() {
|
||||
cordova.getActivity().runOnUiThread(runnable);
|
||||
cordova.getActivity().runOnUiThread(toggleNetworkRunnable);
|
||||
}
|
||||
// Track when online/offline events are fired so that we don't fire excess events.
|
||||
@Override void notifyOfFlush(boolean fromOnlineEvent) {
|
||||
if (fromOnlineEvent) {
|
||||
if (fromOnlineEvent && !ignoreNextFlush) {
|
||||
online = !online;
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.cordova.CordovaArgs;
|
||||
import org.apache.cordova.CordovaWebView;
|
||||
import org.apache.cordova.CallbackContext;
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
@ -65,8 +62,6 @@ public class PluginManager {
|
||||
// Using <url-filter> is deprecated.
|
||||
protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
|
||||
|
||||
private AtomicInteger numPendingUiExecs;
|
||||
|
||||
private Set<String> pluginIdWhitelist;
|
||||
|
||||
/**
|
||||
@ -79,7 +74,6 @@ public class PluginManager {
|
||||
this.ctx = ctx;
|
||||
this.app = app;
|
||||
this.firstRun = true;
|
||||
this.numPendingUiExecs = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
public void setPluginIdWhitelist(Set<String> pluginIdWhitelist) {
|
||||
@ -105,9 +99,6 @@ public class PluginManager {
|
||||
this.clearPluginObjects();
|
||||
}
|
||||
|
||||
// Insert PluginManager service
|
||||
this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
|
||||
|
||||
// Start up all plugins that have onload specified
|
||||
this.startupPlugins();
|
||||
}
|
||||
@ -224,20 +215,6 @@ public class PluginManager {
|
||||
* plugin execute method.
|
||||
*/
|
||||
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
|
||||
if (numPendingUiExecs.get() > 0) {
|
||||
numPendingUiExecs.getAndIncrement();
|
||||
this.ctx.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
execHelper(service, action, callbackId, rawArgs);
|
||||
numPendingUiExecs.getAndDecrement();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
execHelper(service, action, callbackId, rawArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void execHelper(final String service, final String action, final String callbackId, final String rawArgs) {
|
||||
CordovaPlugin plugin = getPlugin(service);
|
||||
if (plugin == null) {
|
||||
Log.d(TAG, "exec() call to unknown plugin: " + service);
|
||||
@ -449,26 +426,4 @@ public class PluginManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class PluginManagerService extends CordovaPlugin {
|
||||
@Override
|
||||
public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
|
||||
if ("startup".equals(action)) {
|
||||
// The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response
|
||||
// to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible
|
||||
// that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue
|
||||
// to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening,
|
||||
// javascript always sends a "startup" exec upon loading a new page which causes all future exec calls to happen on the UI
|
||||
// thread (and hence after onPageStarted) until there are no more pending exec calls remaining.
|
||||
numPendingUiExecs.getAndIncrement();
|
||||
ctx.getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
numPendingUiExecs.getAndDecrement();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user