/*
* Copyright (C) 2009 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 com.android.browser;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewStub;
import android.webkit.BrowserDownloadListener;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebBackForwardListClient;
import android.webkit.WebChromeClient;
import android.webkit.WebHistoryItem;
import android.webkit.WebResourceResponse;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebView.PictureListener;
import android.webkit.WebViewClient;
import android.widget.CheckBox;
import android.widget.Toast;
import com.android.browser.TabControl.OnThumbnailUpdatedListener;
import com.android.browser.homepages.HomeProvider;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
import java.util.Vector;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
/**
* Class for maintaining Tabs with a main WebView and a subwindow.
*/
class Tab implements PictureListener {
// Log Tag
private static final String LOGTAG = "Tab";
private static final boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
// Special case the logtag for messages for the Console to make it easier to
// filter them and match the logtag used for these messages in older versions
// of the browser.
private static final String CONSOLE_LOGTAG = "browser";
private static final int MSG_CAPTURE = 42;
private static final int CAPTURE_DELAY = 100;
private static final int INITIAL_PROGRESS = 5;
private static final String RESTRICTED = "
not allowed";
private static Bitmap sDefaultFavicon;
private static Paint sAlphaPaint = new Paint();
static {
sAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
sAlphaPaint.setColor(Color.TRANSPARENT);
}
public enum SecurityState {
// The page's main resource does not use SSL. Note that we use this
// state irrespective of the SSL authentication state of sub-resources.
SECURITY_STATE_NOT_SECURE,
// The page's main resource uses SSL and the certificate is good. The
// same is true of all sub-resources.
SECURITY_STATE_SECURE,
// The page's main resource uses SSL and the certificate is good, but
// some sub-resources either do not use SSL or have problems with their
// certificates.
SECURITY_STATE_MIXED,
// The page's main resource uses SSL but there is a problem with its
// certificate.
SECURITY_STATE_BAD_CERTIFICATE,
}
Context mContext;
protected WebViewController mWebViewController;
// The tab ID
private long mId = -1;
// The Geolocation permissions prompt
private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
// Main WebView wrapper
private View mContainer;
// Main WebView
private WebView mMainView;
// Subwindow container
private View mSubViewContainer;
// Subwindow WebView
private WebView mSubView;
// Saved bundle for when we are running low on memory. It contains the
// information needed to restore the WebView if the user goes back to the
// tab.
private Bundle mSavedState;
// Parent Tab. This is the Tab that created this Tab, or null if the Tab was
// created by the UI
private Tab mParent;
// Tab that constructed by this Tab. This is used when this Tab is
// destroyed, it clears all mParentTab values in the children.
private Vector mChildren;
// If true, the tab is in the foreground of the current activity.
private boolean mInForeground;
// If true, the tab is in page loading state (after onPageStarted,
// before onPageFinsihed)
private boolean mInPageLoad;
private boolean mDisableOverrideUrlLoading;
// The last reported progress of the current page
private int mPageLoadProgress;
// The time the load started, used to find load page time
private long mLoadStartTime;
// Application identifier used to find tabs that another application wants
// to reuse.
private String mAppId;
// flag to indicate if tab should be closed on back
private boolean mCloseOnBack;
// Keep the original url around to avoid killing the old WebView if the url
// has not changed.
// Error console for the tab
private ErrorConsoleView mErrorConsole;
// The listener that gets invoked when a download is started from the
// mMainView
private final BrowserDownloadListener mDownloadListener;
// Listener used to know when we move forward or back in the history list.
private final WebBackForwardListClient mWebBackForwardListClient;
private DataController mDataController;
// State of the auto-login request.
private DeviceAccountLogin mDeviceAccountLogin;
// AsyncTask for downloading touch icons
DownloadTouchIcon mTouchIconLoader;
private BrowserSettings mSettings;
private int mCaptureWidth;
private int mCaptureHeight;
private Bitmap mCapture;
private Handler mHandler;
private boolean mUpdateThumbnail;
/**
* See {@link #clearBackStackWhenItemAdded(String)}.
*/
private Pattern mClearHistoryUrlPattern;
private static synchronized Bitmap getDefaultFavicon(Context context) {
if (sDefaultFavicon == null) {
sDefaultFavicon = BitmapFactory.decodeResource(
context.getResources(), R.drawable.app_web_browser_sm);
}
return sDefaultFavicon;
}
// All the state needed for a page
protected static class PageState {
String mUrl;
String mOriginalUrl;
String mTitle;
SecurityState mSecurityState;
// This is non-null only when mSecurityState is SECURITY_STATE_BAD_CERTIFICATE.
SslError mSslCertificateError;
Bitmap mFavicon;
boolean mIsBookmarkedSite;
boolean mIncognito;
PageState(Context c, boolean incognito) {
mIncognito = incognito;
if (mIncognito) {
mOriginalUrl = mUrl = "browser:incognito";
mTitle = c.getString(R.string.new_incognito_tab);
} else {
mOriginalUrl = mUrl = "";
mTitle = c.getString(R.string.new_tab);
}
mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
}
PageState(Context c, boolean incognito, String url, Bitmap favicon) {
mIncognito = incognito;
mOriginalUrl = mUrl = url;
if (URLUtil.isHttpsUrl(url)) {
mSecurityState = SecurityState.SECURITY_STATE_SECURE;
} else {
mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
}
mFavicon = favicon;
}
}
// The current/loading page's state
protected PageState mCurrentState;
// Used for saving and restoring each Tab
static final String ID = "ID";
static final String CURRURL = "currentUrl";
static final String CURRTITLE = "currentTitle";
static final String PARENTTAB = "parentTab";
static final String APPID = "appid";
static final String INCOGNITO = "privateBrowsingEnabled";
static final String USERAGENT = "useragent";
static final String CLOSEFLAG = "closeOnBack";
// Container class for the next error dialog that needs to be displayed
private class ErrorDialog {
public final int mTitle;
public final String mDescription;
public final int mError;
ErrorDialog(int title, String desc, int error) {
mTitle = title;
mDescription = desc;
mError = error;
}
}
private void processNextError() {
if (mQueuedErrors == null) {
return;
}
// The first one is currently displayed so just remove it.
mQueuedErrors.removeFirst();
if (mQueuedErrors.size() == 0) {
mQueuedErrors = null;
return;
}
showError(mQueuedErrors.getFirst());
}
private DialogInterface.OnDismissListener mDialogListener =
new DialogInterface.OnDismissListener() {
public void onDismiss(DialogInterface d) {
processNextError();
}
};
private LinkedList mQueuedErrors;
private void queueError(int err, String desc) {
if (mQueuedErrors == null) {
mQueuedErrors = new LinkedList();
}
for (ErrorDialog d : mQueuedErrors) {
if (d.mError == err) {
// Already saw a similar error, ignore the new one.
return;
}
}
ErrorDialog errDialog = new ErrorDialog(
err == WebViewClient.ERROR_FILE_NOT_FOUND ?
R.string.browserFrameFileErrorLabel :
R.string.browserFrameNetworkErrorLabel,
desc, err);
mQueuedErrors.addLast(errDialog);
// Show the dialog now if the queue was empty and it is in foreground
if (mQueuedErrors.size() == 1 && mInForeground) {
showError(errDialog);
}
}
private void showError(ErrorDialog errDialog) {
if (mInForeground) {
AlertDialog d = new AlertDialog.Builder(mContext)
.setTitle(errDialog.mTitle)
.setMessage(errDialog.mDescription)
.setPositiveButton(R.string.ok, null)
.create();
d.setOnDismissListener(mDialogListener);
d.show();
}
}
// -------------------------------------------------------------------------
// WebViewClient implementation for the main WebView
// -------------------------------------------------------------------------
private final WebViewClient mWebViewClient = new WebViewClient() {
private Message mDontResend;
private Message mResend;
private boolean providersDiffer(String url, String otherUrl) {
Uri uri1 = Uri.parse(url);
Uri uri2 = Uri.parse(otherUrl);
return !uri1.getEncodedAuthority().equals(uri2.getEncodedAuthority());
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mInPageLoad = true;
mUpdateThumbnail = true;
mPageLoadProgress = INITIAL_PROGRESS;
mCurrentState = new PageState(mContext,
view.isPrivateBrowsingEnabled(), url, favicon);
mLoadStartTime = SystemClock.uptimeMillis();
// If we start a touch icon load and then load a new page, we don't
// want to cancel the current touch icon loader. But, we do want to
// create a new one when the touch icon url is known.
if (mTouchIconLoader != null) {
mTouchIconLoader.mTab = null;
mTouchIconLoader = null;
}
// reset the error console
if (mErrorConsole != null) {
mErrorConsole.clearErrorMessages();
if (mWebViewController.shouldShowErrorConsole()) {
mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
}
}
// Cancel the auto-login process.
if (mDeviceAccountLogin != null) {
mDeviceAccountLogin.cancel();
mDeviceAccountLogin = null;
mWebViewController.hideAutoLogin(Tab.this);
}
// finally update the UI in the activity if it is in the foreground
mWebViewController.onPageStarted(Tab.this, view, favicon);
updateBookmarkedStatus();
}
@Override
public void onPageFinished(WebView view, String url) {
mDisableOverrideUrlLoading = false;
if (!isPrivateBrowsingEnabled()) {
LogTag.logPageFinishedLoading(
url, SystemClock.uptimeMillis() - mLoadStartTime);
}
syncCurrentState(view, url);
mWebViewController.onPageFinished(Tab.this);
}
// return true if want to hijack the url to let another app to handle it
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!mDisableOverrideUrlLoading && mInForeground) {
return mWebViewController.shouldOverrideUrlLoading(Tab.this,
view, url);
} else {
return false;
}
}
/**
* Updates the security state. This method is called when we discover
* another resource to be loaded for this page (for example,
* javascript). While we update the security state, we do not update
* the lock icon until we are done loading, as it is slightly more
* secure this way.
*/
@Override
public void onLoadResource(WebView view, String url) {
if (url != null && url.length() > 0) {
// It is only if the page claims to be secure that we may have
// to update the security state:
if (mCurrentState.mSecurityState == SecurityState.SECURITY_STATE_SECURE) {
// If NOT a 'safe' url, change the state to mixed content!
if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
|| URLUtil.isAboutUrl(url))) {
mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_MIXED;
}
}
}
}
/**
* Show a dialog informing the user of the network error reported by
* WebCore if it is in the foreground.
*/
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
errorCode != WebViewClient.ERROR_CONNECT &&
errorCode != WebViewClient.ERROR_BAD_URL &&
errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
errorCode != WebViewClient.ERROR_FILE) {
queueError(errorCode, description);
// Don't log URLs when in private browsing mode
if (!isPrivateBrowsingEnabled()) {
Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
+ " " + description);
}
}
}
/**
* Check with the user if it is ok to resend POST data as the page they
* are trying to navigate to is the result of a POST.
*/
@Override
public void onFormResubmission(WebView view, final Message dontResend,
final Message resend) {
if (!mInForeground) {
dontResend.sendToTarget();
return;
}
if (mDontResend != null) {
Log.w(LOGTAG, "onFormResubmission should not be called again "
+ "while dialog is still up");
dontResend.sendToTarget();
return;
}
mDontResend = dontResend;
mResend = resend;
new AlertDialog.Builder(mContext).setTitle(
R.string.browserFrameFormResubmitLabel).setMessage(
R.string.browserFrameFormResubmitMessage)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
if (mResend != null) {
mResend.sendToTarget();
mResend = null;
mDontResend = null;
}
}
}).setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
if (mDontResend != null) {
mDontResend.sendToTarget();
mResend = null;
mDontResend = null;
}
}
}).setOnCancelListener(new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
if (mDontResend != null) {
mDontResend.sendToTarget();
mResend = null;
mDontResend = null;
}
}
}).show();
}
/**
* Insert the url into the visited history database.
* @param url The url to be inserted.
* @param isReload True if this url is being reloaded.
* FIXME: Not sure what to do when reloading the page.
*/
@Override
public void doUpdateVisitedHistory(WebView view, String url,
boolean isReload) {
mWebViewController.doUpdateVisitedHistory(Tab.this, isReload);
}
/**
* Displays SSL error(s) dialog to the user.
*/
@Override
public void onReceivedSslError(final WebView view,
final SslErrorHandler handler, final SslError error) {
if (!mInForeground) {
handler.cancel();
setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE);
return;
}
if (mSettings.showSecurityWarnings()) {
new AlertDialog.Builder(mContext)
.setTitle(R.string.security_warning)
.setMessage(R.string.ssl_warnings_header)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(R.string.ssl_continue,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
handler.proceed();
handleProceededAfterSslError(error);
}
})
.setNeutralButton(R.string.view_certificate,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
mWebViewController.showSslCertificateOnError(
view, handler, error);
}
})
.setNegativeButton(R.string.ssl_go_back,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int whichButton) {
dialog.cancel();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
handler.cancel();
setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE);
mWebViewController.onUserCanceledSsl(Tab.this);
}
})
.show();
} else {
handler.proceed();
}
}
/**
* Handles an HTTP authentication request.
*
* @param handler The authentication handler
* @param host The host
* @param realm The realm
*/
@Override
public void onReceivedHttpAuthRequest(WebView view,
final HttpAuthHandler handler, final String host,
final String realm) {
mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
Uri uri = Uri.parse(url);
if (uri.getScheme().toLowerCase().equals("file")) {
File file = new File(uri.getPath());
try {
if (file.getCanonicalPath().startsWith(
mContext.getApplicationContext().getApplicationInfo().dataDir)) {
return new WebResourceResponse("text/html","UTF-8",
new ByteArrayInputStream(RESTRICTED.getBytes("UTF-8")));
}
} catch (Exception ex) {
Log.e(LOGTAG, "Bad canonical path" + ex.toString());
try {
return new WebResourceResponse("text/html","UTF-8",
new ByteArrayInputStream(RESTRICTED.getBytes("UTF-8")));
} catch (java.io.UnsupportedEncodingException e) {
}
}
}
WebResourceResponse res = HomeProvider.shouldInterceptRequest(
mContext, url);
return res;
}
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
if (!mInForeground) {
return false;
}
return mWebViewController.shouldOverrideKeyEvent(event);
}
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
if (!mInForeground) {
return;
}
if (!mWebViewController.onUnhandledKeyEvent(event)) {
super.onUnhandledKeyEvent(view, event);
}
}
@Override
public void onReceivedLoginRequest(WebView view, String realm,
String account, String args) {
new DeviceAccountLogin(mWebViewController.getActivity(), view, Tab.this, mWebViewController)
.handleLogin(realm, account, args);
}
};
private void syncCurrentState(WebView view, String url) {
// Sync state (in case of stop/timeout)
mCurrentState.mUrl = view.getUrl();
if (mCurrentState.mUrl == null) {
mCurrentState.mUrl = "";
}
mCurrentState.mOriginalUrl = view.getOriginalUrl();
mCurrentState.mTitle = view.getTitle();
mCurrentState.mFavicon = view.getFavicon();
if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
// In case we stop when loading an HTTPS page from an HTTP page
// but before a provisional load occurred
mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
mCurrentState.mSslCertificateError = null;
}
mCurrentState.mIncognito = view.isPrivateBrowsingEnabled();
}
// Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
// displayed.
void setDeviceAccountLogin(DeviceAccountLogin login) {
mDeviceAccountLogin = login;
}
// Returns non-null if the title bar should display the auto-login UI.
DeviceAccountLogin getDeviceAccountLogin() {
return mDeviceAccountLogin;
}
// -------------------------------------------------------------------------
// WebChromeClient implementation for the main WebView
// -------------------------------------------------------------------------
private final WebChromeClient mWebChromeClient = new WebChromeClient() {
// Helper method to create a new tab or sub window.
private void createWindow(final boolean dialog, final Message msg) {
WebView.WebViewTransport transport =
(WebView.WebViewTransport) msg.obj;
if (dialog) {
createSubWindow();
mWebViewController.attachSubWindow(Tab.this);
transport.setWebView(mSubView);
} else {
final Tab newTab = mWebViewController.openTab(null,
Tab.this, true, true);
transport.setWebView(newTab.getWebView());
}
msg.sendToTarget();
}
@Override
public boolean onCreateWindow(WebView view, final boolean dialog,
final boolean userGesture, final Message resultMsg) {
// only allow new window or sub window for the foreground case
if (!mInForeground) {
return false;
}
// Short-circuit if we can't create any more tabs or sub windows.
if (dialog && mSubView != null) {
new AlertDialog.Builder(mContext)
.setTitle(R.string.too_many_subwindows_dialog_title)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.too_many_subwindows_dialog_message)
.setPositiveButton(R.string.ok, null)
.show();
return false;
} else if (!mWebViewController.getTabControl().canCreateNewTab()) {
new AlertDialog.Builder(mContext)
.setTitle(R.string.too_many_windows_dialog_title)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.too_many_windows_dialog_message)
.setPositiveButton(R.string.ok, null)
.show();
return false;
}
// Short-circuit if this was a user gesture.
if (userGesture) {
createWindow(dialog, resultMsg);
return true;
}
// Allow the popup and create the appropriate window.
final AlertDialog.OnClickListener allowListener =
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface d,
int which) {
createWindow(dialog, resultMsg);
}
};
// Block the popup by returning a null WebView.
final AlertDialog.OnClickListener blockListener =
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface d, int which) {
resultMsg.sendToTarget();
}
};
// Build a confirmation dialog to display to the user.
final AlertDialog d =
new AlertDialog.Builder(mContext)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.popup_window_attempt)
.setPositiveButton(R.string.allow, allowListener)
.setNegativeButton(R.string.block, blockListener)
.setCancelable(false)
.create();
// Show the confirmation dialog.
d.show();
return true;
}
@Override
public void onRequestFocus(WebView view) {
if (!mInForeground) {
mWebViewController.switchToTab(Tab.this);
}
}
@Override
public void onCloseWindow(WebView window) {
if (mParent != null) {
// JavaScript can only close popup window.
if (mInForeground) {
mWebViewController.switchToTab(mParent);
}
mWebViewController.closeTab(Tab.this);
}
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
mPageLoadProgress = newProgress;
if (newProgress == 100) {
mInPageLoad = false;
}
mWebViewController.onProgressChanged(Tab.this);
if (mUpdateThumbnail && newProgress == 100) {
mUpdateThumbnail = false;
}
}
@Override
public void onReceivedTitle(WebView view, final String title) {
mCurrentState.mTitle = title;
mWebViewController.onReceivedTitle(Tab.this, title);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
mCurrentState.mFavicon = icon;
mWebViewController.onFavicon(Tab.this, view, icon);
}
@Override
public void onReceivedTouchIconUrl(WebView view, String url,
boolean precomposed) {
final ContentResolver cr = mContext.getContentResolver();
// Let precomposed icons take precedence over non-composed
// icons.
if (precomposed && mTouchIconLoader != null) {
mTouchIconLoader.cancel(false);
mTouchIconLoader = null;
}
// Have only one async task at a time.
if (mTouchIconLoader == null) {
mTouchIconLoader = new DownloadTouchIcon(Tab.this,
mContext, cr, view);
mTouchIconLoader.execute(url);
}
}
@Override
public void onShowCustomView(View view,
WebChromeClient.CustomViewCallback callback) {
Activity activity = mWebViewController.getActivity();
if (activity != null) {
onShowCustomView(view, activity.getRequestedOrientation(), callback);
}
}
@Override
public void onShowCustomView(View view, int requestedOrientation,
WebChromeClient.CustomViewCallback callback) {
if (mInForeground) mWebViewController.showCustomView(Tab.this, view,
requestedOrientation, callback);
}
@Override
public void onHideCustomView() {
if (mInForeground) mWebViewController.hideCustomView();
}
/**
* The origin has exceeded its database quota.
* @param url the URL that exceeded the quota
* @param databaseIdentifier the identifier of the database on which the
* transaction that caused the quota overflow was run
* @param currentQuota the current quota for the origin.
* @param estimatedSize the estimated size of the database.
* @param totalUsedQuota is the sum of all origins' quota.
* @param quotaUpdater The callback to run when a decision to allow or
* deny quota has been made. Don't forget to call this!
*/
@Override
public void onExceededDatabaseQuota(String url,
String databaseIdentifier, long currentQuota, long estimatedSize,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
mSettings.getWebStorageSizeManager()
.onExceededDatabaseQuota(url, databaseIdentifier,
currentQuota, estimatedSize, totalUsedQuota,
quotaUpdater);
}
/**
* The Application Cache has exceeded its max size.
* @param spaceNeeded is the amount of disk space that would be needed
* in order for the last appcache operation to succeed.
* @param totalUsedQuota is the sum of all origins' quota.
* @param quotaUpdater A callback to inform the WebCore thread that a
* new app cache size is available. This callback must always
* be executed at some point to ensure that the sleeping
* WebCore thread is woken up.
*/
@Override
public void onReachedMaxAppCacheSize(long spaceNeeded,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
mSettings.getWebStorageSizeManager()
.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
quotaUpdater);
}
/**
* Instructs the browser to show a prompt to ask the user to set the
* Geolocation permission state for the specified origin.
* @param origin The origin for which Geolocation permissions are
* requested.
* @param callback The callback to call once the user has set the
* Geolocation permission state.
*/
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
if (mInForeground) {
getGeolocationPermissionsPrompt().show(origin, callback);
}
}
/**
* Instructs the browser to hide the Geolocation permissions prompt.
*/
@Override
public void onGeolocationPermissionsHidePrompt() {
if (mInForeground && mGeolocationPermissionsPrompt != null) {
mGeolocationPermissionsPrompt.hide();
}
}
/* Adds a JavaScript error message to the system log and if the JS
* console is enabled in the about:debug options, to that console
* also.
* @param consoleMessage the message object.
*/
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
if (mInForeground) {
// call getErrorConsole(true) so it will create one if needed
ErrorConsoleView errorConsole = getErrorConsole(true);
errorConsole.addErrorMessage(consoleMessage);
if (mWebViewController.shouldShowErrorConsole()
&& errorConsole.getShowState() !=
ErrorConsoleView.SHOW_MAXIMIZED) {
errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
}
}
// Don't log console messages in private browsing mode
if (isPrivateBrowsingEnabled()) return true;
String message = "Console: " + consoleMessage.message() + " "
+ consoleMessage.sourceId() + ":"
+ consoleMessage.lineNumber();
switch (consoleMessage.messageLevel()) {
case TIP:
Log.v(CONSOLE_LOGTAG, message);
break;
case LOG:
Log.i(CONSOLE_LOGTAG, message);
break;
case WARNING:
Log.w(CONSOLE_LOGTAG, message);
break;
case ERROR:
Log.e(CONSOLE_LOGTAG, message);
break;
case DEBUG:
Log.d(CONSOLE_LOGTAG, message);
break;
}
return true;
}
/**
* Ask the browser for an icon to represent a