/* * 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.ClientCertRequest; import android.webkit.ConsoleMessage; import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; import android.webkit.GeolocationPermissions.Callback; import android.webkit.HttpAuthHandler; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.SslErrorHandler; import android.webkit.URLUtil; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebChromeClient.FileChooserParams; 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.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.security.Principal; 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 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; // The permissions prompt private PermissionsPrompt mPermissionsPrompt; // 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(); handleProceededAfterSslError(error); } } /** * Displays client certificate request to the user. */ @Override public void onReceivedClientCertRequest(final WebView view, final ClientCertRequest request) { if (!mInForeground) { request.ignore(); return; } KeyChain.choosePrivateKeyAlias( mWebViewController.getActivity(), new KeyChainAliasCallback() { @Override public void alias(String alias) { if (alias == null) { request.cancel(); return; } new KeyChainLookup(mContext, request, alias).execute(); } }, request.getKeyTypes(), request.getPrincipals(), request.getHost(), request.getPort(), null); } /** * 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) { return HomeProvider.shouldInterceptRequest(mContext, url); } @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 boolean onJsAlert(WebView view, String url, String message, JsResult result) { mWebViewController.getTabControl().setActiveTab(Tab.this); return false; } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { mWebViewController.getTabControl().setActiveTab(Tab.this); return false; } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { mWebViewController.getTabControl().setActiveTab(Tab.this); return false; } @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, 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(); } } @Override public void onPermissionRequest(PermissionRequest request) { if (!mInForeground) return; getPermissionsPrompt().show(request); } @Override public void onPermissionRequestCanceled(PermissionRequest request) { if (mInForeground && mPermissionsPrompt != null) { mPermissionsPrompt.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