You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2013/01/22 02:57:58 UTC
[41/52] [partial] support for 2.4.0rc1. "vendored" the platform libs
in. added Gord and Braden as contributors. removed dependency on unzip and
axed the old download-cordova code.
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/ContactManager.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/ContactManager.java b/lib/cordova-android/framework/src/org/apache/cordova/ContactManager.java
new file mode 100755
index 0000000..8ea64a6
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/ContactManager.java
@@ -0,0 +1,122 @@
+/*
+ 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.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import android.util.Log;
+
+public class ContactManager extends CordovaPlugin {
+
+ private ContactAccessor contactAccessor;
+ private static final String LOG_TAG = "Contact Query";
+
+ public static final int UNKNOWN_ERROR = 0;
+ public static final int INVALID_ARGUMENT_ERROR = 1;
+ public static final int TIMEOUT_ERROR = 2;
+ public static final int PENDING_OPERATION_ERROR = 3;
+ public static final int IO_ERROR = 4;
+ public static final int NOT_SUPPORTED_ERROR = 5;
+ public static final int PERMISSION_DENIED_ERROR = 20;
+
+ /**
+ * Constructor.
+ */
+ public ContactManager() {
+ }
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArray of arguments for the plugin.
+ * @param callbackContext The callback context used when calling back into JavaScript.
+ * @return True if the action was valid, false otherwise.
+ */
+ public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ /**
+ * Check to see if we are on an Android 1.X device. If we are return an error as we
+ * do not support this as of Cordova 1.0.
+ */
+ if (android.os.Build.VERSION.RELEASE.startsWith("1.")) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR));
+ return true;
+ }
+
+ /**
+ * Only create the contactAccessor after we check the Android version or the program will crash
+ * older phones.
+ */
+ if (this.contactAccessor == null) {
+ this.contactAccessor = new ContactAccessorSdk5(this.webView, this.cordova);
+ }
+
+ if (action.equals("search")) {
+ final JSONArray filter = args.getJSONArray(0);
+ final JSONObject options = args.getJSONObject(1);
+ this.cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ JSONArray res = contactAccessor.search(filter, options);
+ callbackContext.success(res);
+ }
+ });
+ }
+ else if (action.equals("save")) {
+ final JSONObject contact = args.getJSONObject(0);
+ this.cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ JSONObject res = null;
+ String id = contactAccessor.save(contact);
+ if (id != null) {
+ try {
+ res = contactAccessor.getContactById(id);
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "JSON fail.", e);
+ }
+ }
+ if (res != null) {
+ callbackContext.success(res);
+ } else {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
+ }
+ }
+ });
+ }
+ else if (action.equals("remove")) {
+ final String contactId = args.getString(0);
+ this.cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ if (contactAccessor.remove(contactId)) {
+ callbackContext.success();
+ } else {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
+ }
+ }
+ });
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/CordovaArgs.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/CordovaArgs.java b/lib/cordova-android/framework/src/org/apache/cordova/CordovaArgs.java
new file mode 100644
index 0000000..3a8b7c0
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/CordovaArgs.java
@@ -0,0 +1,112 @@
+/*
+ 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.json.JSONArray;
+import org.json.JSONException;
+
+import android.util.Base64;
+
+public class CordovaArgs {
+ private JSONArray baseArgs;
+
+ public CordovaArgs(JSONArray args) {
+ this.baseArgs = args;
+ }
+
+
+ // Pass through the basics to the base args.
+ public Object get(int index) throws JSONException {
+ return baseArgs.get(index);
+ }
+
+ public boolean getBoolean(int index) throws JSONException {
+ return baseArgs.getBoolean(index);
+ }
+
+ public double getDouble(int index) throws JSONException {
+ return baseArgs.getDouble(index);
+ }
+
+ public int getInt(int index) throws JSONException {
+ return baseArgs.getInt(index);
+ }
+
+ public JSONArray getJSONArray(int index) throws JSONException {
+ return baseArgs.getJSONArray(index);
+ }
+
+ public Object getJSONObject(int index) throws JSONException {
+ return baseArgs.getJSONObject(index);
+ }
+
+ public long getLong(int index) throws JSONException {
+ return baseArgs.getLong(index);
+ }
+
+ public String getString(int index) throws JSONException {
+ return baseArgs.getString(index);
+ }
+
+
+ public Object opt(int index) {
+ return baseArgs.opt(index);
+ }
+
+ public boolean optBoolean(int index) {
+ return baseArgs.optBoolean(index);
+ }
+
+ public double optDouble(int index) {
+ return baseArgs.optDouble(index);
+ }
+
+ public int optInt(int index) {
+ return baseArgs.optInt(index);
+ }
+
+ public JSONArray optJSONArray(int index) {
+ return baseArgs.optJSONArray(index);
+ }
+
+ public Object optJSONObject(int index) {
+ return baseArgs.optJSONObject(index);
+ }
+
+ public long optLong(int index) {
+ return baseArgs.optLong(index);
+ }
+
+ public String optString(int index) {
+ return baseArgs.optString(index);
+ }
+
+ public boolean isNull(int index) {
+ return baseArgs.isNull(index);
+ }
+
+
+ // The interesting custom helpers.
+ public byte[] getArrayBuffer(int index) throws JSONException {
+ String encoded = baseArgs.getString(index);
+ return Base64.decode(encoded, Base64.DEFAULT);
+ }
+}
+
+
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/CordovaChromeClient.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/CordovaChromeClient.java b/lib/cordova-android/framework/src/org/apache/cordova/CordovaChromeClient.java
new file mode 100755
index 0000000..4b30894
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/CordovaChromeClient.java
@@ -0,0 +1,404 @@
+/*
+ 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.api.CordovaInterface;
+import org.apache.cordova.api.LOG;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+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.
+ */
+public class CordovaChromeClient extends WebChromeClient {
+
+ 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;
+ private CordovaInterface cordova;
+ private CordovaWebView appView;
+
+ // the video progress view
+ private View mVideoProgressView;
+
+ // File Chooser
+ public ValueCallback<Uri> mUploadMessage;
+
+ /**
+ * Constructor.
+ *
+ * @param cordova
+ */
+ public CordovaChromeClient(CordovaInterface cordova) {
+ this.cordova = cordova;
+ }
+
+ /**
+ * 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.create();
+ 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.create();
+ 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://") || url.indexOf(this.appView.baseUrl) == 0 || 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();
+ }
+ }
+
+ // Sets the native->JS bridge mode.
+ else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
+ this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
+ result.confirm("");
+ }
+
+ // Polling for JavaScript messages
+ else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
+ String r = this.appView.exposedJsApi.retrieveJsMessages();
+ 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.create();
+ dlg.show();
+ }
+ return true;
+ }
+
+ /**
+ * Handle database quota exceeded notification.
+ *
+ * @param url
+ * @param databaseIdentifier
+ * @param currentQuota
+ * @param estimatedSize
+ * @param totalUsedQuota
+ * @param quotaUpdater
+ */
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
+ long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
+ {
+ LOG.d(TAG, "DroidGap: onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
+
+ if (estimatedSize < MAX_QUOTA)
+ {
+ //increase for 1Mb
+ long newQuota = estimatedSize;
+ LOG.d(TAG, "calling quotaUpdater.updateQuota newQuota: %d", newQuota);
+ quotaUpdater.updateQuota(newQuota);
+ }
+ else
+ {
+ // Set the quota to whatever it is and force an error
+ // TODO: get docs on how to handle this properly
+ quotaUpdater.updateQuota(currentQuota);
+ }
+ }
+
+ // 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, 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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/CordovaLocationListener.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/CordovaLocationListener.java b/lib/cordova-android/framework/src/org/apache/cordova/CordovaLocationListener.java
new file mode 100755
index 0000000..7b7a9f7
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/CordovaLocationListener.java
@@ -0,0 +1,207 @@
+/*
+ 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cordova.api.CallbackContext;
+
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class CordovaLocationListener implements LocationListener {
+ public static int PERMISSION_DENIED = 1;
+ public static int POSITION_UNAVAILABLE = 2;
+ public static int TIMEOUT = 3;
+
+ protected LocationManager locationManager;
+ private GeoBroker owner;
+ protected boolean running = false;
+
+ public HashMap<String, CallbackContext> watches = new HashMap<String, CallbackContext>();
+ private List<CallbackContext> callbacks = new ArrayList<CallbackContext>();
+
+ private String TAG = "[Cordova Location Listener]";
+
+ public CordovaLocationListener(LocationManager manager, GeoBroker broker, String tag) {
+ this.locationManager = manager;
+ this.owner = broker;
+ this.TAG = tag;
+ }
+
+ protected void fail(int code, String message) {
+ for (CallbackContext callbackContext: this.callbacks)
+ {
+ this.owner.fail(code, message, callbackContext);
+ }
+ if(this.owner.isGlobalListener(this))
+ {
+ Log.d(TAG, "Stopping global listener");
+ this.stop();
+ }
+ this.callbacks.clear();
+
+ Iterator<CallbackContext> it = this.watches.values().iterator();
+ while (it.hasNext()) {
+ this.owner.fail(code, message, it.next());
+ }
+ }
+
+ private void win(Location loc) {
+ for (CallbackContext callbackContext: this.callbacks)
+ {
+ this.owner.win(loc, callbackContext);
+ }
+ if(this.owner.isGlobalListener(this))
+ {
+ Log.d(TAG, "Stopping global listener");
+ this.stop();
+ }
+ this.callbacks.clear();
+
+ Iterator<CallbackContext> it = this.watches.values().iterator();
+ while (it.hasNext()) {
+ this.owner.win(loc, it.next());
+ }
+ }
+
+ /**
+ * Location Listener Methods
+ */
+
+ /**
+ * Called when the provider is disabled by the user.
+ *
+ * @param provider
+ */
+ public void onProviderDisabled(String provider) {
+ Log.d(TAG, "Location provider '" + provider + "' disabled.");
+ this.fail(POSITION_UNAVAILABLE, "GPS provider disabled.");
+ }
+
+ /**
+ * Called when the provider is enabled by the user.
+ *
+ * @param provider
+ */
+ public void onProviderEnabled(String provider) {
+ Log.d(TAG, "Location provider "+ provider + " has been enabled");
+ }
+
+ /**
+ * Called when the provider status changes. This method is called when a
+ * provider is unable to fetch a location or if the provider has recently
+ * become available after a period of unavailability.
+ *
+ * @param provider
+ * @param status
+ * @param extras
+ */
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ Log.d(TAG, "The status of the provider " + provider + " has changed");
+ if (status == 0) {
+ Log.d(TAG, provider + " is OUT OF SERVICE");
+ this.fail(CordovaLocationListener.POSITION_UNAVAILABLE, "Provider " + provider + " is out of service.");
+ }
+ else if (status == 1) {
+ Log.d(TAG, provider + " is TEMPORARILY_UNAVAILABLE");
+ }
+ else {
+ Log.d(TAG, provider + " is AVAILABLE");
+ }
+ }
+
+ /**
+ * Called when the location has changed.
+ *
+ * @param location
+ */
+ public void onLocationChanged(Location location) {
+ Log.d(TAG, "The location has been updated!");
+ this.win(location);
+ }
+
+ // PUBLIC
+
+ public int size() {
+ return this.watches.size() + this.callbacks.size();
+ }
+
+ public void addWatch(String timerId, CallbackContext callbackContext) {
+ this.watches.put(timerId, callbackContext);
+ if (this.size() == 1) {
+ this.start();
+ }
+ }
+ public void addCallback(CallbackContext callbackContext) {
+ this.callbacks.add(callbackContext);
+ if (this.size() == 1) {
+ this.start();
+ }
+ }
+ public void clearWatch(String timerId) {
+ if (this.watches.containsKey(timerId)) {
+ this.watches.remove(timerId);
+ }
+ if (this.size() == 0) {
+ this.stop();
+ }
+ }
+
+ /**
+ * Destroy listener.
+ */
+ public void destroy() {
+ this.stop();
+ }
+
+ // LOCAL
+
+ /**
+ * Start requesting location updates.
+ *
+ * @param interval
+ */
+ protected void start() {
+ if (!this.running) {
+ if (this.locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
+ this.running = true;
+ this.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 60000, 10, this);
+ } else {
+ this.fail(CordovaLocationListener.POSITION_UNAVAILABLE, "Network provider is not available.");
+ }
+ }
+ }
+
+ /**
+ * Stop receiving location updates.
+ */
+ private void stop() {
+ if (this.running) {
+ this.locationManager.removeUpdates(this);
+ this.running = false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebView.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebView.java b/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebView.java
new file mode 100755
index 0000000..44c9299
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebView.java
@@ -0,0 +1,985 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+package org.apache.cordova;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Stack;
+
+import org.apache.cordova.Config;
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.LOG;
+import org.apache.cordova.api.PluginManager;
+import org.apache.cordova.api.PluginResult;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.webkit.WebBackForwardList;
+import android.webkit.WebHistoryItem;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebSettings.LayoutAlgorithm;
+import android.widget.FrameLayout;
+
+public class CordovaWebView extends WebView {
+
+ public static final String TAG = "CordovaWebView";
+
+ private ArrayList<Integer> keyDownCodes = new ArrayList<Integer>();
+ private ArrayList<Integer> keyUpCodes = new ArrayList<Integer>();
+
+ public PluginManager pluginManager;
+ private boolean paused;
+
+ private BroadcastReceiver receiver;
+
+
+ /** Activities and other important classes **/
+ private CordovaInterface cordova;
+ CordovaWebViewClient viewClient;
+ @SuppressWarnings("unused")
+ private CordovaChromeClient chromeClient;
+
+ //This is for the polyfill history
+ private String url;
+ String baseUrl;
+ private Stack<String> urls = new Stack<String>();
+
+ boolean useBrowserHistory = true;
+
+ // Flag to track that a loadUrl timeout occurred
+ int loadUrlTimeout = 0;
+
+ private boolean bound;
+
+ private boolean handleButton = false;
+
+ private long lastMenuEventTime = 0;
+
+ NativeToJsMessageQueue jsMessageQueue;
+ ExposedJsApi exposedJsApi;
+
+ /** custom view created by the browser (a video player for example) */
+ private View mCustomView;
+ private WebChromeClient.CustomViewCallback mCustomViewCallback;
+
+ private ActivityResult mResult = null;
+
+ class ActivityResult {
+
+ int request;
+ int result;
+ Intent incoming;
+
+ public ActivityResult(int req, int res, Intent intent) {
+ request = req;
+ result = res;
+ incoming = intent;
+ }
+
+
+ }
+
+ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ Gravity.CENTER);
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ */
+ public CordovaWebView(Context context) {
+ super(context);
+ if (CordovaInterface.class.isInstance(context))
+ {
+ this.cordova = (CordovaInterface) context;
+ }
+ else
+ {
+ Log.d(TAG, "Your activity must implement CordovaInterface to work");
+ }
+ this.loadConfiguration();
+ this.setup();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * @param attrs
+ */
+ public CordovaWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (CordovaInterface.class.isInstance(context))
+ {
+ this.cordova = (CordovaInterface) context;
+ }
+ else
+ {
+ Log.d(TAG, "Your activity must implement CordovaInterface to work");
+ }
+ this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
+ this.initWebViewClient(this.cordova);
+ this.loadConfiguration();
+ this.setup();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * @param attrs
+ * @param defStyle
+ *
+ */
+ public CordovaWebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (CordovaInterface.class.isInstance(context))
+ {
+ this.cordova = (CordovaInterface) context;
+ }
+ else
+ {
+ Log.d(TAG, "Your activity must implement CordovaInterface to work");
+ }
+ this.setWebChromeClient(new CordovaChromeClient(this.cordova, this));
+ this.loadConfiguration();
+ this.setup();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * @param attrs
+ * @param defStyle
+ * @param privateBrowsing
+ */
+ @TargetApi(11)
+ public CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
+ super(context, attrs, defStyle, privateBrowsing);
+ if (CordovaInterface.class.isInstance(context))
+ {
+ this.cordova = (CordovaInterface) context;
+ }
+ else
+ {
+ Log.d(TAG, "Your activity must implement CordovaInterface to work");
+ }
+ this.setWebChromeClient(new CordovaChromeClient(this.cordova));
+ this.initWebViewClient(this.cordova);
+ this.loadConfiguration();
+ this.setup();
+ }
+
+
+ private void initWebViewClient(CordovaInterface cordova) {
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
+ {
+ this.setWebViewClient(new CordovaWebViewClient(this.cordova, this));
+ }
+ else
+ {
+ this.setWebViewClient(new IceCreamCordovaWebViewClient(this.cordova, this));
+ }
+ }
+
+ /**
+ * Initialize webview.
+ */
+ @SuppressWarnings("deprecation")
+ @SuppressLint("NewApi")
+ private void setup() {
+ this.setInitialScale(0);
+ this.setVerticalScrollBarEnabled(false);
+ this.requestFocusFromTouch();
+
+ // Enable JavaScript
+ WebSettings settings = this.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
+ settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
+
+ // Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
+ try {
+ Method gingerbread_getMethod = WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
+ {
+ gingerbread_getMethod.invoke(settings, true);
+ }
+ } catch (NoSuchMethodException e) {
+ Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Doing the NavDump failed with bad arguments");
+ } catch (IllegalAccessException e) {
+ Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
+ } catch (InvocationTargetException e) {
+ Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
+ }
+
+ // Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
+ // while we do this
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ Level16Apis.enableUniversalAccess(settings);
+ // Enable database
+ settings.setDatabaseEnabled(true);
+ String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
+ settings.setDatabasePath(databasePath);
+
+ // Enable DOM storage
+ settings.setDomStorageEnabled(true);
+
+ // Enable built-in geolocation
+ settings.setGeolocationEnabled(true);
+
+ // Fix for CB-1405
+ // Google issue 4641
+ this.updateUserAgentString();
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ if (this.receiver == null) {
+ this.receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateUserAgentString();
+ }
+ };
+ this.cordova.getActivity().registerReceiver(this.receiver, intentFilter);
+ }
+ // end CB-1405
+
+ pluginManager = new PluginManager(this, this.cordova);
+ jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
+ exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
+ exposeJsInterface();
+ }
+
+ private void updateUserAgentString() {
+ this.getSettings().getUserAgentString();
+ }
+
+ private void exposeJsInterface() {
+ int SDK_INT = Build.VERSION.SDK_INT;
+ boolean isHoneycomb = (SDK_INT >= Build.VERSION_CODES.HONEYCOMB && SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
+ if (isHoneycomb || (SDK_INT < Build.VERSION_CODES.GINGERBREAD)) {
+ Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
+ // Bug being that Java Strings do not get converted to JS strings automatically.
+ // This isn't hard to work-around on the JS side, but it's easier to just
+ // use the prompt bridge instead.
+ return;
+ } else if (SDK_INT < Build.VERSION_CODES.HONEYCOMB && Build.MANUFACTURER.equals("unknown")) {
+ // addJavascriptInterface crashes on the 2.3 emulator.
+ Log.i(TAG, "Disabled addJavascriptInterface() bridge callback due to a bug on the 2.3 emulator");
+ return;
+ }
+ this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
+ }
+
+ /**
+ * Set the WebViewClient.
+ *
+ * @param client
+ */
+ public void setWebViewClient(CordovaWebViewClient client) {
+ this.viewClient = client;
+ super.setWebViewClient(client);
+ }
+
+ /**
+ * Set the WebChromeClient.
+ *
+ * @param client
+ */
+ public void setWebChromeClient(CordovaChromeClient client) {
+ this.chromeClient = client;
+ super.setWebChromeClient(client);
+ }
+
+ public CordovaChromeClient getWebChromeClient() {
+ return this.chromeClient;
+ }
+
+ /**
+ * Load the url into the webview.
+ *
+ * @param url
+ */
+ @Override
+ public void loadUrl(String url) {
+ if (url.equals("about:blank") || url.startsWith("javascript:")) {
+ 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.urls.size() > 0)) {
+ this.loadUrlIntoView(url);
+ }
+ // Otherwise use the URL specified in the activity's extras bundle
+ else {
+ this.loadUrlIntoView(initUrl);
+ }
+ }
+ }
+
+ /**
+ * Load the url into the webview after waiting for period of time.
+ * This is used to display the splashscreen for certain amount of time.
+ *
+ * @param url
+ * @param time The number of ms to wait before loading webview
+ */
+ 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.urls.size() > 0)) {
+ this.loadUrlIntoView(url, time);
+ }
+ // Otherwise use the URL specified in the activity's extras bundle
+ else {
+ this.loadUrlIntoView(initUrl);
+ }
+ }
+
+ /**
+ * Load the url into the webview.
+ *
+ * @param url
+ */
+ public void loadUrlIntoView(final String url) {
+ LOG.d(TAG, ">>> loadUrl(" + url + ")");
+
+ this.url = url;
+ if (this.baseUrl == null) {
+ int i = url.lastIndexOf('/');
+ if (i > 0) {
+ this.baseUrl = url.substring(0, i + 1);
+ }
+ else {
+ this.baseUrl = this.url + "/";
+ }
+
+ this.pluginManager.init();
+
+ if (!this.useBrowserHistory) {
+ this.urls.push(url);
+ }
+ }
+
+ // Create a timeout timer for loadUrl
+ final CordovaWebView me = this;
+ final int currentLoadUrlTimeout = me.loadUrlTimeout;
+ final int loadUrlTimeoutValue = Integer.parseInt(this.getProperty("loadUrlTimeoutValue", "20000"));
+
+ // Timeout error method
+ final Runnable loadError = new Runnable() {
+ public void run() {
+ me.stopLoading();
+ LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
+ if (viewClient != null) {
+ viewClient.onReceivedError(me, -6, "The connection to the server was unsuccessful.", url);
+ }
+ }
+ };
+
+ // Timeout timer method
+ final Runnable timeoutCheck = new Runnable() {
+ public void run() {
+ try {
+ synchronized (this) {
+ wait(loadUrlTimeoutValue);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ // If timeout, then stop loading and handle error
+ if (me.loadUrlTimeout == currentLoadUrlTimeout) {
+ me.cordova.getActivity().runOnUiThread(loadError);
+ }
+ }
+ };
+
+ // Load url
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ Thread thread = new Thread(timeoutCheck);
+ thread.start();
+ me.loadUrlNow(url);
+ }
+ });
+ }
+
+ /**
+ * Load URL in webview.
+ *
+ * @param url
+ */
+ void loadUrlNow(String url) {
+ if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
+ LOG.d(TAG, ">>> loadUrlNow()");
+ }
+ if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
+ super.loadUrl(url);
+ }
+ }
+
+ /**
+ * Load the url into the webview after waiting for period of time.
+ * This is used to display the splashscreen for certain amount of time.
+ *
+ * @param url
+ * @param time The number of ms to wait before loading webview
+ */
+ public void loadUrlIntoView(final String url, final int time) {
+
+ // If not first page of app, then load immediately
+ // Add support for browser history if we use it.
+ if ((url.startsWith("javascript:")) || this.urls.size() > 0 || this.canGoBack()) {
+ }
+
+ // If first page, then show splashscreen
+ else {
+
+ LOG.d(TAG, "DroidGap.loadUrl(%s, %d)", url, time);
+
+ // Send message to show splashscreen now if desired
+ this.postMessage("splashscreen", "show");
+ }
+
+ // Load url
+ this.loadUrlIntoView(url);
+ }
+
+ /**
+ * Send JavaScript statement back to JavaScript.
+ * (This is a convenience method)
+ *
+ * @param message
+ */
+ public void sendJavascript(String statement) {
+ this.jsMessageQueue.addJavaScript(statement);
+ }
+
+ /**
+ * Send a plugin result back to JavaScript.
+ * (This is a convenience method)
+ *
+ * @param message
+ */
+ public void sendPluginResult(PluginResult result, String callbackId) {
+ this.jsMessageQueue.addPluginResult(result, callbackId);
+ }
+
+ /**
+ * Send a message to all plugins.
+ *
+ * @param id The message id
+ * @param data The message data
+ */
+ public void postMessage(String id, Object data) {
+ if (this.pluginManager != null) {
+ this.pluginManager.postMessage(id, data);
+ }
+ }
+
+ /**
+ * Returns the top url on the stack without removing it from
+ * the stack.
+ */
+ public String peekAtUrlStack() {
+ if (this.urls.size() > 0) {
+ return this.urls.peek();
+ }
+ return "";
+ }
+
+ /**
+ * Add a url to the stack
+ *
+ * @param url
+ */
+ public void pushUrl(String url) {
+ this.urls.push(url);
+ }
+
+ /**
+ * Go to previous page in history. (We manage our own history)
+ *
+ * @return true if we went back, false if we are already at top
+ */
+ public boolean backHistory() {
+
+ // Check webview first to see if there is a history
+ // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
+ if (super.canGoBack()) {
+ printBackForwardList();
+ super.goBack();
+
+ return true;
+ }
+
+ // If our managed history has prev url
+ if (this.urls.size() > 1 && !this.useBrowserHistory) {
+ this.urls.pop(); // Pop current url
+ String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl()
+ this.loadUrl(url);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return true if there is a history item.
+ *
+ * @return
+ */
+ public boolean canGoBack() {
+ if (super.canGoBack()) {
+ return true;
+ }
+ if (this.urls.size() > 1) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Load the specified URL in the Cordova webview or a new browser instance.
+ *
+ * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
+ *
+ * @param url The url to load.
+ * @param openExternal Load url in browser instead of Cordova webview.
+ * @param clearHistory Clear the history stack, so new page becomes top of history
+ * @param params DroidGap parameters for new app
+ */
+ public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
+ LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
+
+ // If clearing history
+ if (clearHistory) {
+ this.clearHistory();
+ }
+
+ // If loading into our webview
+ if (!openExternal) {
+
+ // Make sure url is in whitelist
+ if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
+ // TODO: What about params?
+
+ // Clear out current url from history, since it will be replacing it
+ if (clearHistory) {
+ this.urls.clear();
+ }
+
+ // Load new URL
+ this.loadUrl(url);
+ }
+ // Load in default viewer if not
+ else {
+ LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL=" + url + ")");
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(TAG, "Error loading url " + url, e);
+ }
+ }
+ }
+
+ // Load in default view intent
+ else {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(TAG, "Error loading url " + url, e);
+ }
+ }
+ }
+
+ /**
+ * Check configuration parameters from Config.
+ * Approved list of URLs that can be loaded into DroidGap
+ * <access origin="http://server regexp" subdomains="true" />
+ * Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
+ * <log level="DEBUG" />
+ */
+ private void loadConfiguration() {
+ // Config has already been loaded, and it stores these preferences on the Intent.
+ if("false".equals(this.getProperty("useBrowserHistory", "true")))
+ {
+ //Switch back to the old browser history and state the six month policy
+ this.useBrowserHistory = false;
+ Log.w(TAG, "useBrowserHistory=false is deprecated as of Cordova 2.2.0 and will be removed six months after the 2.2.0 release. Please use the browser history and use history.back().");
+ }
+
+ if ("true".equals(this.getProperty("fullscreen", "false"))) {
+ this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+ }
+
+ /**
+ * Get string property for activity.
+ *
+ * @param name
+ * @param defaultValue
+ * @return
+ */
+ public String getProperty(String name, String defaultValue) {
+ Bundle bundle = this.cordova.getActivity().getIntent().getExtras();
+ if (bundle == null) {
+ return defaultValue;
+ }
+ Object p = bundle.get(name);
+ if (p == null) {
+ return defaultValue;
+ }
+ return p.toString();
+ }
+
+ /*
+ * onKeyDown
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ if(keyDownCodes.contains(keyCode))
+ {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ // only override default behavior is event bound
+ LOG.d(TAG, "Down Key Hit");
+ this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
+ return true;
+ }
+ // If volumeup key
+ else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ LOG.d(TAG, "Up Key Hit");
+ this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
+ return true;
+ }
+ else
+ {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+ else if(keyCode == KeyEvent.KEYCODE_BACK)
+ {
+ //Because exit is fired on the keyDown and not the key up on Android 4.x
+ //we need to check for this.
+ //Also, I really wished "canGoBack" worked!
+ if(this.useBrowserHistory)
+ return !(this.startOfHistory()) || this.bound;
+ else
+ return this.urls.size() > 1 || this.bound;
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ // If back key
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // A custom view is currently displayed (e.g. playing a video)
+ if(mCustomView != null) {
+ this.hideCustomView();
+ } else {
+ // The webview is currently displayed
+ // If back key is bound, then send event to JavaScript
+ if (this.bound) {
+ this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
+ return true;
+ } else {
+ // If not bound
+ // Go to previous page in webview if it is possible to go back
+ if (this.backHistory()) {
+ return true;
+ }
+ // If not, then invoke default behaviour
+ else {
+ //this.activityState = ACTIVITY_EXITING;
+ return false;
+ }
+ }
+ }
+ }
+ // Legacy
+ else if (keyCode == KeyEvent.KEYCODE_MENU) {
+ if (this.lastMenuEventTime < event.getEventTime()) {
+ this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
+ }
+ this.lastMenuEventTime = event.getEventTime();
+ return super.onKeyUp(keyCode, event);
+ }
+ // If search key
+ else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
+ return true;
+ }
+ else if(keyUpCodes.contains(keyCode))
+ {
+ //What the hell should this do?
+ return super.onKeyUp(keyCode, event);
+ }
+
+ //Does webkit change this behavior?
+ return super.onKeyUp(keyCode, event);
+ }
+
+
+ public void bindButton(boolean override)
+ {
+ this.bound = override;
+ }
+
+ public void bindButton(String button, boolean override) {
+ // TODO Auto-generated method stub
+ if (button.compareTo("volumeup")==0) {
+ keyDownCodes.add(KeyEvent.KEYCODE_VOLUME_UP);
+ }
+ else if (button.compareTo("volumedown")==0) {
+ keyDownCodes.add(KeyEvent.KEYCODE_VOLUME_DOWN);
+ }
+ }
+
+ public void bindButton(int keyCode, boolean keyDown, boolean override) {
+ if(keyDown)
+ {
+ keyDownCodes.add(keyCode);
+ }
+ else
+ {
+ keyUpCodes.add(keyCode);
+ }
+ }
+
+ public boolean isBackButtonBound()
+ {
+ return this.bound;
+ }
+
+ public void handlePause(boolean keepRunning)
+ {
+ LOG.d(TAG, "Handle the pause");
+ // Send pause event to JavaScript
+ this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
+
+ // Forward to plugins
+ if (this.pluginManager != null) {
+ this.pluginManager.onPause(keepRunning);
+ }
+
+ // If app doesn't want to run in background
+ if (!keepRunning) {
+ // Pause JavaScript timers (including setInterval)
+ this.pauseTimers();
+ }
+ paused = true;
+
+ }
+
+ public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
+ {
+
+ this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
+
+ // Forward to plugins
+ if (this.pluginManager != null) {
+ this.pluginManager.onResume(keepRunning);
+ }
+
+ // Resume JavaScript timers (including setInterval)
+ this.resumeTimers();
+ paused = false;
+ }
+
+ public void handleDestroy()
+ {
+ // Send destroy event to JavaScript
+ this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
+
+ // Load blank page so that JavaScript onunload is called
+ this.loadUrl("about:blank");
+
+ // Forward to plugins
+ if (this.pluginManager != null) {
+ this.pluginManager.onDestroy();
+ }
+
+ // unregister the receiver
+ if (this.receiver != null) {
+ try {
+ this.cordova.getActivity().unregisterReceiver(this.receiver);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ public void onNewIntent(Intent intent)
+ {
+ //Forward to plugins
+ if (this.pluginManager != null) {
+ this.pluginManager.onNewIntent(intent);
+ }
+ }
+
+ public boolean isPaused()
+ {
+ return paused;
+ }
+
+ public boolean hadKeyEvent() {
+ return handleButton;
+ }
+
+ // Wrapping these functions in their own class prevents warnings in adb like:
+ // VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
+ @TargetApi(16)
+ private static class Level16Apis {
+ static void enableUniversalAccess(WebSettings settings) {
+ settings.setAllowUniversalAccessFromFileURLs(true);
+ }
+ }
+
+ public void printBackForwardList() {
+ WebBackForwardList currentList = this.copyBackForwardList();
+ int currentSize = currentList.getSize();
+ for(int i = 0; i < currentSize; ++i)
+ {
+ WebHistoryItem item = currentList.getItemAtIndex(i);
+ String url = item.getUrl();
+ LOG.d(TAG, "The URL at index: " + Integer.toString(i) + "is " + url );
+ }
+ }
+
+
+ //Can Go Back is BROKEN!
+ public boolean startOfHistory()
+ {
+ WebBackForwardList currentList = this.copyBackForwardList();
+ WebHistoryItem item = currentList.getItemAtIndex(0);
+ String url = item.getUrl();
+ String currentUrl = this.getUrl();
+ LOG.d(TAG, "The current URL is: " + currentUrl);
+ LOG.d(TAG, "The URL at item 0 is:" + url);
+ return currentUrl.equals(url);
+ }
+
+ public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
+ // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
+ Log.d(TAG, "showing Custom View");
+ // if a view already exists then immediately terminate the new one
+ if (mCustomView != null) {
+ callback.onCustomViewHidden();
+ return;
+ }
+
+ // Store the view and its callback for later (to kill it properly)
+ mCustomView = view;
+ mCustomViewCallback = callback;
+
+ // Add the custom view to its container.
+ ViewGroup parent = (ViewGroup) this.getParent();
+ parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
+
+ // Hide the content view.
+ this.setVisibility(View.GONE);
+
+ // Finally show the custom view container.
+ parent.setVisibility(View.VISIBLE);
+ parent.bringToFront();
+ }
+
+ public void hideCustomView() {
+ // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
+ Log.d(TAG, "Hidding Custom View");
+ if (mCustomView == null) return;
+
+ // Hide the custom view.
+ mCustomView.setVisibility(View.GONE);
+
+ // Remove the custom view from its container.
+ ViewGroup parent = (ViewGroup) this.getParent();
+ parent.removeView(mCustomView);
+ mCustomView = null;
+ mCustomViewCallback.onCustomViewHidden();
+
+ // Show the content view.
+ this.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * if the video overlay is showing then we need to know
+ * as it effects back button handling
+ *
+ * @return
+ */
+ public boolean isCustomViewShowing() {
+ return mCustomView != null;
+ }
+
+ public WebBackForwardList restoreState(Bundle savedInstanceState)
+ {
+ WebBackForwardList myList = super.restoreState(savedInstanceState);
+ Log.d(TAG, "WebView restoration crew now restoring!");
+ //Initialize the plugin manager once more
+ this.pluginManager.init();
+ return myList;
+ }
+
+ public void storeResult(int requestCode, int resultCode, Intent intent) {
+ mResult = new ActivityResult(requestCode, resultCode, intent);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java b/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
new file mode 100755
index 0000000..7a7e6ed
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
@@ -0,0 +1,481 @@
+/*
+ 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.util.Hashtable;
+
+import org.apache.cordova.api.CordovaInterface;
+
+import org.apache.cordova.api.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.WebView;
+import android.webkit.WebViewClient;
+
+/**
+ * This class is the WebViewClient that implements callbacks for our web view.
+ */
+public class CordovaWebViewClient extends WebViewClient {
+
+ private static final String TAG = "Cordova";
+ private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
+ CordovaInterface cordova;
+ CordovaWebView appView;
+ private boolean doClearHistory = false;
+
+ /** 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());
+ }
+ }
+
+ // 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:") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
+ //This will fix iFrames
+ if (appView.useBrowserHistory || url.startsWith("data:"))
+ return false;
+ else
+ this.appView.loadUrl(url);
+ }
+
+ // 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());
+ }
+ }
+
+ /**
+ * 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) {
+ // Clear history so history.back() doesn't do anything.
+ // So we can reinit() native side CallbackServer & PluginManager.
+ if (!this.appView.useBrowserHistory) {
+ view.clearHistory();
+ this.doClearHistory = true;
+ }
+
+ // 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);
+ 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++;
+
+ // Try firing the onNativeReady event in JS. If it fails because the JS is
+ // not loaded yet then just set a flag so that the onNativeReady can be fired
+ // from the JS side when the JS gets to that code.
+ if (!url.equals("about:blank")) {
+ LOG.d(TAG, "Trying to fire onNativeReady");
+ this.appView.loadUrl("javascript:try{ cordova.require('cordova/channel').onNativeReady.fire();}catch(e){_nativeReady = true;}");
+ this.appView.postMessage("onNativeReady", null);
+ }
+
+ // 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) {
+ 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);
+ }
+ }
+
+ /**
+ * Notify the host application to update its visited links database.
+ *
+ * @param view The WebView that is initiating the callback.
+ * @param url The url being visited.
+ * @param isReload True if this url is being reloaded.
+ */
+ @Override
+ public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
+ /*
+ * If you do a document.location.href the url does not get pushed on the stack
+ * so we do a check here to see if the url should be pushed.
+ */
+ if (!this.appView.peekAtUrlStack().equals(url)) {
+ this.appView.pushUrl(url);
+ }
+ }
+
+ /**
+ * 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();
+ }
+
+}