diff --git a/README.md b/README.md
index 1f615532..ea999ffb 100755
--- a/README.md
+++ b/README.md
@@ -19,6 +19,16 @@ Requires
- Apache ANT
- Android SDK [http://developer.android.com](http://developer.android.com)
+
+Building
+---
+
+To create your phonegap.jar, run in the framework directory:
+
+ android update project -p . -t android-15
+ ant jar
+
+
Cordova Android Developer Tools
---
diff --git a/framework/res/xml/phonegap.xml b/framework/res/xml/phonegap.xml
index f57d9ab5..69e48763 100644
--- a/framework/res/xml/phonegap.xml
+++ b/framework/res/xml/phonegap.xml
@@ -1,6 +1,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/framework/res/xml/plugins.xml b/framework/res/xml/plugins.xml
index 73169370..4d84f599 100644
--- a/framework/res/xml/plugins.xml
+++ b/framework/res/xml/plugins.xml
@@ -16,4 +16,5 @@
+
diff --git a/framework/src/com/phonegap/CordovaChromeClient.java b/framework/src/com/phonegap/CordovaChromeClient.java
new file mode 100644
index 00000000..54f7b811
--- /dev/null
+++ b/framework/src/com/phonegap/CordovaChromeClient.java
@@ -0,0 +1,294 @@
+package com.phonegap;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.phonegap.api.LOG;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.KeyEvent;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+import android.webkit.GeolocationPermissions.Callback;
+import android.widget.EditText;
+
+
+public class CordovaChromeClient extends WebChromeClient {
+
+
+ private String TAG = "PhoneGapLog";
+ private long MAX_QUOTA = 100 * 1024 * 1024;
+ private DroidGap ctx;
+
+ /**
+ * Constructor.
+ *
+ * @param ctx
+ */
+ public CordovaChromeClient(Context ctx) {
+ this.ctx = (DroidGap) ctx;
+ }
+
+ /**
+ * 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.ctx);
+ 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.confirm();
+ }
+ });
+ 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.ctx);
+ 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.ctx.baseUrl) == 0 || ctx.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);
+ boolean async = array.getBoolean(3);
+ String r = ctx.pluginManager.exec(service, action, callbackId, message, async);
+ result.confirm(r);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Polling for JavaScript messages
+ else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
+ String r = ctx.callbackServer.getJavascript();
+ result.confirm(r);
+ }
+
+ // Calling into CallbackServer
+ else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
+ String r = "";
+ if (message.equals("usePolling")) {
+ r = ""+ ctx.callbackServer.usePolling();
+ }
+ else if (message.equals("restartServer")) {
+ ctx.callbackServer.restartServer();
+ }
+ else if (message.equals("getPort")) {
+ r = Integer.toString(ctx.callbackServer.getPort());
+ }
+ else if (message.equals("getToken")) {
+ r = ctx.callbackServer.getToken();
+ }
+ result.confirm(r);
+ }
+
+ // PhoneGap JS has initialized, so show webview
+ // (This solves white flash seen when rendering HTML)
+ else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) {
+ ctx.appView.setVisibility(View.VISIBLE);
+ ctx.spinnerStop();
+ result.confirm("OK");
+ }
+
+ // Show dialog
+ else {
+ final JsPromptResult res = result;
+ AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx);
+ dlg.setMessage(message);
+ final EditText input = new EditText(this.ctx);
+ 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
+ @Override
+ public void onConsoleMessage(String message, int lineNumber, String sourceID)
+ {
+ LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
+ super.onConsoleMessage(message, lineNumber, sourceID);
+ }
+
+ @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);
+ }
+
+
+}
diff --git a/framework/src/com/phonegap/CordovaWebViewClient.java b/framework/src/com/phonegap/CordovaWebViewClient.java
new file mode 100644
index 00000000..c7121fa9
--- /dev/null
+++ b/framework/src/com/phonegap/CordovaWebViewClient.java
@@ -0,0 +1,261 @@
+package com.phonegap;
+
+import com.phonegap.api.LOG;
+
+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.view.View;
+import android.webkit.HttpAuthHandler;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+public class CordovaWebViewClient extends WebViewClient {
+
+ private static final String TAG = "Cordova";
+ DroidGap ctx;
+
+ /**
+ * Constructor.
+ *
+ * @param ctx
+ */
+ public CordovaWebViewClient(DroidGap ctx) {
+ this.ctx = ctx;
+ }
+
+ /**
+ * 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) {
+
+ // First give any plugins the chance to handle the url themselves
+ if (this.ctx.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));
+ ctx.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));
+ ctx.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));
+ ctx.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");
+ ctx.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 phonegap 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.indexOf(this.ctx.baseUrl) == 0 || ctx.isUrlWhiteListed(url)) {
+ this.ctx.loadUrl(url);
+ }
+
+ // If not our application, let default viewer handle
+ else {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ ctx.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
+ * the view
+ * @param handler
+ * the handler
+ * @param host
+ * the host
+ * @param realm
+ * the realm
+ */
+ @Override
+ public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
+ String realm) {
+
+ // get the authentication token
+ AuthenticationToken token = ctx.getAuthenticationToken(host,realm);
+
+ if(token != null) {
+ handler.proceed(token.getUserName(), token.getPassword());
+ }
+ }
+
+
+ @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.
+ view.clearHistory();
+ }
+
+ /**
+ * Notify the host application that a page has finished loading.
+ *
+ * @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);
+
+ // Clear timeout flag
+ this.ctx.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")) {
+ ctx.appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}");
+ }
+
+ // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly
+ if (ctx.appView.getVisibility() == View.INVISIBLE) {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(2000);
+ ctx.runOnUiThread(new Runnable() {
+ public void run() {
+ ctx.appView.setVisibility(View.VISIBLE);
+ ctx.spinnerStop();
+ }
+ });
+ } catch (InterruptedException e) {
+ }
+ }
+ });
+ t.start();
+ }
+
+
+ // Shutdown if blank loaded
+ if (url.equals("about:blank")) {
+ if (this.ctx.callbackServer != null) {
+ this.ctx.callbackServer.destroy();
+ }
+ this.ctx.endActivity();
+ }
+ }
+
+ /**
+ * 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, "DroidGap: GapViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
+
+ // Clear timeout flag
+ this.ctx.loadUrlTimeout++;
+
+ // Stop "app loading" spinner if showing
+ this.ctx.spinnerStop();
+
+ // Handle error
+ this.ctx.onReceivedError(errorCode, description, failingUrl);
+ }
+
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+
+ final String packageName = this.ctx.getPackageName();
+ final PackageManager pm = this.ctx.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);
+ }
+ }
+}
diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java
index 59e4a15d..edfb4d34 100755
--- a/framework/src/com/phonegap/DroidGap.java
+++ b/framework/src/com/phonegap/DroidGap.java
@@ -191,14 +191,14 @@ public class DroidGap extends PhonegapActivity {
// The base of the initial URL for our app.
// Does not include file name. Ends with /
// ie http://server/path/
- private String baseUrl = null;
+ String baseUrl = null;
// Plugin to call when activity result is received
protected IPlugin activityResultCallback = null;
protected boolean activityResultKeepRunning;
// Flag indicates that a loadUrl timeout occurred
- private int loadUrlTimeout = 0;
+ int loadUrlTimeout = 0;
// Default background color for activity
// (this is not the color for the webview, which is set in HTML)
@@ -375,8 +375,8 @@ public class DroidGap extends PhonegapActivity {
ViewGroup.LayoutParams.FILL_PARENT,
1.0F));
- this.appView.setWebChromeClient(new GapClient(DroidGap.this));
- this.setWebViewClient(this.appView, new GapViewClient(this));
+ this.appView.setWebChromeClient(new CordovaChromeClient(DroidGap.this));
+ this.setWebViewClient(this.appView, new CordovaWebViewClient(this));
//14 is Ice Cream Sandwich!
if(android.os.Build.VERSION.SDK_INT < 14 && this.classicRender)
@@ -1066,527 +1066,6 @@ public class DroidGap extends PhonegapActivity {
this.spinnerDialog = null;
}
}
-
- /**
- * Set the chrome handler.
- */
- public class GapClient extends WebChromeClient {
-
- private String TAG = "PhoneGapLog";
- private long MAX_QUOTA = 100 * 1024 * 1024;
- private DroidGap ctx;
-
- /**
- * Constructor.
- *
- * @param ctx
- */
- public GapClient(Context ctx) {
- this.ctx = (DroidGap)ctx;
- }
-
- /**
- * 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.ctx);
- 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.confirm();
- }
- });
- 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.ctx);
- 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.ctx.baseUrl) == 0 || 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);
- boolean async = array.getBoolean(3);
- String r = pluginManager.exec(service, action, callbackId, message, async);
- result.confirm(r);
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
-
- // Polling for JavaScript messages
- else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
- String r = callbackServer.getJavascript();
- result.confirm(r);
- }
-
- // Calling into CallbackServer
- else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) {
- String r = "";
- if (message.equals("usePolling")) {
- r = ""+callbackServer.usePolling();
- }
- else if (message.equals("restartServer")) {
- callbackServer.restartServer();
- }
- else if (message.equals("getPort")) {
- r = Integer.toString(callbackServer.getPort());
- }
- else if (message.equals("getToken")) {
- r = callbackServer.getToken();
- }
- result.confirm(r);
- }
-
- // PhoneGap JS has initialized, so show webview
- // (This solves white flash seen when rendering HTML)
- else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) {
- appView.setVisibility(View.VISIBLE);
- ctx.spinnerStop();
- result.confirm("OK");
- }
-
- // Show dialog
- else {
- final JsPromptResult res = result;
- AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx);
- dlg.setMessage(message);
- final EditText input = new EditText(this.ctx);
- 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
- @Override
- public void onConsoleMessage(String message, int lineNumber, String sourceID)
- {
- LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
- super.onConsoleMessage(message, lineNumber, sourceID);
- }
-
- @Override
- public boolean onConsoleMessage(ConsoleMessage consoleMessage)
- {
- 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);
- }
-
- }
-
- /**
- * The webview client receives notifications about appView
- */
- public class GapViewClient extends WebViewClient {
-
- DroidGap ctx;
-
- /**
- * Constructor.
- *
- * @param ctx
- */
- public GapViewClient(DroidGap ctx) {
- this.ctx = ctx;
- }
-
- /**
- * 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) {
-
- // First give any plugins the chance to handle the url themselves
- if (this.ctx.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));
- 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));
- 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));
- 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");
- 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 phonegap 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.indexOf(this.ctx.baseUrl) == 0 || isUrlWhiteListed(url)) {
- this.ctx.loadUrl(url);
- }
-
- // If not our application, let default viewer handle
- else {
- try {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(url));
- 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
- * the view
- * @param handler
- * the handler
- * @param host
- * the host
- * @param realm
- * the realm
- */
- @Override
- public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
- String realm) {
-
- // get the authentication token
- AuthenticationToken token = getAuthenticationToken(host,realm);
-
- if(token != null) {
- handler.proceed(token.getUserName(), token.getPassword());
- }
- }
-
-
- @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.
- view.clearHistory();
- }
-
- /**
- * Notify the host application that a page has finished loading.
- *
- * @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);
-
- // Clear timeout flag
- this.ctx.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")) {
- appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}");
- }
-
- // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly
- if (appView.getVisibility() == View.INVISIBLE) {
- Thread t = new Thread(new Runnable() {
- public void run() {
- try {
- Thread.sleep(2000);
- ctx.runOnUiThread(new Runnable() {
- public void run() {
- appView.setVisibility(View.VISIBLE);
- ctx.spinnerStop();
- }
- });
- } catch (InterruptedException e) {
- }
- }
- });
- t.start();
- }
-
-
- // Shutdown if blank loaded
- if (url.equals("about:blank")) {
- if (this.ctx.callbackServer != null) {
- this.ctx.callbackServer.destroy();
- }
- this.ctx.endActivity();
- }
- }
-
- /**
- * 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, "DroidGap: GapViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
-
- // Clear timeout flag
- this.ctx.loadUrlTimeout++;
-
- // Stop "app loading" spinner if showing
- this.ctx.spinnerStop();
-
- // Handle error
- this.ctx.onReceivedError(errorCode, description, failingUrl);
- }
-
- public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-
- final String packageName = this.ctx.getPackageName();
- final PackageManager pm = this.ctx.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);
- }
- }
- }
/**
* End this activity by calling finish for activity
@@ -1774,88 +1253,6 @@ public class DroidGap extends PhonegapActivity {
});
}
- /**
- * We are providing this class to detect when the soft keyboard is shown
- * and hidden in the web view.
- */
- class LinearLayoutSoftKeyboardDetect extends LinearLayout {
-
- private static final String TAG = "SoftKeyboardDetect";
-
- private int oldHeight = 0; // Need to save the old height as not to send redundant events
- private int oldWidth = 0; // Need to save old width for orientation change
- private int screenWidth = 0;
- private int screenHeight = 0;
-
- public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
- super(context);
- screenWidth = width;
- screenHeight = height;
- }
-
- @Override
- /**
- * Start listening to new measurement events. Fire events when the height
- * gets smaller fire a show keyboard event and when height gets bigger fire
- * a hide keyboard event.
- *
- * Note: We are using callbackServer.sendJavascript() instead of
- * this.appView.loadUrl() as changing the URL of the app would cause the
- * soft keyboard to go away.
- *
- * @param widthMeasureSpec
- * @param heightMeasureSpec
- */
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- LOG.v(TAG, "We are in our onMeasure method");
-
- // Get the current height of the visible part of the screen.
- // This height will not included the status bar.
- int height = MeasureSpec.getSize(heightMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
-
- LOG.v(TAG, "Old Height = %d", oldHeight);
- LOG.v(TAG, "Height = %d", height);
- LOG.v(TAG, "Old Width = %d", oldWidth);
- LOG.v(TAG, "Width = %d", width);
-
- // If the oldHeight = 0 then this is the first measure event as the app starts up.
- // If oldHeight == height then we got a measurement change that doesn't affect us.
- if (oldHeight == 0 || oldHeight == height) {
- LOG.d(TAG, "Ignore this event");
- }
- // Account for orientation change and ignore this event/Fire orientation change
- else if(screenHeight == width)
- {
- int tmp_var = screenHeight;
- screenHeight = screenWidth;
- screenWidth = tmp_var;
- LOG.v(TAG, "Orientation Change");
- }
- // If the height as gotten bigger then we will assume the soft keyboard has
- // gone away.
- else if (height > oldHeight) {
- if (callbackServer != null) {
- LOG.v(TAG, "Throw hide keyboard event");
- callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');");
- }
- }
- // If the height as gotten smaller then we will assume the soft keyboard has
- // been displayed.
- else if (height < oldHeight) {
- if (callbackServer != null) {
- LOG.v(TAG, "Throw show keyboard event");
- callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');");
- }
- }
-
- // Update the old height for the next event
- oldHeight = height;
- oldWidth = width;
- }
- }
/**
* Load PhoneGap configuration from res/xml/phonegap.xml.
@@ -1963,7 +1360,7 @@ public class DroidGap extends PhonegapActivity {
* @param url
* @return
*/
- private boolean isUrlWhiteListed(String url) {
+ boolean isUrlWhiteListed(String url) {
// Check to see if we have matched url previously
if (whiteListCache.get(url) != null) {
diff --git a/framework/src/com/phonegap/GapView.java b/framework/src/com/phonegap/GapView.java
deleted file mode 100644
index 6ac3b20c..00000000
--- a/framework/src/com/phonegap/GapView.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.phonegap;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.WebView;
-import android.widget.EditText;
-
-public class GapView extends WebView {
-
- public GapView(Context context) {
- super(context);
- // TODO Auto-generated constructor stub
- }
-
- @Override
- protected void attachViewToParent(View child, int index, ViewGroup.LayoutParams params)
- {
- if(child.getClass() != EditText.class)
- super.attachViewToParent(child, index, params);
- else
- {
- super.attachViewToParent(child, index, params);
- }
- }
-
- @Override
- protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params)
- {
- return super.addViewInLayout(child, index, params);
- }
-
- @Override
- protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params, boolean preventRequestLayout)
- {
- return super.addViewInLayout(child, index, params);
- }
-}
diff --git a/framework/src/com/phonegap/KeyboardHandler.java b/framework/src/com/phonegap/KeyboardHandler.java
new file mode 100644
index 00000000..d10988cf
--- /dev/null
+++ b/framework/src/com/phonegap/KeyboardHandler.java
@@ -0,0 +1,33 @@
+package com.phonegap;
+
+import org.json.JSONArray;
+
+import com.phonegap.api.Plugin;
+import com.phonegap.api.PluginResult;
+
+public class KeyboardHandler extends Plugin {
+
+
+ /*
+ * This will never be called!
+ * (non-Javadoc)
+ * @see com.phonegap.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String)
+ */
+ @Override
+ public PluginResult execute(String action, JSONArray args, String callbackId) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public void onMessage(String id, Object data)
+ {
+ if(id.equals("keyboardHidden"))
+ {
+ super.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');");
+ }
+ else if(id.equals("keyboardVisible"))
+ {
+ super.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');");
+ }
+ }
+}
diff --git a/framework/src/com/phonegap/LinearLayoutSoftKeyboardDetect.java b/framework/src/com/phonegap/LinearLayoutSoftKeyboardDetect.java
new file mode 100644
index 00000000..1ef6001b
--- /dev/null
+++ b/framework/src/com/phonegap/LinearLayoutSoftKeyboardDetect.java
@@ -0,0 +1,84 @@
+package com.phonegap;
+import com.phonegap.api.LOG;
+
+import android.content.Context;
+import android.view.View.MeasureSpec;
+import android.widget.LinearLayout;
+
+
+public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
+
+ private static final String TAG = "SoftKeyboardDetect";
+
+ private int oldHeight = 0; // Need to save the old height as not to send redundant events
+ private int oldWidth = 0; // Need to save old width for orientation change
+ private int screenWidth = 0;
+ private int screenHeight = 0;
+ private DroidGap app = null;
+
+ public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
+ super(context);
+ screenWidth = width;
+ screenHeight = height;
+ app = (DroidGap) context;
+ }
+
+ @Override
+ /**
+ * Start listening to new measurement events. Fire events when the height
+ * gets smaller fire a show keyboard event and when height gets bigger fire
+ * a hide keyboard event.
+ *
+ * Note: We are using app.postMessage so that this is more compatible with the API
+ *
+ * @param widthMeasureSpec
+ * @param heightMeasureSpec
+ */
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ LOG.v(TAG, "We are in our onMeasure method");
+
+ // Get the current height of the visible part of the screen.
+ // This height will not included the status bar.\
+ int width, height;
+
+ height = MeasureSpec.getSize(heightMeasureSpec);
+ width = MeasureSpec.getSize(widthMeasureSpec);
+ LOG.v(TAG, "Old Height = %d", oldHeight);
+ LOG.v(TAG, "Height = %d", height);
+ LOG.v(TAG, "Old Width = %d", oldWidth);
+ LOG.v(TAG, "Width = %d", width);
+
+ // If the oldHeight = 0 then this is the first measure event as the app starts up.
+ // If oldHeight == height then we got a measurement change that doesn't affect us.
+ if (oldHeight == 0 || oldHeight == height) {
+ LOG.d(TAG, "Ignore this event");
+ }
+ // Account for orientation change and ignore this event/Fire orientation change
+ else if(screenHeight == width)
+ {
+ int tmp_var = screenHeight;
+ screenHeight = screenWidth;
+ screenWidth = tmp_var;
+ LOG.v(TAG, "Orientation Change");
+ }
+ // If the height as gotten bigger then we will assume the soft keyboard has
+ // gone away.
+ else if (height > oldHeight) {
+ if(app != null)
+ app.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');");
+ }
+ // If the height as gotten smaller then we will assume the soft keyboard has
+ // been displayed.
+ else if (height < oldHeight) {
+ if(app != null)
+ app.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');");
+ }
+
+ // Update the old height for the next event
+ oldHeight = height;
+ oldWidth = width;
+ }
+
+}