blob: a326da2ef8970a2cfa0f941f9680697857199d33 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Browser;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import com.android.internal.R;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class is a proxy class for handling WebCore -> UI thread messaging. All
* the callback functions are called from the WebCore thread and messages are
* posted to the UI thread for the actual client callback.
*/
/*
* This class is created in the UI thread so its handler and any private classes
* that extend Handler will operate in the UI thread.
*/
class CallbackProxy extends Handler {
// Logging tag
private static final String LOGTAG = "CallbackProxy";
// Instance of WebViewClient that is the client callback.
private volatile WebViewClient mWebViewClient;
// Instance of WebChromeClient for handling all chrome functions.
private volatile WebChromeClient mWebChromeClient;
// Instance of WebViewClassic for handling UI requests.
private final WebViewClassic mWebView;
// Client registered callback listener for download events
private volatile DownloadListener mDownloadListener;
// Keep track of multiple progress updates.
private boolean mProgressUpdatePending;
// Keep track of the last progress amount.
// Start with 100 to indicate it is not in load for the empty page.
private volatile int mLatestProgress = 100;
// Back/Forward list
private final WebBackForwardListClassic mBackForwardList;
// Back/Forward list client
private volatile WebBackForwardListClient mWebBackForwardListClient;
// Used to call startActivity during url override.
private final Context mContext;
// block messages flag for destroy
private boolean mBlockMessages;
// Message IDs
private static final int PAGE_STARTED = 100;
private static final int RECEIVED_ICON = 101;
private static final int RECEIVED_TITLE = 102;
private static final int OVERRIDE_URL = 103;
private static final int AUTH_REQUEST = 104;
private static final int SSL_ERROR = 105;
private static final int PROGRESS = 106;
private static final int UPDATE_VISITED = 107;
private static final int LOAD_RESOURCE = 108;
private static final int CREATE_WINDOW = 109;
private static final int CLOSE_WINDOW = 110;
private static final int SAVE_PASSWORD = 111;
private static final int JS_ALERT = 112;
private static final int JS_CONFIRM = 113;
private static final int JS_PROMPT = 114;
private static final int JS_UNLOAD = 115;
private static final int ASYNC_KEYEVENTS = 116;
private static final int DOWNLOAD_FILE = 118;
private static final int REPORT_ERROR = 119;
private static final int RESEND_POST_DATA = 120;
private static final int PAGE_FINISHED = 121;
private static final int REQUEST_FOCUS = 122;
private static final int SCALE_CHANGED = 123;
private static final int RECEIVED_CERTIFICATE = 124;
private static final int SWITCH_OUT_HISTORY = 125;
private static final int EXCEEDED_DATABASE_QUOTA = 126;
private static final int REACHED_APPCACHE_MAXSIZE = 127;
private static final int JS_TIMEOUT = 128;
private static final int ADD_MESSAGE_TO_CONSOLE = 129;
private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130;
private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
private static final int RECEIVED_TOUCH_ICON_URL = 132;
private static final int GET_VISITED_HISTORY = 133;
private static final int OPEN_FILE_CHOOSER = 134;
private static final int ADD_HISTORY_ITEM = 135;
private static final int HISTORY_INDEX_CHANGED = 136;
private static final int AUTH_CREDENTIALS = 137;
private static final int AUTO_LOGIN = 140;
private static final int CLIENT_CERT_REQUEST = 141;
private static final int PROCEEDED_AFTER_SSL_ERROR = 144;
// Message triggered by the client to resume execution
private static final int NOTIFY = 200;
// Result transportation object for returning results across thread
// boundaries.
private static class ResultTransport<E> {
// Private result object
private E mResult;
public ResultTransport(E defaultResult) {
mResult = defaultResult;
}
public synchronized void setResult(E result) {
mResult = result;
}
public synchronized E getResult() {
return mResult;
}
}
private class JsResultReceiver implements JsResult.ResultReceiver {
// This prevents a user from interacting with the result before WebCore is
// ready to handle it.
private boolean mReady;
// Tells us if the user tried to confirm or cancel the result before WebCore
// is ready.
private boolean mTriedToNotifyBeforeReady;
public JsPromptResult mJsResult = new JsPromptResult(this);
final void setReady() {
mReady = true;
if (mTriedToNotifyBeforeReady) {
notifyCallbackProxy();
}
}
/* Wake up the WebCore thread. */
@Override
public void onJsResultComplete(JsResult result) {
if (mReady) {
notifyCallbackProxy();
} else {
mTriedToNotifyBeforeReady = true;
}
}
private void notifyCallbackProxy() {
synchronized (CallbackProxy.this) {
CallbackProxy.this.notify();
}
}
}
/**
* Construct a new CallbackProxy.
*/
public CallbackProxy(Context context, WebViewClassic w) {
// Used to start a default activity.
mContext = context;
mWebView = w;
mBackForwardList = new WebBackForwardListClassic(this);
}
protected synchronized void blockMessages() {
mBlockMessages = true;
}
protected synchronized boolean messagesBlocked() {
return mBlockMessages;
}
protected void shutdown() {
removeCallbacksAndMessages(null);
setWebViewClient(null);
setWebChromeClient(null);
}
/**
* Set the WebViewClient.
* @param client An implementation of WebViewClient.
*/
public void setWebViewClient(WebViewClient client) {
mWebViewClient = client;
}
/**
* Get the WebViewClient.
* @return the current WebViewClient instance.
*/
public WebViewClient getWebViewClient() {
return mWebViewClient;
}
/**
* Set the WebChromeClient.
* @param client An implementation of WebChromeClient.
*/
public void setWebChromeClient(WebChromeClient client) {
mWebChromeClient = client;
}
/**
* Get the WebChromeClient.
* @return the current WebChromeClient instance.
*/
public WebChromeClient getWebChromeClient() {
return mWebChromeClient;
}
/**
* Set the client DownloadListener.
* @param client An implementation of DownloadListener.
*/
public void setDownloadListener(DownloadListener client) {
mDownloadListener = client;
}
/**
* Get the Back/Forward list to return to the user or to update the cached
* history list.
*/
public WebBackForwardListClassic getBackForwardList() {
return mBackForwardList;
}
void setWebBackForwardListClient(WebBackForwardListClient client) {
mWebBackForwardListClient = client;
}
WebBackForwardListClient getWebBackForwardListClient() {
return mWebBackForwardListClient;
}
/**
* Called by the UI side. Calling overrideUrlLoading from the WebCore
* side will post a message to call this method.
*/
public boolean uiOverrideUrlLoading(String overrideUrl) {
if (overrideUrl == null || overrideUrl.length() == 0) {
return false;
}
boolean override = false;
if (mWebViewClient != null) {
override = mWebViewClient.shouldOverrideUrlLoading(mWebView.getWebView(),
overrideUrl);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(overrideUrl));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// If another application is running a WebView and launches the
// Browser through this Intent, we want to reuse the same window if
// possible.
intent.putExtra(Browser.EXTRA_APPLICATION_ID,
mContext.getPackageName());
try {
mContext.startActivity(intent);
override = true;
} catch (ActivityNotFoundException ex) {
// If no application can handle the URL, assume that the
// browser can handle it.
}
}
return override;
}
/**
* Called by UI side.
*/
public boolean uiOverrideKeyEvent(KeyEvent event) {
if (mWebViewClient != null) {
return mWebViewClient.shouldOverrideKeyEvent(mWebView.getWebView(), event);
}
return false;
}
@Override
public void handleMessage(Message msg) {
// We don't have to do synchronization because this function operates
// in the UI thread. The WebViewClient and WebChromeClient functions
// that check for a non-null callback are ok because java ensures atomic
// 32-bit reads and writes.
if (messagesBlocked()) return;
switch (msg.what) {
case PAGE_STARTED:
String startedUrl = msg.getData().getString("url");
mWebView.onPageStarted(startedUrl);
if (mWebViewClient != null) {
mWebViewClient.onPageStarted(mWebView.getWebView(), startedUrl,
(Bitmap) msg.obj);
}
break;
case PAGE_FINISHED:
String finishedUrl = (String) msg.obj;
mWebView.onPageFinished(finishedUrl);
if (mWebViewClient != null) {
mWebViewClient.onPageFinished(mWebView.getWebView(), finishedUrl);
}
break;
case RECEIVED_ICON:
if (mWebChromeClient != null) {
mWebChromeClient.onReceivedIcon(mWebView.getWebView(), (Bitmap) msg.obj);
}
break;
case RECEIVED_TOUCH_ICON_URL:
if (mWebChromeClient != null) {
mWebChromeClient.onReceivedTouchIconUrl(mWebView.getWebView(),
(String) msg.obj, msg.arg1 == 1);
}
break;
case RECEIVED_TITLE:
if (mWebChromeClient != null) {
mWebChromeClient.onReceivedTitle(mWebView.getWebView(),
(String) msg.obj);
}
break;
case REPORT_ERROR:
if (mWebViewClient != null) {
int reasonCode = msg.arg1;
final String description = msg.getData().getString("description");
final String failUrl = msg.getData().getString("failingUrl");
mWebViewClient.onReceivedError(mWebView.getWebView(), reasonCode,
description, failUrl);
}
break;
case RESEND_POST_DATA:
Message resend =
(Message) msg.getData().getParcelable("resend");
Message dontResend =
(Message) msg.getData().getParcelable("dontResend");
if (mWebViewClient != null) {
mWebViewClient.onFormResubmission(mWebView.getWebView(), dontResend,
resend);
} else {
dontResend.sendToTarget();
}
break;
case OVERRIDE_URL:
String overrideUrl = msg.getData().getString("url");
boolean override = uiOverrideUrlLoading(overrideUrl);
ResultTransport<Boolean> result =
(ResultTransport<Boolean>) msg.obj;
synchronized (this) {
result.setResult(override);
notify();
}
break;
case AUTH_REQUEST:
if (mWebViewClient != null) {
HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
String host = msg.getData().getString("host");
String realm = msg.getData().getString("realm");
mWebViewClient.onReceivedHttpAuthRequest(mWebView.getWebView(), handler,
host, realm);
}
break;
case SSL_ERROR:
if (mWebViewClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
mWebViewClient.onReceivedSslError(mWebView.getWebView(),
(SslErrorHandler) map.get("handler"),
(SslError) map.get("error"));
}
break;
case PROCEEDED_AFTER_SSL_ERROR:
if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
mWebView.getWebView(),
(SslError) msg.obj);
}
break;
case CLIENT_CERT_REQUEST:
if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
mWebView.getWebView(),
(ClientCertRequestHandler) map.get("handler"),
(String) map.get("host_and_port"));
}
break;
case PROGRESS:
// Synchronize to ensure mLatestProgress is not modified after
// setProgress is called and before mProgressUpdatePending is
// changed.
synchronized (this) {
if (mWebChromeClient != null) {
mWebChromeClient.onProgressChanged(mWebView.getWebView(),
mLatestProgress);
}
mProgressUpdatePending = false;
}
break;
case UPDATE_VISITED:
if (mWebViewClient != null) {
mWebViewClient.doUpdateVisitedHistory(mWebView.getWebView(),
(String) msg.obj, msg.arg1 != 0);
}
break;
case LOAD_RESOURCE:
if (mWebViewClient != null) {
mWebViewClient.onLoadResource(mWebView.getWebView(), (String) msg.obj);
}
break;
case DOWNLOAD_FILE:
if (mDownloadListener != null) {
String url = msg.getData().getString("url");
String userAgent = msg.getData().getString("userAgent");
String contentDisposition =
msg.getData().getString("contentDisposition");
String mimetype = msg.getData().getString("mimetype");
String referer = msg.getData().getString("referer");
Long contentLength = msg.getData().getLong("contentLength");
if (mDownloadListener instanceof BrowserDownloadListener) {
((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
userAgent, contentDisposition, mimetype, referer, contentLength);
} else {
mDownloadListener.onDownloadStart(url, userAgent,
contentDisposition, mimetype, contentLength);
}
}
break;
case CREATE_WINDOW:
if (mWebChromeClient != null) {
if (!mWebChromeClient.onCreateWindow(mWebView.getWebView(),
msg.arg1 == 1, msg.arg2 == 1,
(Message) msg.obj)) {
synchronized (this) {
notify();
}
}
mWebView.dismissZoomControl();
}
break;
case REQUEST_FOCUS:
if (mWebChromeClient != null) {
mWebChromeClient.onRequestFocus(mWebView.getWebView());
}
break;
case CLOSE_WINDOW:
if (mWebChromeClient != null) {
mWebChromeClient.onCloseWindow(((WebViewClassic) msg.obj).getWebView());
}
break;
case SAVE_PASSWORD:
Bundle bundle = msg.getData();
String schemePlusHost = bundle.getString("host");
String username = bundle.getString("username");
String password = bundle.getString("password");
// If the client returned false it means that the notify message
// will not be sent and we should notify WebCore ourselves.
if (!mWebView.onSavePassword(schemePlusHost, username, password,
(Message) msg.obj)) {
synchronized (this) {
notify();
}
}
break;
case ASYNC_KEYEVENTS:
if (mWebViewClient != null) {
mWebViewClient.onUnhandledKeyEvent(mWebView.getWebView(),
(KeyEvent) msg.obj);
}
break;
case EXCEEDED_DATABASE_QUOTA:
if (mWebChromeClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
String databaseIdentifier =
(String) map.get("databaseIdentifier");
String url = (String) map.get("url");
long quota =
((Long) map.get("quota")).longValue();
long totalQuota =
((Long) map.get("totalQuota")).longValue();
long estimatedDatabaseSize =
((Long) map.get("estimatedDatabaseSize")).longValue();
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
mWebChromeClient.onExceededDatabaseQuota(url,
databaseIdentifier, quota, estimatedDatabaseSize,
totalQuota, quotaUpdater);
}
break;
case REACHED_APPCACHE_MAXSIZE:
if (mWebChromeClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
long requiredStorage =
((Long) map.get("requiredStorage")).longValue();
long quota =
((Long) map.get("quota")).longValue();
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
quota, quotaUpdater);
}
break;
case GEOLOCATION_PERMISSIONS_SHOW_PROMPT:
if (mWebChromeClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
String origin = (String) map.get("origin");
GeolocationPermissions.Callback callback =
(GeolocationPermissions.Callback)
map.get("callback");
mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
callback);
}
break;
case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
if (mWebChromeClient != null) {
mWebChromeClient.onGeolocationPermissionsHidePrompt();
}
break;
case JS_ALERT:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setMessage(message)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
receiver.setReady();
}
break;
case JS_CONFIRM:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setMessage(message)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.cancel();
}})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
// Tell the JsResult that it is ready for client
// interaction.
receiver.setReady();
}
break;
case JS_PROMPT:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsPromptResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String defaultVal = msg.getData().getString("default");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsPrompt(mWebView.getWebView(), url, message,
defaultVal, res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
final LayoutInflater factory = LayoutInflater
.from(mContext);
final View view = factory.inflate(R.layout.js_prompt,
null);
final EditText v = (EditText) view
.findViewById(R.id.value);
v.setText(defaultVal);
((TextView) view.findViewById(R.id.message))
.setText(message);
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setView(view)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int whichButton) {
res.confirm(v.getText()
.toString());
}
})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int whichButton) {
res.cancel();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
// Tell the JsResult that it is ready for client
// interaction.
receiver.setReady();
}
break;
case JS_UNLOAD:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
message, res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
final String m = mContext.getString(
R.string.js_dialog_before_unload, message);
new AlertDialog.Builder(mContext)
.setMessage(m)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}
})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.cancel();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
receiver.setReady();
}
break;
case JS_TIMEOUT:
if(mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
if(mWebChromeClient.onJsTimeout()) {
res.confirm();
} else {
res.cancel();
}
receiver.setReady();
}
break;
case RECEIVED_CERTIFICATE:
mWebView.setCertificate((SslCertificate) msg.obj);
break;
case NOTIFY:
synchronized (this) {
notify();
}
break;
case SCALE_CHANGED:
if (mWebViewClient != null) {
mWebViewClient.onScaleChanged(mWebView.getWebView(), msg.getData()
.getFloat("old"), msg.getData().getFloat("new"));
}
break;
case SWITCH_OUT_HISTORY:
mWebView.switchOutDrawHistory();
break;
case ADD_MESSAGE_TO_CONSOLE:
if (mWebChromeClient == null) {
break;
}
String message = msg.getData().getString("message");
String sourceID = msg.getData().getString("sourceID");
int lineNumber = msg.getData().getInt("lineNumber");
int msgLevel = msg.getData().getInt("msgLevel");
int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
// Sanity bounds check as we'll index an array with msgLevel
if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
msgLevel = 0;
}
ConsoleMessage.MessageLevel messageLevel =
ConsoleMessage.MessageLevel.values()[msgLevel];
if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
lineNumber, messageLevel))) {
// If false was returned the user did not provide their own console function so
// we should output some default messages to the system log.
String logTag = "Web Console";
String logMessage = message + " at " + sourceID + ":" + lineNumber;
switch (messageLevel) {
case TIP:
Log.v(logTag, logMessage);
break;
case LOG:
Log.i(logTag, logMessage);
break;
case WARNING:
Log.w(logTag, logMessage);
break;
case ERROR:
Log.e(logTag, logMessage);
break;
case DEBUG:
Log.d(logTag, logMessage);
break;
}
}
break;
case GET_VISITED_HISTORY:
if (mWebChromeClient != null) {
mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
}
break;
case OPEN_FILE_CHOOSER:
if (mWebChromeClient != null) {
UploadFileMessageData data = (UploadFileMessageData)msg.obj;
mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
data.getCapture());
}
break;
case ADD_HISTORY_ITEM:
if (mWebBackForwardListClient != null) {
mWebBackForwardListClient.onNewHistoryItem(
(WebHistoryItem) msg.obj);
}
break;
case HISTORY_INDEX_CHANGED:
if (mWebBackForwardListClient != null) {
mWebBackForwardListClient.onIndexChanged(
(WebHistoryItem) msg.obj, msg.arg1);
}
break;
case AUTH_CREDENTIALS: {
String host = msg.getData().getString("host");
String realm = msg.getData().getString("realm");
username = msg.getData().getString("username");
password = msg.getData().getString("password");
mWebView.setHttpAuthUsernamePassword(
host, realm, username, password);
break;
}
case AUTO_LOGIN: {
if (mWebViewClient != null) {
String realm = msg.getData().getString("realm");
String account = msg.getData().getString("account");
String args = msg.getData().getString("args");
mWebViewClient.onReceivedLoginRequest(mWebView.getWebView(), realm,
account, args);
}
break;
}
}
}
/**
* Return the latest progress.
*/
public int getProgress() {
return mLatestProgress;
}
/**
* Called by WebCore side to switch out of history Picture drawing mode
*/
void switchOutDrawHistory() {
sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
}
private String getJsDialogTitle(String url) {
String title = url;
if (URLUtil.isDataUrl(url)) {
// For data: urls, we just display 'JavaScript' similar to Safari.
title = mContext.getString(R.string.js_dialog_title_default);
} else {
try {
URL aUrl = new URL(url);
// For example: "The page at 'http://www.mit.edu' says:"
title = mContext.getString(R.string.js_dialog_title,
aUrl.getProtocol() + "://" + aUrl.getHost());
} catch (MalformedURLException ex) {
// do nothing. just use the url as the title
}
}
return title;
}
//--------------------------------------------------------------------------
// WebViewClient functions.
// NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
// it is not necessary to include it here.
//--------------------------------------------------------------------------
// Performance probe
private static final boolean PERF_PROBE = false;
private long mWebCoreThreadTime;
private long mWebCoreIdleTime;
/*
* If PERF_PROBE is true, this block needs to be added to MessageQueue.java.
* startWait() and finishWait() should be called before and after wait().
private WaitCallback mWaitCallback = null;
public static interface WaitCallback {
void startWait();
void finishWait();
}
public final void setWaitCallback(WaitCallback callback) {
mWaitCallback = callback;
}
*/
// un-comment this block if PERF_PROBE is true
/*
private IdleCallback mIdleCallback = new IdleCallback();
private final class IdleCallback implements MessageQueue.WaitCallback {
private long mStartTime = 0;
public void finishWait() {
mWebCoreIdleTime += SystemClock.uptimeMillis() - mStartTime;
}
public void startWait() {
mStartTime = SystemClock.uptimeMillis();
}
}
*/
public void onPageStarted(String url, Bitmap favicon) {
// We need to send the message even if no WebViewClient is set, because we need to call
// WebView.onPageStarted().
// Performance probe
if (PERF_PROBE) {
mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
mWebCoreIdleTime = 0;
// un-comment this if PERF_PROBE is true
// Looper.myQueue().setWaitCallback(mIdleCallback);
}
Message msg = obtainMessage(PAGE_STARTED);
msg.obj = favicon;
msg.getData().putString("url", url);
sendMessage(msg);
}
public void onPageFinished(String url) {
// Performance probe
if (PERF_PROBE) {
// un-comment this if PERF_PROBE is true
// Looper.myQueue().setWaitCallback(null);
Log.d("WebCore", "WebCore thread used " +
(SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
+ " ms and idled " + mWebCoreIdleTime + " ms");
}
Message msg = obtainMessage(PAGE_FINISHED, url);
sendMessage(msg);
}
// Because this method is public and because CallbackProxy is mistakenly
// party of the public classes, we cannot remove this method.
public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
// deprecated.
}
public void onReceivedError(int errorCode, String description,
String failingUrl) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
return;
}
Message msg = obtainMessage(REPORT_ERROR);
msg.arg1 = errorCode;
msg.getData().putString("description", description);
msg.getData().putString("failingUrl", failingUrl);
sendMessage(msg);
}
public void onFormResubmission(Message dontResend,
Message resend) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
dontResend.sendToTarget();
return;
}
Message msg = obtainMessage(RESEND_POST_DATA);
Bundle bundle = msg.getData();
bundle.putParcelable("resend", resend);
bundle.putParcelable("dontResend", dontResend);
sendMessage(msg);
}
/**
* Called by the WebCore side
*/
public boolean shouldOverrideUrlLoading(String url) {
// We have a default behavior if no client exists so always send the
// message.
ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
Message msg = obtainMessage(OVERRIDE_URL);
msg.getData().putString("url", url);
msg.obj = res;
sendMessageToUiThreadSync(msg);
return res.getResult().booleanValue();
}
public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
String hostName, String realmName) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
handler.cancel();
return;
}
Message msg = obtainMessage(AUTH_REQUEST, handler);
msg.getData().putString("host", hostName);
msg.getData().putString("realm", realmName);
sendMessage(msg);
}
public void onReceivedSslError(SslErrorHandler handler, SslError error) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
handler.cancel();
return;
}
Message msg = obtainMessage(SSL_ERROR);
HashMap<String, Object> map = new HashMap();
map.put("handler", handler);
map.put("error", error);
msg.obj = map;
sendMessage(msg);
}
public void onProceededAfterSslError(SslError error) {
if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
return;
}
Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
msg.obj = error;
sendMessage(msg);
}
public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
handler.cancel();
return;
}
Message msg = obtainMessage(CLIENT_CERT_REQUEST);
HashMap<String, Object> map = new HashMap();
map.put("handler", handler);
map.put("host_and_port", host_and_port);
msg.obj = map;
sendMessage(msg);
}
public void onReceivedCertificate(SslCertificate certificate) {
// here, certificate can be null (if the site is not secure)
sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
}
public void doUpdateVisitedHistory(String url, boolean isReload) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
return;
}
sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
}
WebResourceResponse shouldInterceptRequest(String url) {
if (mWebViewClient == null) {
return null;
}
// Note: This method does _not_ send a message.
WebResourceResponse r =
mWebViewClient.shouldInterceptRequest(mWebView.getWebView(), url);
if (r == null) {
sendMessage(obtainMessage(LOAD_RESOURCE, url));
}
return r;
}
public void onUnhandledKeyEvent(KeyEvent event) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
return;
}
sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
}
public void onScaleChanged(float oldScale, float newScale) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
return;
}
Message msg = obtainMessage(SCALE_CHANGED);
Bundle bundle = msg.getData();
bundle.putFloat("old", oldScale);
bundle.putFloat("new", newScale);
sendMessage(msg);
}
void onReceivedLoginRequest(String realm, String account, String args) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebViewClient == null) {
return;
}
Message msg = obtainMessage(AUTO_LOGIN);
Bundle bundle = msg.getData();
bundle.putString("realm", realm);
bundle.putString("account", account);
bundle.putString("args", args);
sendMessage(msg);
}
//--------------------------------------------------------------------------
// DownloadListener functions.
//--------------------------------------------------------------------------
/**
* Starts a download if a download listener has been registered, otherwise
* return false.
*/
public boolean onDownloadStart(String url, String userAgent,
String contentDisposition, String mimetype, String referer,
long contentLength) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mDownloadListener == null) {
// Cancel the download if there is no browser client.
return false;
}
Message msg = obtainMessage(DOWNLOAD_FILE);
Bundle bundle = msg.getData();
bundle.putString("url", url);
bundle.putString("userAgent", userAgent);
bundle.putString("mimetype", mimetype);
bundle.putString("referer", referer);
bundle.putLong("contentLength", contentLength);
bundle.putString("contentDisposition", contentDisposition);
sendMessage(msg);
return true;
}
//--------------------------------------------------------------------------
// WebView specific functions that do not interact with a client. These
// functions just need to operate within the UI thread.
//--------------------------------------------------------------------------
public boolean onSavePassword(String schemePlusHost, String username,
String password, Message resumeMsg) {
// resumeMsg should be null at this point because we want to create it
// within the CallbackProxy.
if (DebugFlags.CALLBACK_PROXY) {
junit.framework.Assert.assertNull(resumeMsg);
}
resumeMsg = obtainMessage(NOTIFY);
Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
Bundle bundle = msg.getData();
bundle.putString("host", schemePlusHost);
bundle.putString("username", username);
bundle.putString("password", password);
sendMessageToUiThreadSync(msg);
// Doesn't matter here
return false;
}
public void onReceivedHttpAuthCredentials(String host, String realm,
String username, String password) {
Message msg = obtainMessage(AUTH_CREDENTIALS);
msg.getData().putString("host", host);
msg.getData().putString("realm", realm);
msg.getData().putString("username", username);
msg.getData().putString("password", password);
sendMessage(msg);
}
//--------------------------------------------------------------------------
// WebChromeClient methods
//--------------------------------------------------------------------------
public void onProgressChanged(int newProgress) {
// Synchronize so that mLatestProgress is up-to-date.
synchronized (this) {
// update mLatestProgress even mWebChromeClient is null as
// WebView.getProgress() needs it
if (mLatestProgress == newProgress) {
return;
}
mLatestProgress = newProgress;
if (mWebChromeClient == null) {
return;
}
if (!mProgressUpdatePending) {
sendEmptyMessage(PROGRESS);
mProgressUpdatePending = true;
}
}
}
public BrowserFrame createWindow(boolean dialog, boolean userGesture) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return null;
}
WebView.WebViewTransport transport =
mWebView.getWebView().new WebViewTransport();
final Message msg = obtainMessage(NOTIFY);
msg.obj = transport;
sendMessageToUiThreadSync(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
userGesture ? 1 : 0, msg));
WebViewClassic w = WebViewClassic.fromWebView(transport.getWebView());
if (w != null) {
WebViewCore core = w.getWebViewCore();
// If WebView.destroy() has been called, core may be null. Skip
// initialization in that case and return null.
if (core != null) {
core.initializeSubwindow();
return core.getBrowserFrame();
}
}
return null;
}
public void onRequestFocus() {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return;
}
sendEmptyMessage(REQUEST_FOCUS);
}
public void onCloseWindow(WebViewClassic window) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return;
}
sendMessage(obtainMessage(CLOSE_WINDOW, window));
}
public void onReceivedIcon(Bitmap icon) {
// The current item might be null if the icon was already stored in the
// database and this is a new WebView.
WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
if (i != null) {
i.setFavicon(icon);
}
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return;
}
sendMessage(obtainMessage(RECEIVED_ICON, icon));
}
/* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
// We should have a current item but we do not want to crash so check
// for null.
WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
if (i != null) {
i.setTouchIconUrl(url, precomposed);
}
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return;
}
sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
precomposed ? 1 : 0, 0, url));
}
public void onReceivedTitle(String title) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return;
}
sendMessage(obtainMessage(RECEIVED_TITLE, title));
}
public void onJsAlert(String url, String message) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return;
}
JsResultReceiver result = new JsResultReceiver();
Message alert = obtainMessage(JS_ALERT, result);
alert.getData().putString("message", message);
alert.getData().putString("url", url);
sendMessageToUiThreadSync(alert);
}
public boolean onJsConfirm(String url, String message) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return false;
}
JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_CONFIRM, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
sendMessageToUiThreadSync(confirm);
return result.mJsResult.getResult();
}
public String onJsPrompt(String url, String message, String defaultValue) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return null;
}
JsResultReceiver result = new JsResultReceiver();
Message prompt = obtainMessage(JS_PROMPT, result);
prompt.getData().putString("message", message);
prompt.getData().putString("default", defaultValue);
prompt.getData().putString("url", url);
sendMessageToUiThreadSync(prompt);
return result.mJsResult.getStringResult();
}
public boolean onJsBeforeUnload(String url, String message) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
return true;
}
JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_UNLOAD, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
sendMessageToUiThreadSync(confirm);
return result.mJsResult.getResult();
}
/**
* Called by WebViewCore to inform the Java side that the current origin
* has overflowed it's database quota. Called in the WebCore thread so
* posts a message to the UI thread that will prompt the WebChromeClient
* for what to do. On return back to C++ side, the WebCore thread will
* sleep pending a new quota value.
* @param url The URL that caused the quota overflow.
* @param databaseIdentifier The identifier of the database that the
* transaction that caused the overflow was running on.
* @param quota The current quota the origin is allowed.
* @param estimatedDatabaseSize The estimated size of the database.
* @param totalQuota is the sum of all origins' quota.
* @param quotaUpdater An instance of a class encapsulating a callback
* to WebViewCore to run when the decision to allow or deny more
* quota has been made.
*/
public void onExceededDatabaseQuota(
String url, String databaseIdentifier, long quota,
long estimatedDatabaseSize, long totalQuota,
WebStorage.QuotaUpdater quotaUpdater) {
if (mWebChromeClient == null) {
// Native-side logic prevents the quota being updated to a smaller
// value.
quotaUpdater.updateQuota(quota);
return;
}
Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
HashMap<String, Object> map = new HashMap();
map.put("databaseIdentifier", databaseIdentifier);
map.put("url", url);
map.put("quota", quota);
map.put("estimatedDatabaseSize", estimatedDatabaseSize);
map.put("totalQuota", totalQuota);
map.put("quotaUpdater", quotaUpdater);
exceededQuota.obj = map;
sendMessage(exceededQuota);
}
/**
* Called by WebViewCore to inform the Java side that the appcache has
* exceeded its max size.
* @param requiredStorage is the amount of storage, in bytes, that would be
* needed in order for the last appcache operation to succeed.
* @param quota is the current quota (for all origins).
* @param quotaUpdater An instance of a class encapsulating a callback
* to WebViewCore to run when the decision to allow or deny a bigger
* app cache size has been made.
*/
public void onReachedMaxAppCacheSize(long requiredStorage,
long quota, WebStorage.QuotaUpdater quotaUpdater) {
if (mWebChromeClient == null) {
// Native-side logic prevents the quota being updated to a smaller
// value.
quotaUpdater.updateQuota(quota);
return;
}
Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
HashMap<String, Object> map = new HashMap();
map.put("requiredStorage", requiredStorage);
map.put("quota", quota);
map.put("quotaUpdater", quotaUpdater);
msg.obj = map;
sendMessage(msg);
}
/**
* Called by WebViewCore to instruct the browser to display a prompt to ask
* the user to set the Geolocation permission state for the given origin.
* @param origin The origin requesting Geolocation permsissions.
* @param callback The callback to call once a permission state has been
* obtained.
*/
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
if (mWebChromeClient == null) {
return;
}
Message showMessage =
obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
HashMap<String, Object> map = new HashMap();
map.put("origin", origin);
map.put("callback", callback);
showMessage.obj = map;
sendMessage(showMessage);
}
/**
* Called by WebViewCore to instruct the browser to hide the Geolocation
* permissions prompt.
*/
public void onGeolocationPermissionsHidePrompt() {
if (mWebChromeClient == null) {
return;
}
Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
sendMessage(hideMessage);
}
/**
* Called by WebViewCore when we have a message to be added to the JavaScript
* error console. Sends a message to the Java side with the details.
* @param message The message to add to the console.
* @param lineNumber The lineNumber of the source file on which the error
* occurred.
* @param sourceID The filename of the source file in which the error
* occurred.
* @param msgLevel The message level, corresponding to the MessageLevel enum in
* WebCore/page/Console.h
*/
public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
if (mWebChromeClient == null) {
return;
}
Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
msg.getData().putString("message", message);
msg.getData().putString("sourceID", sourceID);
msg.getData().putInt("lineNumber", lineNumber);
msg.getData().putInt("msgLevel", msgLevel);
sendMessage(msg);
}
public boolean onJsTimeout() {
//always interrupt timedout JS by default
if (mWebChromeClient == null) {
return true;
}
JsResultReceiver result = new JsResultReceiver();
Message timeout = obtainMessage(JS_TIMEOUT, result);
sendMessageToUiThreadSync(timeout);
return result.mJsResult.getResult();
}
public void getVisitedHistory(ValueCallback<String[]> callback) {
if (mWebChromeClient == null) {
return;
}
Message msg = obtainMessage(GET_VISITED_HISTORY);
msg.obj = callback;
sendMessage(msg);
}
private static class UploadFileMessageData {
private UploadFile mCallback;
private String mAcceptType;
private String mCapture;
public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
mCallback = uploadFile;
mAcceptType = acceptType;
mCapture = capture;
}
public UploadFile getUploadFile() {
return mCallback;
}
public String getAcceptType() {
return mAcceptType;
}
public String getCapture() {
return mCapture;
}
}
private class UploadFile implements ValueCallback<Uri> {
private Uri mValue;
public void onReceiveValue(Uri value) {
mValue = value;
synchronized (CallbackProxy.this) {
CallbackProxy.this.notify();
}
}
public Uri getResult() {
return mValue;
}
}
/**
* Called by WebViewCore to open a file chooser.
*/
/* package */ Uri openFileChooser(String acceptType, String capture) {
if (mWebChromeClient == null) {
return null;
}
Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
UploadFile uploadFile = new UploadFile();
UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
myMessage.obj = data;
sendMessageToUiThreadSync(myMessage);
return uploadFile.getResult();
}
void onNewHistoryItem(WebHistoryItem item) {
if (mWebBackForwardListClient == null) {
return;
}
Message msg = obtainMessage(ADD_HISTORY_ITEM, item);
sendMessage(msg);
}
void onIndexChanged(WebHistoryItem item, int index) {
if (mWebBackForwardListClient == null) {
return;
}
Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
sendMessage(msg);
}
boolean canShowAlertDialog() {
// We can only display the alert dialog if mContext is
// an Activity context.
// FIXME: Should we display dialogs if mContext does
// not have the window focus (e.g. if the user is viewing
// another Activity when the alert should be displayed?
// See bug 3166409
return mContext instanceof Activity;
}
private synchronized void sendMessageToUiThreadSync(Message msg) {
sendMessage(msg);
WebCoreThreadWatchdog.pause();
try {
wait();
} catch (InterruptedException e) {
Log.e(LOGTAG, "Caught exception waiting for synchronous UI message to be processed");
Log.e(LOGTAG, Log.getStackTraceString(e));
}
WebCoreThreadWatchdog.resume();
}
}