mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-26 20:33:07 +08:00
Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/incubator-cordova-android
This commit is contained in:
commit
6e5ef1e819
10
README.md
10
README.md
@ -19,6 +19,16 @@ Requires
|
|||||||
- Apache ANT
|
- Apache ANT
|
||||||
- Android SDK [http://developer.android.com](http://developer.android.com)
|
- 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
|
Cordova Android Developer Tools
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<phonegap>
|
<phonegap>
|
||||||
<access origin="http://127.0.0.1*"/>
|
<!--
|
||||||
|
access elements control the Android whitelist.
|
||||||
|
Domains are assumed blocked unless set otherwise
|
||||||
|
-->
|
||||||
|
|
||||||
|
<access origin="http://127.0.0.1*"/> <!-- allow local pages -->
|
||||||
|
|
||||||
|
<!-- <access origin="https://example.com" /> allow any secure requests to example.com -->
|
||||||
|
<!-- <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www -->
|
||||||
|
<!-- <access origin=".*"/> Allow all domains, suggested development use only -->
|
||||||
|
|
||||||
<log level="DEBUG"/>
|
<log level="DEBUG"/>
|
||||||
<render legacy="true" />
|
<render legacy="true" />
|
||||||
</phonegap>
|
</phonegap>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,4 +16,5 @@
|
|||||||
<plugin name="FileTransfer" value="com.phonegap.FileTransfer"/>
|
<plugin name="FileTransfer" value="com.phonegap.FileTransfer"/>
|
||||||
<plugin name="Capture" value="com.phonegap.Capture"/>
|
<plugin name="Capture" value="com.phonegap.Capture"/>
|
||||||
<plugin name="Battery" value="com.phonegap.BatteryListener"/>
|
<plugin name="Battery" value="com.phonegap.BatteryListener"/>
|
||||||
|
<plugin name="Keyboard" value="com.phonegap.KeyboardHandler" />
|
||||||
</plugins>
|
</plugins>
|
||||||
|
294
framework/src/com/phonegap/CordovaChromeClient.java
Normal file
294
framework/src/com/phonegap/CordovaChromeClient.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
261
framework/src/com/phonegap/CordovaWebViewClient.java
Normal file
261
framework/src/com/phonegap/CordovaWebViewClient.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -191,14 +191,14 @@ public class DroidGap extends PhonegapActivity {
|
|||||||
// The base of the initial URL for our app.
|
// The base of the initial URL for our app.
|
||||||
// Does not include file name. Ends with /
|
// Does not include file name. Ends with /
|
||||||
// ie http://server/path/
|
// ie http://server/path/
|
||||||
private String baseUrl = null;
|
String baseUrl = null;
|
||||||
|
|
||||||
// Plugin to call when activity result is received
|
// Plugin to call when activity result is received
|
||||||
protected IPlugin activityResultCallback = null;
|
protected IPlugin activityResultCallback = null;
|
||||||
protected boolean activityResultKeepRunning;
|
protected boolean activityResultKeepRunning;
|
||||||
|
|
||||||
// Flag indicates that a loadUrl timeout occurred
|
// Flag indicates that a loadUrl timeout occurred
|
||||||
private int loadUrlTimeout = 0;
|
int loadUrlTimeout = 0;
|
||||||
|
|
||||||
// Default background color for activity
|
// Default background color for activity
|
||||||
// (this is not the color for the webview, which is set in HTML)
|
// (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,
|
ViewGroup.LayoutParams.FILL_PARENT,
|
||||||
1.0F));
|
1.0F));
|
||||||
|
|
||||||
this.appView.setWebChromeClient(new GapClient(DroidGap.this));
|
this.appView.setWebChromeClient(new CordovaChromeClient(DroidGap.this));
|
||||||
this.setWebViewClient(this.appView, new GapViewClient(this));
|
this.setWebViewClient(this.appView, new CordovaWebViewClient(this));
|
||||||
|
|
||||||
//14 is Ice Cream Sandwich!
|
//14 is Ice Cream Sandwich!
|
||||||
if(android.os.Build.VERSION.SDK_INT < 14 && this.classicRender)
|
if(android.os.Build.VERSION.SDK_INT < 14 && this.classicRender)
|
||||||
@ -1066,527 +1066,6 @@ public class DroidGap extends PhonegapActivity {
|
|||||||
this.spinnerDialog = null;
|
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
|
* 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.
|
* Load PhoneGap configuration from res/xml/phonegap.xml.
|
||||||
@ -1963,7 +1360,7 @@ public class DroidGap extends PhonegapActivity {
|
|||||||
* @param url
|
* @param url
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private boolean isUrlWhiteListed(String url) {
|
boolean isUrlWhiteListed(String url) {
|
||||||
|
|
||||||
// Check to see if we have matched url previously
|
// Check to see if we have matched url previously
|
||||||
if (whiteListCache.get(url) != null) {
|
if (whiteListCache.get(url) != 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);
|
|
||||||
}
|
|
||||||
}
|
|
33
framework/src/com/phonegap/KeyboardHandler.java
Normal file
33
framework/src/com/phonegap/KeyboardHandler.java
Normal file
@ -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');");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user