Home | History | Annotate | Download | only in browser
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.browser;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.content.ContentResolver;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.DialogInterface.OnCancelListener;
     26 import android.graphics.Bitmap;
     27 import android.graphics.Bitmap.CompressFormat;
     28 import android.graphics.BitmapFactory;
     29 import android.graphics.Canvas;
     30 import android.graphics.Color;
     31 import android.graphics.Paint;
     32 import android.graphics.Picture;
     33 import android.graphics.PorterDuff;
     34 import android.graphics.PorterDuffXfermode;
     35 import android.net.Uri;
     36 import android.net.http.SslError;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.Message;
     40 import android.os.SystemClock;
     41 import android.security.KeyChain;
     42 import android.security.KeyChainAliasCallback;
     43 import android.text.TextUtils;
     44 import android.util.Log;
     45 import android.view.KeyEvent;
     46 import android.view.LayoutInflater;
     47 import android.view.View;
     48 import android.view.ViewStub;
     49 import android.webkit.BrowserDownloadListener;
     50 import android.webkit.ConsoleMessage;
     51 import android.webkit.GeolocationPermissions;
     52 import android.webkit.HttpAuthHandler;
     53 import android.webkit.SslErrorHandler;
     54 import android.webkit.URLUtil;
     55 import android.webkit.ValueCallback;
     56 import android.webkit.WebBackForwardList;
     57 import android.webkit.WebBackForwardListClient;
     58 import android.webkit.WebChromeClient;
     59 import android.webkit.WebHistoryItem;
     60 import android.webkit.WebResourceResponse;
     61 import android.webkit.WebStorage;
     62 import android.webkit.WebView;
     63 import android.webkit.WebView.PictureListener;
     64 import android.webkit.WebViewClient;
     65 import android.widget.CheckBox;
     66 import android.widget.Toast;
     67 
     68 import com.android.browser.TabControl.OnThumbnailUpdatedListener;
     69 import com.android.browser.homepages.HomeProvider;
     70 import com.android.browser.provider.SnapshotProvider.Snapshots;
     71 
     72 import java.io.ByteArrayInputStream;
     73 import java.io.ByteArrayOutputStream;
     74 import java.io.File;
     75 import java.io.IOException;
     76 import java.io.OutputStream;
     77 import java.nio.ByteBuffer;
     78 import java.util.LinkedList;
     79 import java.util.Map;
     80 import java.util.UUID;
     81 import java.util.Vector;
     82 import java.util.regex.Pattern;
     83 import java.util.zip.GZIPOutputStream;
     84 
     85 /**
     86  * Class for maintaining Tabs with a main WebView and a subwindow.
     87  */
     88 class Tab implements PictureListener {
     89 
     90     // Log Tag
     91     private static final String LOGTAG = "Tab";
     92     private static final boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
     93     // Special case the logtag for messages for the Console to make it easier to
     94     // filter them and match the logtag used for these messages in older versions
     95     // of the browser.
     96     private static final String CONSOLE_LOGTAG = "browser";
     97 
     98     private static final int MSG_CAPTURE = 42;
     99     private static final int CAPTURE_DELAY = 100;
    100     private static final int INITIAL_PROGRESS = 5;
    101 
    102     private static final String RESTRICTED = "<html><body>not allowed</body></html>";
    103 
    104     private static Bitmap sDefaultFavicon;
    105 
    106     private static Paint sAlphaPaint = new Paint();
    107     static {
    108         sAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    109         sAlphaPaint.setColor(Color.TRANSPARENT);
    110     }
    111 
    112     public enum SecurityState {
    113         // The page's main resource does not use SSL. Note that we use this
    114         // state irrespective of the SSL authentication state of sub-resources.
    115         SECURITY_STATE_NOT_SECURE,
    116         // The page's main resource uses SSL and the certificate is good. The
    117         // same is true of all sub-resources.
    118         SECURITY_STATE_SECURE,
    119         // The page's main resource uses SSL and the certificate is good, but
    120         // some sub-resources either do not use SSL or have problems with their
    121         // certificates.
    122         SECURITY_STATE_MIXED,
    123         // The page's main resource uses SSL but there is a problem with its
    124         // certificate.
    125         SECURITY_STATE_BAD_CERTIFICATE,
    126     }
    127 
    128     Context mContext;
    129     protected WebViewController mWebViewController;
    130 
    131     // The tab ID
    132     private long mId = -1;
    133 
    134     // The Geolocation permissions prompt
    135     private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
    136     // Main WebView wrapper
    137     private View mContainer;
    138     // Main WebView
    139     private WebView mMainView;
    140     // Subwindow container
    141     private View mSubViewContainer;
    142     // Subwindow WebView
    143     private WebView mSubView;
    144     // Saved bundle for when we are running low on memory. It contains the
    145     // information needed to restore the WebView if the user goes back to the
    146     // tab.
    147     private Bundle mSavedState;
    148     // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
    149     // created by the UI
    150     private Tab mParent;
    151     // Tab that constructed by this Tab. This is used when this Tab is
    152     // destroyed, it clears all mParentTab values in the children.
    153     private Vector<Tab> mChildren;
    154     // If true, the tab is in the foreground of the current activity.
    155     private boolean mInForeground;
    156     // If true, the tab is in page loading state (after onPageStarted,
    157     // before onPageFinsihed)
    158     private boolean mInPageLoad;
    159     private boolean mDisableOverrideUrlLoading;
    160     // The last reported progress of the current page
    161     private int mPageLoadProgress;
    162     // The time the load started, used to find load page time
    163     private long mLoadStartTime;
    164     // Application identifier used to find tabs that another application wants
    165     // to reuse.
    166     private String mAppId;
    167     // flag to indicate if tab should be closed on back
    168     private boolean mCloseOnBack;
    169     // Keep the original url around to avoid killing the old WebView if the url
    170     // has not changed.
    171     // Error console for the tab
    172     private ErrorConsoleView mErrorConsole;
    173     // The listener that gets invoked when a download is started from the
    174     // mMainView
    175     private final BrowserDownloadListener mDownloadListener;
    176     // Listener used to know when we move forward or back in the history list.
    177     private final WebBackForwardListClient mWebBackForwardListClient;
    178     private DataController mDataController;
    179     // State of the auto-login request.
    180     private DeviceAccountLogin mDeviceAccountLogin;
    181 
    182     // AsyncTask for downloading touch icons
    183     DownloadTouchIcon mTouchIconLoader;
    184 
    185     private BrowserSettings mSettings;
    186     private int mCaptureWidth;
    187     private int mCaptureHeight;
    188     private Bitmap mCapture;
    189     private Handler mHandler;
    190     private boolean mUpdateThumbnail;
    191 
    192     /**
    193      * See {@link #clearBackStackWhenItemAdded(String)}.
    194      */
    195     private Pattern mClearHistoryUrlPattern;
    196 
    197     private static synchronized Bitmap getDefaultFavicon(Context context) {
    198         if (sDefaultFavicon == null) {
    199             sDefaultFavicon = BitmapFactory.decodeResource(
    200                     context.getResources(), R.drawable.app_web_browser_sm);
    201         }
    202         return sDefaultFavicon;
    203     }
    204 
    205     // All the state needed for a page
    206     protected static class PageState {
    207         String mUrl;
    208         String mOriginalUrl;
    209         String mTitle;
    210         SecurityState mSecurityState;
    211         // This is non-null only when mSecurityState is SECURITY_STATE_BAD_CERTIFICATE.
    212         SslError mSslCertificateError;
    213         Bitmap mFavicon;
    214         boolean mIsBookmarkedSite;
    215         boolean mIncognito;
    216 
    217         PageState(Context c, boolean incognito) {
    218             mIncognito = incognito;
    219             if (mIncognito) {
    220                 mOriginalUrl = mUrl = "browser:incognito";
    221                 mTitle = c.getString(R.string.new_incognito_tab);
    222             } else {
    223                 mOriginalUrl = mUrl = "";
    224                 mTitle = c.getString(R.string.new_tab);
    225             }
    226             mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
    227         }
    228 
    229         PageState(Context c, boolean incognito, String url, Bitmap favicon) {
    230             mIncognito = incognito;
    231             mOriginalUrl = mUrl = url;
    232             if (URLUtil.isHttpsUrl(url)) {
    233                 mSecurityState = SecurityState.SECURITY_STATE_SECURE;
    234             } else {
    235                 mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
    236             }
    237             mFavicon = favicon;
    238         }
    239 
    240     }
    241 
    242     // The current/loading page's state
    243     protected PageState mCurrentState;
    244 
    245     // Used for saving and restoring each Tab
    246     static final String ID = "ID";
    247     static final String CURRURL = "currentUrl";
    248     static final String CURRTITLE = "currentTitle";
    249     static final String PARENTTAB = "parentTab";
    250     static final String APPID = "appid";
    251     static final String INCOGNITO = "privateBrowsingEnabled";
    252     static final String USERAGENT = "useragent";
    253     static final String CLOSEFLAG = "closeOnBack";
    254 
    255     // Container class for the next error dialog that needs to be displayed
    256     private class ErrorDialog {
    257         public final int mTitle;
    258         public final String mDescription;
    259         public final int mError;
    260         ErrorDialog(int title, String desc, int error) {
    261             mTitle = title;
    262             mDescription = desc;
    263             mError = error;
    264         }
    265     }
    266 
    267     private void processNextError() {
    268         if (mQueuedErrors == null) {
    269             return;
    270         }
    271         // The first one is currently displayed so just remove it.
    272         mQueuedErrors.removeFirst();
    273         if (mQueuedErrors.size() == 0) {
    274             mQueuedErrors = null;
    275             return;
    276         }
    277         showError(mQueuedErrors.getFirst());
    278     }
    279 
    280     private DialogInterface.OnDismissListener mDialogListener =
    281             new DialogInterface.OnDismissListener() {
    282                 public void onDismiss(DialogInterface d) {
    283                     processNextError();
    284                 }
    285             };
    286     private LinkedList<ErrorDialog> mQueuedErrors;
    287 
    288     private void queueError(int err, String desc) {
    289         if (mQueuedErrors == null) {
    290             mQueuedErrors = new LinkedList<ErrorDialog>();
    291         }
    292         for (ErrorDialog d : mQueuedErrors) {
    293             if (d.mError == err) {
    294                 // Already saw a similar error, ignore the new one.
    295                 return;
    296             }
    297         }
    298         ErrorDialog errDialog = new ErrorDialog(
    299                 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
    300                 R.string.browserFrameFileErrorLabel :
    301                 R.string.browserFrameNetworkErrorLabel,
    302                 desc, err);
    303         mQueuedErrors.addLast(errDialog);
    304 
    305         // Show the dialog now if the queue was empty and it is in foreground
    306         if (mQueuedErrors.size() == 1 && mInForeground) {
    307             showError(errDialog);
    308         }
    309     }
    310 
    311     private void showError(ErrorDialog errDialog) {
    312         if (mInForeground) {
    313             AlertDialog d = new AlertDialog.Builder(mContext)
    314                     .setTitle(errDialog.mTitle)
    315                     .setMessage(errDialog.mDescription)
    316                     .setPositiveButton(R.string.ok, null)
    317                     .create();
    318             d.setOnDismissListener(mDialogListener);
    319             d.show();
    320         }
    321     }
    322 
    323     // -------------------------------------------------------------------------
    324     // WebViewClient implementation for the main WebView
    325     // -------------------------------------------------------------------------
    326 
    327     private final WebViewClient mWebViewClient = new WebViewClient() {
    328         private Message mDontResend;
    329         private Message mResend;
    330 
    331         private boolean providersDiffer(String url, String otherUrl) {
    332             Uri uri1 = Uri.parse(url);
    333             Uri uri2 = Uri.parse(otherUrl);
    334             return !uri1.getEncodedAuthority().equals(uri2.getEncodedAuthority());
    335         }
    336 
    337         @Override
    338         public void onPageStarted(WebView view, String url, Bitmap favicon) {
    339             mInPageLoad = true;
    340             mUpdateThumbnail = true;
    341             mPageLoadProgress = INITIAL_PROGRESS;
    342             mCurrentState = new PageState(mContext,
    343                     view.isPrivateBrowsingEnabled(), url, favicon);
    344             mLoadStartTime = SystemClock.uptimeMillis();
    345 
    346             // If we start a touch icon load and then load a new page, we don't
    347             // want to cancel the current touch icon loader. But, we do want to
    348             // create a new one when the touch icon url is known.
    349             if (mTouchIconLoader != null) {
    350                 mTouchIconLoader.mTab = null;
    351                 mTouchIconLoader = null;
    352             }
    353 
    354             // reset the error console
    355             if (mErrorConsole != null) {
    356                 mErrorConsole.clearErrorMessages();
    357                 if (mWebViewController.shouldShowErrorConsole()) {
    358                     mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
    359                 }
    360             }
    361 
    362             // Cancel the auto-login process.
    363             if (mDeviceAccountLogin != null) {
    364                 mDeviceAccountLogin.cancel();
    365                 mDeviceAccountLogin = null;
    366                 mWebViewController.hideAutoLogin(Tab.this);
    367             }
    368 
    369             // finally update the UI in the activity if it is in the foreground
    370             mWebViewController.onPageStarted(Tab.this, view, favicon);
    371 
    372             updateBookmarkedStatus();
    373         }
    374 
    375         @Override
    376         public void onPageFinished(WebView view, String url) {
    377             mDisableOverrideUrlLoading = false;
    378             if (!isPrivateBrowsingEnabled()) {
    379                 LogTag.logPageFinishedLoading(
    380                         url, SystemClock.uptimeMillis() - mLoadStartTime);
    381             }
    382             syncCurrentState(view, url);
    383             mWebViewController.onPageFinished(Tab.this);
    384         }
    385 
    386         // return true if want to hijack the url to let another app to handle it
    387         @Override
    388         public boolean shouldOverrideUrlLoading(WebView view, String url) {
    389             if (!mDisableOverrideUrlLoading && mInForeground) {
    390                 return mWebViewController.shouldOverrideUrlLoading(Tab.this,
    391                         view, url);
    392             } else {
    393                 return false;
    394             }
    395         }
    396 
    397         /**
    398          * Updates the security state. This method is called when we discover
    399          * another resource to be loaded for this page (for example,
    400          * javascript). While we update the security state, we do not update
    401          * the lock icon until we are done loading, as it is slightly more
    402          * secure this way.
    403          */
    404         @Override
    405         public void onLoadResource(WebView view, String url) {
    406             if (url != null && url.length() > 0) {
    407                 // It is only if the page claims to be secure that we may have
    408                 // to update the security state:
    409                 if (mCurrentState.mSecurityState == SecurityState.SECURITY_STATE_SECURE) {
    410                     // If NOT a 'safe' url, change the state to mixed content!
    411                     if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
    412                             || URLUtil.isAboutUrl(url))) {
    413                         mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_MIXED;
    414                     }
    415                 }
    416             }
    417         }
    418 
    419         /**
    420          * Show a dialog informing the user of the network error reported by
    421          * WebCore if it is in the foreground.
    422          */
    423         @Override
    424         public void onReceivedError(WebView view, int errorCode,
    425                 String description, String failingUrl) {
    426             if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
    427                     errorCode != WebViewClient.ERROR_CONNECT &&
    428                     errorCode != WebViewClient.ERROR_BAD_URL &&
    429                     errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
    430                     errorCode != WebViewClient.ERROR_FILE) {
    431                 queueError(errorCode, description);
    432 
    433                 // Don't log URLs when in private browsing mode
    434                 if (!isPrivateBrowsingEnabled()) {
    435                     Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
    436                         + " " + description);
    437                 }
    438             }
    439         }
    440 
    441         /**
    442          * Check with the user if it is ok to resend POST data as the page they
    443          * are trying to navigate to is the result of a POST.
    444          */
    445         @Override
    446         public void onFormResubmission(WebView view, final Message dontResend,
    447                                        final Message resend) {
    448             if (!mInForeground) {
    449                 dontResend.sendToTarget();
    450                 return;
    451             }
    452             if (mDontResend != null) {
    453                 Log.w(LOGTAG, "onFormResubmission should not be called again "
    454                         + "while dialog is still up");
    455                 dontResend.sendToTarget();
    456                 return;
    457             }
    458             mDontResend = dontResend;
    459             mResend = resend;
    460             new AlertDialog.Builder(mContext).setTitle(
    461                     R.string.browserFrameFormResubmitLabel).setMessage(
    462                     R.string.browserFrameFormResubmitMessage)
    463                     .setPositiveButton(R.string.ok,
    464                             new DialogInterface.OnClickListener() {
    465                                 public void onClick(DialogInterface dialog,
    466                                         int which) {
    467                                     if (mResend != null) {
    468                                         mResend.sendToTarget();
    469                                         mResend = null;
    470                                         mDontResend = null;
    471                                     }
    472                                 }
    473                             }).setNegativeButton(R.string.cancel,
    474                             new DialogInterface.OnClickListener() {
    475                                 public void onClick(DialogInterface dialog,
    476                                         int which) {
    477                                     if (mDontResend != null) {
    478                                         mDontResend.sendToTarget();
    479                                         mResend = null;
    480                                         mDontResend = null;
    481                                     }
    482                                 }
    483                             }).setOnCancelListener(new OnCancelListener() {
    484                         public void onCancel(DialogInterface dialog) {
    485                             if (mDontResend != null) {
    486                                 mDontResend.sendToTarget();
    487                                 mResend = null;
    488                                 mDontResend = null;
    489                             }
    490                         }
    491                     }).show();
    492         }
    493 
    494         /**
    495          * Insert the url into the visited history database.
    496          * @param url The url to be inserted.
    497          * @param isReload True if this url is being reloaded.
    498          * FIXME: Not sure what to do when reloading the page.
    499          */
    500         @Override
    501         public void doUpdateVisitedHistory(WebView view, String url,
    502                 boolean isReload) {
    503             mWebViewController.doUpdateVisitedHistory(Tab.this, isReload);
    504         }
    505 
    506         /**
    507          * Displays SSL error(s) dialog to the user.
    508          */
    509         @Override
    510         public void onReceivedSslError(final WebView view,
    511                 final SslErrorHandler handler, final SslError error) {
    512             if (!mInForeground) {
    513                 handler.cancel();
    514                 setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE);
    515                 return;
    516             }
    517             if (mSettings.showSecurityWarnings()) {
    518                 new AlertDialog.Builder(mContext)
    519                     .setTitle(R.string.security_warning)
    520                     .setMessage(R.string.ssl_warnings_header)
    521                     .setIconAttribute(android.R.attr.alertDialogIcon)
    522                     .setPositiveButton(R.string.ssl_continue,
    523                         new DialogInterface.OnClickListener() {
    524                             @Override
    525                             public void onClick(DialogInterface dialog,
    526                                     int whichButton) {
    527                                 handler.proceed();
    528                                 handleProceededAfterSslError(error);
    529                             }
    530                         })
    531                     .setNeutralButton(R.string.view_certificate,
    532                         new DialogInterface.OnClickListener() {
    533                             @Override
    534                             public void onClick(DialogInterface dialog,
    535                                     int whichButton) {
    536                                 mWebViewController.showSslCertificateOnError(
    537                                         view, handler, error);
    538                             }
    539                         })
    540                     .setNegativeButton(R.string.ssl_go_back,
    541                         new DialogInterface.OnClickListener() {
    542                             @Override
    543                             public void onClick(DialogInterface dialog,
    544                                     int whichButton) {
    545                                 dialog.cancel();
    546                             }
    547                         })
    548                     .setOnCancelListener(
    549                         new DialogInterface.OnCancelListener() {
    550                             @Override
    551                             public void onCancel(DialogInterface dialog) {
    552                                 handler.cancel();
    553                                 setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE);
    554                                 mWebViewController.onUserCanceledSsl(Tab.this);
    555                             }
    556                         })
    557                     .show();
    558             } else {
    559                 handler.proceed();
    560             }
    561         }
    562 
    563        /**
    564          * Handles an HTTP authentication request.
    565          *
    566          * @param handler The authentication handler
    567          * @param host The host
    568          * @param realm The realm
    569          */
    570         @Override
    571         public void onReceivedHttpAuthRequest(WebView view,
    572                 final HttpAuthHandler handler, final String host,
    573                 final String realm) {
    574             mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm);
    575         }
    576 
    577         @Override
    578         public WebResourceResponse shouldInterceptRequest(WebView view,
    579                 String url) {
    580             Uri uri = Uri.parse(url);
    581             if (uri.getScheme().toLowerCase().equals("file")) {
    582                 File file = new File(uri.getPath());
    583                 try {
    584                     if (file.getCanonicalPath().startsWith(
    585                             mContext.getApplicationContext().getApplicationInfo().dataDir)) {
    586                         return new WebResourceResponse("text/html","UTF-8",
    587                                 new ByteArrayInputStream(RESTRICTED.getBytes("UTF-8")));
    588                     }
    589                 } catch (Exception ex) {
    590                     Log.e(LOGTAG, "Bad canonical path" + ex.toString());
    591                     try {
    592                         return new WebResourceResponse("text/html","UTF-8",
    593                                 new ByteArrayInputStream(RESTRICTED.getBytes("UTF-8")));
    594                     } catch (java.io.UnsupportedEncodingException e) {
    595                     }
    596                 }
    597             }
    598             WebResourceResponse res = HomeProvider.shouldInterceptRequest(
    599                     mContext, url);
    600             return res;
    601         }
    602 
    603         @Override
    604         public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
    605             if (!mInForeground) {
    606                 return false;
    607             }
    608             return mWebViewController.shouldOverrideKeyEvent(event);
    609         }
    610 
    611         @Override
    612         public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
    613             if (!mInForeground) {
    614                 return;
    615             }
    616             if (!mWebViewController.onUnhandledKeyEvent(event)) {
    617                 super.onUnhandledKeyEvent(view, event);
    618             }
    619         }
    620 
    621         @Override
    622         public void onReceivedLoginRequest(WebView view, String realm,
    623                 String account, String args) {
    624             new DeviceAccountLogin(mWebViewController.getActivity(), view, Tab.this, mWebViewController)
    625                     .handleLogin(realm, account, args);
    626         }
    627 
    628     };
    629 
    630     private void syncCurrentState(WebView view, String url) {
    631         // Sync state (in case of stop/timeout)
    632         mCurrentState.mUrl = view.getUrl();
    633         if (mCurrentState.mUrl == null) {
    634             mCurrentState.mUrl = "";
    635         }
    636         mCurrentState.mOriginalUrl = view.getOriginalUrl();
    637         mCurrentState.mTitle = view.getTitle();
    638         mCurrentState.mFavicon = view.getFavicon();
    639         if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
    640             // In case we stop when loading an HTTPS page from an HTTP page
    641             // but before a provisional load occurred
    642             mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
    643             mCurrentState.mSslCertificateError = null;
    644         }
    645         mCurrentState.mIncognito = view.isPrivateBrowsingEnabled();
    646     }
    647 
    648     // Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
    649     // displayed.
    650     void setDeviceAccountLogin(DeviceAccountLogin login) {
    651         mDeviceAccountLogin = login;
    652     }
    653 
    654     // Returns non-null if the title bar should display the auto-login UI.
    655     DeviceAccountLogin getDeviceAccountLogin() {
    656         return mDeviceAccountLogin;
    657     }
    658 
    659     // -------------------------------------------------------------------------
    660     // WebChromeClient implementation for the main WebView
    661     // -------------------------------------------------------------------------
    662 
    663     private final WebChromeClient mWebChromeClient = new WebChromeClient() {
    664         // Helper method to create a new tab or sub window.
    665         private void createWindow(final boolean dialog, final Message msg) {
    666             WebView.WebViewTransport transport =
    667                     (WebView.WebViewTransport) msg.obj;
    668             if (dialog) {
    669                 createSubWindow();
    670                 mWebViewController.attachSubWindow(Tab.this);
    671                 transport.setWebView(mSubView);
    672             } else {
    673                 final Tab newTab = mWebViewController.openTab(null,
    674                         Tab.this, true, true);
    675                 transport.setWebView(newTab.getWebView());
    676             }
    677             msg.sendToTarget();
    678         }
    679 
    680         @Override
    681         public boolean onCreateWindow(WebView view, final boolean dialog,
    682                 final boolean userGesture, final Message resultMsg) {
    683             // only allow new window or sub window for the foreground case
    684             if (!mInForeground) {
    685                 return false;
    686             }
    687             // Short-circuit if we can't create any more tabs or sub windows.
    688             if (dialog && mSubView != null) {
    689                 new AlertDialog.Builder(mContext)
    690                         .setTitle(R.string.too_many_subwindows_dialog_title)
    691                         .setIconAttribute(android.R.attr.alertDialogIcon)
    692                         .setMessage(R.string.too_many_subwindows_dialog_message)
    693                         .setPositiveButton(R.string.ok, null)
    694                         .show();
    695                 return false;
    696             } else if (!mWebViewController.getTabControl().canCreateNewTab()) {
    697                 new AlertDialog.Builder(mContext)
    698                         .setTitle(R.string.too_many_windows_dialog_title)
    699                         .setIconAttribute(android.R.attr.alertDialogIcon)
    700                         .setMessage(R.string.too_many_windows_dialog_message)
    701                         .setPositiveButton(R.string.ok, null)
    702                         .show();
    703                 return false;
    704             }
    705 
    706             // Short-circuit if this was a user gesture.
    707             if (userGesture) {
    708                 createWindow(dialog, resultMsg);
    709                 return true;
    710             }
    711 
    712             // Allow the popup and create the appropriate window.
    713             final AlertDialog.OnClickListener allowListener =
    714                     new AlertDialog.OnClickListener() {
    715                         public void onClick(DialogInterface d,
    716                                 int which) {
    717                             createWindow(dialog, resultMsg);
    718                         }
    719                     };
    720 
    721             // Block the popup by returning a null WebView.
    722             final AlertDialog.OnClickListener blockListener =
    723                     new AlertDialog.OnClickListener() {
    724                         public void onClick(DialogInterface d, int which) {
    725                             resultMsg.sendToTarget();
    726                         }
    727                     };
    728 
    729             // Build a confirmation dialog to display to the user.
    730             final AlertDialog d =
    731                     new AlertDialog.Builder(mContext)
    732                     .setIconAttribute(android.R.attr.alertDialogIcon)
    733                     .setMessage(R.string.popup_window_attempt)
    734                     .setPositiveButton(R.string.allow, allowListener)
    735                     .setNegativeButton(R.string.block, blockListener)
    736                     .setCancelable(false)
    737                     .create();
    738 
    739             // Show the confirmation dialog.
    740             d.show();
    741             return true;
    742         }
    743 
    744         @Override
    745         public void onRequestFocus(WebView view) {
    746             if (!mInForeground) {
    747                 mWebViewController.switchToTab(Tab.this);
    748             }
    749         }
    750 
    751         @Override
    752         public void onCloseWindow(WebView window) {
    753             if (mParent != null) {
    754                 // JavaScript can only close popup window.
    755                 if (mInForeground) {
    756                     mWebViewController.switchToTab(mParent);
    757                 }
    758                 mWebViewController.closeTab(Tab.this);
    759             }
    760         }
    761 
    762         @Override
    763         public void onProgressChanged(WebView view, int newProgress) {
    764             mPageLoadProgress = newProgress;
    765             if (newProgress == 100) {
    766                 mInPageLoad = false;
    767             }
    768             mWebViewController.onProgressChanged(Tab.this);
    769             if (mUpdateThumbnail && newProgress == 100) {
    770                 mUpdateThumbnail = false;
    771             }
    772         }
    773 
    774         @Override
    775         public void onReceivedTitle(WebView view, final String title) {
    776             mCurrentState.mTitle = title;
    777             mWebViewController.onReceivedTitle(Tab.this, title);
    778         }
    779 
    780         @Override
    781         public void onReceivedIcon(WebView view, Bitmap icon) {
    782             mCurrentState.mFavicon = icon;
    783             mWebViewController.onFavicon(Tab.this, view, icon);
    784         }
    785 
    786         @Override
    787         public void onReceivedTouchIconUrl(WebView view, String url,
    788                 boolean precomposed) {
    789             final ContentResolver cr = mContext.getContentResolver();
    790             // Let precomposed icons take precedence over non-composed
    791             // icons.
    792             if (precomposed && mTouchIconLoader != null) {
    793                 mTouchIconLoader.cancel(false);
    794                 mTouchIconLoader = null;
    795             }
    796             // Have only one async task at a time.
    797             if (mTouchIconLoader == null) {
    798                 mTouchIconLoader = new DownloadTouchIcon(Tab.this,
    799                         mContext, cr, view);
    800                 mTouchIconLoader.execute(url);
    801             }
    802         }
    803 
    804         @Override
    805         public void onShowCustomView(View view,
    806                 WebChromeClient.CustomViewCallback callback) {
    807             Activity activity = mWebViewController.getActivity();
    808             if (activity != null) {
    809                 onShowCustomView(view, activity.getRequestedOrientation(), callback);
    810             }
    811         }
    812 
    813         @Override
    814         public void onShowCustomView(View view, int requestedOrientation,
    815                 WebChromeClient.CustomViewCallback callback) {
    816             if (mInForeground) mWebViewController.showCustomView(Tab.this, view,
    817                     requestedOrientation, callback);
    818         }
    819 
    820         @Override
    821         public void onHideCustomView() {
    822             if (mInForeground) mWebViewController.hideCustomView();
    823         }
    824 
    825         /**
    826          * The origin has exceeded its database quota.
    827          * @param url the URL that exceeded the quota
    828          * @param databaseIdentifier the identifier of the database on which the
    829          *            transaction that caused the quota overflow was run
    830          * @param currentQuota the current quota for the origin.
    831          * @param estimatedSize the estimated size of the database.
    832          * @param totalUsedQuota is the sum of all origins' quota.
    833          * @param quotaUpdater The callback to run when a decision to allow or
    834          *            deny quota has been made. Don't forget to call this!
    835          */
    836         @Override
    837         public void onExceededDatabaseQuota(String url,
    838             String databaseIdentifier, long currentQuota, long estimatedSize,
    839             long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
    840             mSettings.getWebStorageSizeManager()
    841                     .onExceededDatabaseQuota(url, databaseIdentifier,
    842                             currentQuota, estimatedSize, totalUsedQuota,
    843                             quotaUpdater);
    844         }
    845 
    846         /**
    847          * The Application Cache has exceeded its max size.
    848          * @param spaceNeeded is the amount of disk space that would be needed
    849          *            in order for the last appcache operation to succeed.
    850          * @param totalUsedQuota is the sum of all origins' quota.
    851          * @param quotaUpdater A callback to inform the WebCore thread that a
    852          *            new app cache size is available. This callback must always
    853          *            be executed at some point to ensure that the sleeping
    854          *            WebCore thread is woken up.
    855          */
    856         @Override
    857         public void onReachedMaxAppCacheSize(long spaceNeeded,
    858                 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
    859             mSettings.getWebStorageSizeManager()
    860                     .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
    861                             quotaUpdater);
    862         }
    863 
    864         /**
    865          * Instructs the browser to show a prompt to ask the user to set the
    866          * Geolocation permission state for the specified origin.
    867          * @param origin The origin for which Geolocation permissions are
    868          *     requested.
    869          * @param callback The callback to call once the user has set the
    870          *     Geolocation permission state.
    871          */
    872         @Override
    873         public void onGeolocationPermissionsShowPrompt(String origin,
    874                 GeolocationPermissions.Callback callback) {
    875             if (mInForeground) {
    876                 getGeolocationPermissionsPrompt().show(origin, callback);
    877             }
    878         }
    879 
    880         /**
    881          * Instructs the browser to hide the Geolocation permissions prompt.
    882          */
    883         @Override
    884         public void onGeolocationPermissionsHidePrompt() {
    885             if (mInForeground && mGeolocationPermissionsPrompt != null) {
    886                 mGeolocationPermissionsPrompt.hide();
    887             }
    888         }
    889 
    890         /* Adds a JavaScript error message to the system log and if the JS
    891          * console is enabled in the about:debug options, to that console
    892          * also.
    893          * @param consoleMessage the message object.
    894          */
    895         @Override
    896         public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    897             if (mInForeground) {
    898                 // call getErrorConsole(true) so it will create one if needed
    899                 ErrorConsoleView errorConsole = getErrorConsole(true);
    900                 errorConsole.addErrorMessage(consoleMessage);
    901                 if (mWebViewController.shouldShowErrorConsole()
    902                         && errorConsole.getShowState() !=
    903                             ErrorConsoleView.SHOW_MAXIMIZED) {
    904                     errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
    905                 }
    906             }
    907 
    908             // Don't log console messages in private browsing mode
    909             if (isPrivateBrowsingEnabled()) return true;
    910 
    911             String message = "Console: " + consoleMessage.message() + " "
    912                     + consoleMessage.sourceId() +  ":"
    913                     + consoleMessage.lineNumber();
    914 
    915             switch (consoleMessage.messageLevel()) {
    916                 case TIP:
    917                     Log.v(CONSOLE_LOGTAG, message);
    918                     break;
    919                 case LOG:
    920                     Log.i(CONSOLE_LOGTAG, message);
    921                     break;
    922                 case WARNING:
    923                     Log.w(CONSOLE_LOGTAG, message);
    924                     break;
    925                 case ERROR:
    926                     Log.e(CONSOLE_LOGTAG, message);
    927                     break;
    928                 case DEBUG:
    929                     Log.d(CONSOLE_LOGTAG, message);
    930                     break;
    931             }
    932 
    933             return true;
    934         }
    935 
    936         /**
    937          * Ask the browser for an icon to represent a <video> element.
    938          * This icon will be used if the Web page did not specify a poster attribute.
    939          * @return Bitmap The icon or null if no such icon is available.
    940          */
    941         @Override
    942         public Bitmap getDefaultVideoPoster() {
    943             if (mInForeground) {
    944                 return mWebViewController.getDefaultVideoPoster();
    945             }
    946             return null;
    947         }
    948 
    949         /**
    950          * Ask the host application for a custom progress view to show while
    951          * a <video> is loading.
    952          * @return View The progress view.
    953          */
    954         @Override
    955         public View getVideoLoadingProgressView() {
    956             if (mInForeground) {
    957                 return mWebViewController.getVideoLoadingProgressView();
    958             }
    959             return null;
    960         }
    961 
    962         @Override
    963         public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
    964             if (mInForeground) {
    965                 mWebViewController.openFileChooser(uploadMsg, acceptType, capture);
    966             } else {
    967                 uploadMsg.onReceiveValue(null);
    968             }
    969         }
    970 
    971         /**
    972          * Deliver a list of already-visited URLs
    973          */
    974         @Override
    975         public void getVisitedHistory(final ValueCallback<String[]> callback) {
    976             mWebViewController.getVisitedHistory(callback);
    977         }
    978 
    979     };
    980 
    981     // -------------------------------------------------------------------------
    982     // WebViewClient implementation for the sub window
    983     // -------------------------------------------------------------------------
    984 
    985     // Subclass of WebViewClient used in subwindows to notify the main
    986     // WebViewClient of certain WebView activities.
    987     private static class SubWindowClient extends WebViewClient {
    988         // The main WebViewClient.
    989         private final WebViewClient mClient;
    990         private final WebViewController mController;
    991 
    992         SubWindowClient(WebViewClient client, WebViewController controller) {
    993             mClient = client;
    994             mController = controller;
    995         }
    996         @Override
    997         public void onPageStarted(WebView view, String url, Bitmap favicon) {
    998             // Unlike the others, do not call mClient's version, which would
    999             // change the progress bar.  However, we do want to remove the
   1000             // find or select dialog.
   1001             mController.endActionMode();
   1002         }
   1003         @Override
   1004         public void doUpdateVisitedHistory(WebView view, String url,
   1005                 boolean isReload) {
   1006             mClient.doUpdateVisitedHistory(view, url, isReload);
   1007         }
   1008         @Override
   1009         public boolean shouldOverrideUrlLoading(WebView view, String url) {
   1010             return mClient.shouldOverrideUrlLoading(view, url);
   1011         }
   1012         @Override
   1013         public void onReceivedSslError(WebView view, SslErrorHandler handler,
   1014                 SslError error) {
   1015             mClient.onReceivedSslError(view, handler, error);
   1016         }
   1017         @Override
   1018         public void onReceivedHttpAuthRequest(WebView view,
   1019                 HttpAuthHandler handler, String host, String realm) {
   1020             mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
   1021         }
   1022         @Override
   1023         public void onFormResubmission(WebView view, Message dontResend,
   1024                 Message resend) {
   1025             mClient.onFormResubmission(view, dontResend, resend);
   1026         }
   1027         @Override
   1028         public void onReceivedError(WebView view, int errorCode,
   1029                 String description, String failingUrl) {
   1030             mClient.onReceivedError(view, errorCode, description, failingUrl);
   1031         }
   1032         @Override
   1033         public boolean shouldOverrideKeyEvent(WebView view,
   1034                 android.view.KeyEvent event) {
   1035             return mClient.shouldOverrideKeyEvent(view, event);
   1036         }
   1037         @Override
   1038         public void onUnhandledKeyEvent(WebView view,
   1039                 android.view.KeyEvent event) {
   1040             mClient.onUnhandledKeyEvent(view, event);
   1041         }
   1042     }
   1043 
   1044     // -------------------------------------------------------------------------
   1045     // WebChromeClient implementation for the sub window
   1046     // -------------------------------------------------------------------------
   1047 
   1048     private class SubWindowChromeClient extends WebChromeClient {
   1049         // The main WebChromeClient.
   1050         private final WebChromeClient mClient;
   1051 
   1052         SubWindowChromeClient(WebChromeClient client) {
   1053             mClient = client;
   1054         }
   1055         @Override
   1056         public void onProgressChanged(WebView view, int newProgress) {
   1057             mClient.onProgressChanged(view, newProgress);
   1058         }
   1059         @Override
   1060         public boolean onCreateWindow(WebView view, boolean dialog,
   1061                 boolean userGesture, android.os.Message resultMsg) {
   1062             return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
   1063         }
   1064         @Override
   1065         public void onCloseWindow(WebView window) {
   1066             if (window != mSubView) {
   1067                 Log.e(LOGTAG, "Can't close the window");
   1068             }
   1069             mWebViewController.dismissSubWindow(Tab.this);
   1070         }
   1071     }
   1072 
   1073     // -------------------------------------------------------------------------
   1074 
   1075     // Construct a new tab
   1076     Tab(WebViewController wvcontroller, WebView w) {
   1077         this(wvcontroller, w, null);
   1078     }
   1079 
   1080     Tab(WebViewController wvcontroller, Bundle state) {
   1081         this(wvcontroller, null, state);
   1082     }
   1083 
   1084     Tab(WebViewController wvcontroller, WebView w, Bundle state) {
   1085         mWebViewController = wvcontroller;
   1086         mContext = mWebViewController.getContext();
   1087         mSettings = BrowserSettings.getInstance();
   1088         mDataController = DataController.getInstance(mContext);
   1089         mCurrentState = new PageState(mContext, w != null
   1090                 ? w.isPrivateBrowsingEnabled() : false);
   1091         mInPageLoad = false;
   1092         mInForeground = false;
   1093 
   1094         mDownloadListener = new BrowserDownloadListener() {
   1095             public void onDownloadStart(String url, String userAgent,
   1096                     String contentDisposition, String mimetype, String referer,
   1097                     long contentLength) {
   1098                 mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,
   1099                         mimetype, referer, contentLength);
   1100             }
   1101         };
   1102         mWebBackForwardListClient = new WebBackForwardListClient() {
   1103             @Override
   1104             public void onNewHistoryItem(WebHistoryItem item) {
   1105                 if (mClearHistoryUrlPattern != null) {
   1106                     boolean match =
   1107                         mClearHistoryUrlPattern.matcher(item.getOriginalUrl()).matches();
   1108                     if (LOGD_ENABLED) {
   1109                         Log.d(LOGTAG, "onNewHistoryItem: match=" + match + "\n\t"
   1110                                 + item.getUrl() + "\n\t"
   1111                                 + mClearHistoryUrlPattern);
   1112                     }
   1113                     if (match) {
   1114                         if (mMainView != null) {
   1115                             mMainView.clearHistory();
   1116                         }
   1117                     }
   1118                     mClearHistoryUrlPattern = null;
   1119                 }
   1120             }
   1121         };
   1122 
   1123         mCaptureWidth = mContext.getResources().getDimensionPixelSize(
   1124                 R.dimen.tab_thumbnail_width);
   1125         mCaptureHeight = mContext.getResources().getDimensionPixelSize(
   1126                 R.dimen.tab_thumbnail_height);
   1127         updateShouldCaptureThumbnails();
   1128         restoreState(state);
   1129         if (getId() == -1) {
   1130             mId = TabControl.getNextId();
   1131         }
   1132         setWebView(w);
   1133         mHandler = new Handler() {
   1134             @Override
   1135             public void handleMessage(Message m) {
   1136                 switch (m.what) {
   1137                 case MSG_CAPTURE:
   1138                     capture();
   1139                     break;
   1140                 }
   1141             }
   1142         };
   1143     }
   1144 
   1145     public boolean shouldUpdateThumbnail() {
   1146         return mUpdateThumbnail;
   1147     }
   1148 
   1149     /**
   1150      * This is used to get a new ID when the tab has been preloaded, before it is displayed and
   1151      * added to TabControl. Preloaded tabs can be created before restoreInstanceState, leading
   1152      * to overlapping IDs between the preloaded and restored tabs.
   1153      */
   1154     public void refreshIdAfterPreload() {
   1155         mId = TabControl.getNextId();
   1156     }
   1157 
   1158     public void updateShouldCaptureThumbnails() {
   1159         if (mWebViewController.shouldCaptureThumbnails()) {
   1160             synchronized (Tab.this) {
   1161                 if (mCapture == null) {
   1162                     mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight,
   1163                             Bitmap.Config.RGB_565);
   1164                     mCapture.eraseColor(Color.WHITE);
   1165                     if (mInForeground) {
   1166                         postCapture();
   1167                     }
   1168                 }
   1169             }
   1170         } else {
   1171             synchronized (Tab.this) {
   1172                 mCapture = null;
   1173                 deleteThumbnail();
   1174             }
   1175         }
   1176     }
   1177 
   1178     public void setController(WebViewController ctl) {
   1179         mWebViewController = ctl;
   1180         updateShouldCaptureThumbnails();
   1181     }
   1182 
   1183     public long getId() {
   1184         return mId;
   1185     }
   1186 
   1187     void setWebView(WebView w) {
   1188         setWebView(w, true);
   1189     }
   1190 
   1191     /**
   1192      * Sets the WebView for this tab, correctly removing the old WebView from
   1193      * the container view.
   1194      */
   1195     void setWebView(WebView w, boolean restore) {
   1196         if (mMainView == w) {
   1197             return;
   1198         }
   1199 
   1200         // If the WebView is changing, the page will be reloaded, so any ongoing
   1201         // Geolocation permission requests are void.
   1202         if (mGeolocationPermissionsPrompt != null) {
   1203             mGeolocationPermissionsPrompt.hide();
   1204         }
   1205 
   1206         mWebViewController.onSetWebView(this, w);
   1207 
   1208         if (mMainView != null) {
   1209             mMainView.setPictureListener(null);
   1210             if (w != null) {
   1211                 syncCurrentState(w, null);
   1212             } else {
   1213                 mCurrentState = new PageState(mContext, false);
   1214             }
   1215         }
   1216         // set the new one
   1217         mMainView = w;
   1218         // attach the WebViewClient, WebChromeClient and DownloadListener
   1219         if (mMainView != null) {
   1220             mMainView.setWebViewClient(mWebViewClient);
   1221             mMainView.setWebChromeClient(mWebChromeClient);
   1222             // Attach DownloadManager so that downloads can start in an active
   1223             // or a non-active window. This can happen when going to a site that
   1224             // does a redirect after a period of time. The user could have
   1225             // switched to another tab while waiting for the download to start.
   1226             mMainView.setDownloadListener(mDownloadListener);
   1227             TabControl tc = mWebViewController.getTabControl();
   1228             if (tc != null && tc.getOnThumbnailUpdatedListener() != null) {
   1229                 mMainView.setPictureListener(this);
   1230             }
   1231             if (restore && (mSavedState != null)) {
   1232                 restoreUserAgent();
   1233                 WebBackForwardList restoredState
   1234                         = mMainView.restoreState(mSavedState);
   1235                 if (restoredState == null || restoredState.getSize() == 0) {
   1236                     Log.w(LOGTAG, "Failed to restore WebView state!");
   1237                     loadUrl(mCurrentState.mOriginalUrl, null);
   1238                 }
   1239                 mSavedState = null;
   1240             }
   1241         }
   1242     }
   1243 
   1244     /**
   1245      * Destroy the tab's main WebView and subWindow if any
   1246      */
   1247     void destroy() {
   1248         if (mMainView != null) {
   1249             dismissSubWindow();
   1250             // save the WebView to call destroy() after detach it from the tab
   1251             WebView webView = mMainView;
   1252             setWebView(null);
   1253             webView.destroy();
   1254         }
   1255     }
   1256 
   1257     /**
   1258      * Remove the tab from the parent
   1259      */
   1260     void removeFromTree() {
   1261         // detach the children
   1262         if (mChildren != null) {
   1263             for(Tab t : mChildren) {
   1264                 t.setParent(null);
   1265             }
   1266         }
   1267         // remove itself from the parent list
   1268         if (mParent != null) {
   1269             mParent.mChildren.remove(this);
   1270         }
   1271         deleteThumbnail();
   1272     }
   1273 
   1274     /**
   1275      * Create a new subwindow unless a subwindow already exists.
   1276      * @return True if a new subwindow was created. False if one already exists.
   1277      */
   1278     boolean createSubWindow() {
   1279         if (mSubView == null) {
   1280             mWebViewController.createSubWindow(this);
   1281             mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
   1282                     mWebViewController));
   1283             mSubView.setWebChromeClient(new SubWindowChromeClient(
   1284                     mWebChromeClient));
   1285             // Set a different DownloadListener for the mSubView, since it will
   1286             // just need to dismiss the mSubView, rather than close the Tab
   1287             mSubView.setDownloadListener(new BrowserDownloadListener() {
   1288                 public void onDownloadStart(String url, String userAgent,
   1289                         String contentDisposition, String mimetype, String referer,
   1290                         long contentLength) {
   1291                     mWebViewController.onDownloadStart(Tab.this, url, userAgent,
   1292                             contentDisposition, mimetype, referer, contentLength);
   1293                     if (mSubView.copyBackForwardList().getSize() == 0) {
   1294                         // This subwindow was opened for the sole purpose of
   1295                         // downloading a file. Remove it.
   1296                         mWebViewController.dismissSubWindow(Tab.this);
   1297                     }
   1298                 }
   1299             });
   1300             mSubView.setOnCreateContextMenuListener(mWebViewController.getActivity());
   1301             return true;
   1302         }
   1303         return false;
   1304     }
   1305 
   1306     /**
   1307      * Dismiss the subWindow for the tab.
   1308      */
   1309     void dismissSubWindow() {
   1310         if (mSubView != null) {
   1311             mWebViewController.endActionMode();
   1312             mSubView.destroy();
   1313             mSubView = null;
   1314             mSubViewContainer = null;
   1315         }
   1316     }
   1317 
   1318 
   1319     /**
   1320      * Set the parent tab of this tab.
   1321      */
   1322     void setParent(Tab parent) {
   1323         if (parent == this) {
   1324             throw new IllegalStateException("Cannot set parent to self!");
   1325         }
   1326         mParent = parent;
   1327         // This tab may have been freed due to low memory. If that is the case,
   1328         // the parent tab id is already saved. If we are changing that id
   1329         // (most likely due to removing the parent tab) we must update the
   1330         // parent tab id in the saved Bundle.
   1331         if (mSavedState != null) {
   1332             if (parent == null) {
   1333                 mSavedState.remove(PARENTTAB);
   1334             } else {
   1335                 mSavedState.putLong(PARENTTAB, parent.getId());
   1336             }
   1337         }
   1338 
   1339         // Sync the WebView useragent with the parent
   1340         if (parent != null && mSettings.hasDesktopUseragent(parent.getWebView())
   1341                 != mSettings.hasDesktopUseragent(getWebView())) {
   1342             mSettings.toggleDesktopUseragent(getWebView());
   1343         }
   1344 
   1345         if (parent != null && parent.getId() == getId()) {
   1346             throw new IllegalStateException("Parent has same ID as child!");
   1347         }
   1348     }
   1349 
   1350     /**
   1351      * If this Tab was created through another Tab, then this method returns
   1352      * that Tab.
   1353      * @return the Tab parent or null
   1354      */
   1355     public Tab getParent() {
   1356         return mParent;
   1357     }
   1358 
   1359     /**
   1360      * When a Tab is created through the content of another Tab, then we
   1361      * associate the Tabs.
   1362      * @param child the Tab that was created from this Tab
   1363      */
   1364     void addChildTab(Tab child) {
   1365         if (mChildren == null) {
   1366             mChildren = new Vector<Tab>();
   1367         }
   1368         mChildren.add(child);
   1369         child.setParent(this);
   1370     }
   1371 
   1372     Vector<Tab> getChildren() {
   1373         return mChildren;
   1374     }
   1375 
   1376     void resume() {
   1377         if (mMainView != null) {
   1378             setupHwAcceleration(mMainView);
   1379             mMainView.onResume();
   1380             if (mSubView != null) {
   1381                 mSubView.onResume();
   1382             }
   1383         }
   1384     }
   1385 
   1386     private void setupHwAcceleration(View web) {
   1387         if (web == null) return;
   1388         BrowserSettings settings = BrowserSettings.getInstance();
   1389         if (settings.isHardwareAccelerated()) {
   1390             web.setLayerType(View.LAYER_TYPE_NONE, null);
   1391         } else {
   1392             web.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
   1393         }
   1394     }
   1395 
   1396     void pause() {
   1397         if (mMainView != null) {
   1398             mMainView.onPause();
   1399             if (mSubView != null) {
   1400                 mSubView.onPause();
   1401             }
   1402         }
   1403     }
   1404 
   1405     void putInForeground() {
   1406         if (mInForeground) {
   1407             return;
   1408         }
   1409         mInForeground = true;
   1410         resume();
   1411         Activity activity = mWebViewController.getActivity();
   1412         mMainView.setOnCreateContextMenuListener(activity);
   1413         if (mSubView != null) {
   1414             mSubView.setOnCreateContextMenuListener(activity);
   1415         }
   1416         // Show the pending error dialog if the queue is not empty
   1417         if (mQueuedErrors != null && mQueuedErrors.size() >  0) {
   1418             showError(mQueuedErrors.getFirst());
   1419         }
   1420         mWebViewController.bookmarkedStatusHasChanged(this);
   1421     }
   1422 
   1423     void putInBackground() {
   1424         if (!mInForeground) {
   1425             return;
   1426         }
   1427         capture();
   1428         mInForeground = false;
   1429         pause();
   1430         mMainView.setOnCreateContextMenuListener(null);
   1431         if (mSubView != null) {
   1432             mSubView.setOnCreateContextMenuListener(null);
   1433         }
   1434     }
   1435 
   1436     boolean inForeground() {
   1437         return mInForeground;
   1438     }
   1439 
   1440     /**
   1441      * Return the top window of this tab; either the subwindow if it is not
   1442      * null or the main window.
   1443      * @return The top window of this tab.
   1444      */
   1445     WebView getTopWindow() {
   1446         if (mSubView != null) {
   1447             return mSubView;
   1448         }
   1449         return mMainView;
   1450     }
   1451 
   1452     /**
   1453      * Return the main window of this tab. Note: if a tab is freed in the
   1454      * background, this can return null. It is only guaranteed to be
   1455      * non-null for the current tab.
   1456      * @return The main WebView of this tab.
   1457      */
   1458     WebView getWebView() {
   1459         return mMainView;
   1460     }
   1461 
   1462     void setViewContainer(View container) {
   1463         mContainer = container;
   1464     }
   1465 
   1466     View getViewContainer() {
   1467         return mContainer;
   1468     }
   1469 
   1470     /**
   1471      * Return whether private browsing is enabled for the main window of
   1472      * this tab.
   1473      * @return True if private browsing is enabled.
   1474      */
   1475     boolean isPrivateBrowsingEnabled() {
   1476         return mCurrentState.mIncognito;
   1477     }
   1478 
   1479     /**
   1480      * Return the subwindow of this tab or null if there is no subwindow.
   1481      * @return The subwindow of this tab or null.
   1482      */
   1483     WebView getSubWebView() {
   1484         return mSubView;
   1485     }
   1486 
   1487     void setSubWebView(WebView subView) {
   1488         mSubView = subView;
   1489     }
   1490 
   1491     View getSubViewContainer() {
   1492         return mSubViewContainer;
   1493     }
   1494 
   1495     void setSubViewContainer(View subViewContainer) {
   1496         mSubViewContainer = subViewContainer;
   1497     }
   1498 
   1499     /**
   1500      * @return The geolocation permissions prompt for this tab.
   1501      */
   1502     GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
   1503         if (mGeolocationPermissionsPrompt == null) {
   1504             ViewStub stub = (ViewStub) mContainer
   1505                     .findViewById(R.id.geolocation_permissions_prompt);
   1506             mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
   1507                     .inflate();
   1508         }
   1509         return mGeolocationPermissionsPrompt;
   1510     }
   1511 
   1512     /**
   1513      * @return The application id string
   1514      */
   1515     String getAppId() {
   1516         return mAppId;
   1517     }
   1518 
   1519     /**
   1520      * Set the application id string
   1521      * @param id
   1522      */
   1523     void setAppId(String id) {
   1524         mAppId = id;
   1525     }
   1526 
   1527     boolean closeOnBack() {
   1528         return mCloseOnBack;
   1529     }
   1530 
   1531     void setCloseOnBack(boolean close) {
   1532         mCloseOnBack = close;
   1533     }
   1534 
   1535     String getUrl() {
   1536         return UrlUtils.filteredUrl(mCurrentState.mUrl);
   1537     }
   1538 
   1539     String getOriginalUrl() {
   1540         if (mCurrentState.mOriginalUrl == null) {
   1541             return getUrl();
   1542         }
   1543         return UrlUtils.filteredUrl(mCurrentState.mOriginalUrl);
   1544     }
   1545 
   1546     /**
   1547      * Get the title of this tab.
   1548      */
   1549     String getTitle() {
   1550         if (mCurrentState.mTitle == null && mInPageLoad) {
   1551             return mContext.getString(R.string.title_bar_loading);
   1552         }
   1553         return mCurrentState.mTitle;
   1554     }
   1555 
   1556     /**
   1557      * Get the favicon of this tab.
   1558      */
   1559     Bitmap getFavicon() {
   1560         if (mCurrentState.mFavicon != null) {
   1561             return mCurrentState.mFavicon;
   1562         }
   1563         return getDefaultFavicon(mContext);
   1564     }
   1565 
   1566     public boolean isBookmarkedSite() {
   1567         return mCurrentState.mIsBookmarkedSite;
   1568     }
   1569 
   1570     /**
   1571      * Return the tab's error console. Creates the console if createIfNEcessary
   1572      * is true and we haven't already created the console.
   1573      * @param createIfNecessary Flag to indicate if the console should be
   1574      *            created if it has not been already.
   1575      * @return The tab's error console, or null if one has not been created and
   1576      *         createIfNecessary is false.
   1577      */
   1578     ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
   1579         if (createIfNecessary && mErrorConsole == null) {
   1580             mErrorConsole = new ErrorConsoleView(mContext);
   1581             mErrorConsole.setWebView(mMainView);
   1582         }
   1583         return mErrorConsole;
   1584     }
   1585 
   1586     /**
   1587      * Sets the security state, clears the SSL certificate error and informs
   1588      * the controller.
   1589      */
   1590     private void setSecurityState(SecurityState securityState) {
   1591         mCurrentState.mSecurityState = securityState;
   1592         mCurrentState.mSslCertificateError = null;
   1593         mWebViewController.onUpdatedSecurityState(this);
   1594     }
   1595 
   1596     /**
   1597      * @return The tab's security state.
   1598      */
   1599     SecurityState getSecurityState() {
   1600         return mCurrentState.mSecurityState;
   1601     }
   1602 
   1603     /**
   1604      * Gets the SSL certificate error, if any, for the page's main resource.
   1605      * This is only non-null when the security state is
   1606      * SECURITY_STATE_BAD_CERTIFICATE.
   1607      */
   1608     SslError getSslCertificateError() {
   1609         return mCurrentState.mSslCertificateError;
   1610     }
   1611 
   1612     int getLoadProgress() {
   1613         if (mInPageLoad) {
   1614             return mPageLoadProgress;
   1615         }
   1616         return 100;
   1617     }
   1618 
   1619     /**
   1620      * @return TRUE if onPageStarted is called while onPageFinished is not
   1621      *         called yet.
   1622      */
   1623     boolean inPageLoad() {
   1624         return mInPageLoad;
   1625     }
   1626 
   1627     /**
   1628      * @return The Bundle with the tab's state if it can be saved, otherwise null
   1629      */
   1630     public Bundle saveState() {
   1631         // If the WebView is null it means we ran low on memory and we already
   1632         // stored the saved state in mSavedState.
   1633         if (mMainView == null) {
   1634             return mSavedState;
   1635         }
   1636 
   1637         if (TextUtils.isEmpty(mCurrentState.mUrl)) {
   1638             return null;
   1639         }
   1640 
   1641         mSavedState = new Bundle();
   1642         WebBackForwardList savedList = mMainView.saveState(mSavedState);
   1643         if (savedList == null || savedList.getSize() == 0) {
   1644             Log.w(LOGTAG, "Failed to save back/forward list for "
   1645                     + mCurrentState.mUrl);
   1646         }
   1647 
   1648         mSavedState.putLong(ID, mId);
   1649         mSavedState.putString(CURRURL, mCurrentState.mUrl);
   1650         mSavedState.putString(CURRTITLE, mCurrentState.mTitle);
   1651         mSavedState.putBoolean(INCOGNITO, mMainView.isPrivateBrowsingEnabled());
   1652         if (mAppId != null) {
   1653             mSavedState.putString(APPID, mAppId);
   1654         }
   1655         mSavedState.putBoolean(CLOSEFLAG, mCloseOnBack);
   1656         // Remember the parent tab so the relationship can be restored.
   1657         if (mParent != null) {
   1658             mSavedState.putLong(PARENTTAB, mParent.mId);
   1659         }
   1660         mSavedState.putBoolean(USERAGENT,
   1661                 mSettings.hasDesktopUseragent(getWebView()));
   1662         return mSavedState;
   1663     }
   1664 
   1665     /*
   1666      * Restore the state of the tab.
   1667      */
   1668     private void restoreState(Bundle b) {
   1669         mSavedState = b;
   1670         if (mSavedState == null) {
   1671             return;
   1672         }
   1673         // Restore the internal state even if the WebView fails to restore.
   1674         // This will maintain the app id, original url and close-on-exit values.
   1675         mId = b.getLong(ID);
   1676         mAppId = b.getString(APPID);
   1677         mCloseOnBack = b.getBoolean(CLOSEFLAG);
   1678         restoreUserAgent();
   1679         String url = b.getString(CURRURL);
   1680         String title = b.getString(CURRTITLE);
   1681         boolean incognito = b.getBoolean(INCOGNITO);
   1682         mCurrentState = new PageState(mContext, incognito, url, null);
   1683         mCurrentState.mTitle = title;
   1684         synchronized (Tab.this) {
   1685             if (mCapture != null) {
   1686                 DataController.getInstance(mContext).loadThumbnail(this);
   1687             }
   1688         }
   1689     }
   1690 
   1691     private void restoreUserAgent() {
   1692         if (mMainView == null || mSavedState == null) {
   1693             return;
   1694         }
   1695         if (mSavedState.getBoolean(USERAGENT)
   1696                 != mSettings.hasDesktopUseragent(mMainView)) {
   1697             mSettings.toggleDesktopUseragent(mMainView);
   1698         }
   1699     }
   1700 
   1701     public void updateBookmarkedStatus() {
   1702         mDataController.queryBookmarkStatus(getUrl(), mIsBookmarkCallback);
   1703     }
   1704 
   1705     private DataController.OnQueryUrlIsBookmark mIsBookmarkCallback
   1706             = new DataController.OnQueryUrlIsBookmark() {
   1707         @Override
   1708         public void onQueryUrlIsBookmark(String url, boolean isBookmark) {
   1709             if (mCurrentState.mUrl.equals(url)) {
   1710                 mCurrentState.mIsBookmarkedSite = isBookmark;
   1711                 mWebViewController.bookmarkedStatusHasChanged(Tab.this);
   1712             }
   1713         }
   1714     };
   1715 
   1716     public Bitmap getScreenshot() {
   1717         synchronized (Tab.this) {
   1718             return mCapture;
   1719         }
   1720     }
   1721 
   1722     public boolean isSnapshot() {
   1723         return false;
   1724     }
   1725 
   1726     private static class SaveCallback implements ValueCallback<Boolean> {
   1727         boolean mResult;
   1728 
   1729         @Override
   1730         public void onReceiveValue(Boolean value) {
   1731             mResult = value;
   1732             synchronized (this) {
   1733                 notifyAll();
   1734             }
   1735         }
   1736 
   1737     }
   1738 
   1739     /**
   1740      * Must be called on the UI thread
   1741      */
   1742     public ContentValues createSnapshotValues() {
   1743         return null;
   1744     }
   1745 
   1746     /**
   1747      * Probably want to call this on a background thread
   1748      */
   1749     public boolean saveViewState(ContentValues values) {
   1750         return false;
   1751     }
   1752 
   1753     public byte[] compressBitmap(Bitmap bitmap) {
   1754         if (bitmap == null) {
   1755             return null;
   1756         }
   1757         ByteArrayOutputStream stream = new ByteArrayOutputStream();
   1758         bitmap.compress(CompressFormat.PNG, 100, stream);
   1759         return stream.toByteArray();
   1760     }
   1761 
   1762     public void loadUrl(String url, Map<String, String> headers) {
   1763         if (mMainView != null) {
   1764             mPageLoadProgress = INITIAL_PROGRESS;
   1765             mInPageLoad = true;
   1766             mCurrentState = new PageState(mContext, false, url, null);
   1767             mWebViewController.onPageStarted(this, mMainView, null);
   1768             mMainView.loadUrl(url, headers);
   1769         }
   1770     }
   1771 
   1772     public void disableUrlOverridingForLoad() {
   1773         mDisableOverrideUrlLoading = true;
   1774     }
   1775 
   1776     protected void capture() {
   1777         if (mMainView == null || mCapture == null) return;
   1778         if (mMainView.getContentWidth() <= 0 || mMainView.getContentHeight() <= 0) {
   1779             return;
   1780         }
   1781         Canvas c = new Canvas(mCapture);
   1782         final int left = mMainView.getScrollX();
   1783         final int top = mMainView.getScrollY() + mMainView.getVisibleTitleHeight();
   1784         int state = c.save();
   1785         c.translate(-left, -top);
   1786         float scale = mCaptureWidth / (float) mMainView.getWidth();
   1787         c.scale(scale, scale, left, top);
   1788         if (mMainView instanceof BrowserWebView) {
   1789             ((BrowserWebView)mMainView).drawContent(c);
   1790         } else {
   1791             mMainView.draw(c);
   1792         }
   1793         c.restoreToCount(state);
   1794         // manually anti-alias the edges for the tilt
   1795         c.drawRect(0, 0, 1, mCapture.getHeight(), sAlphaPaint);
   1796         c.drawRect(mCapture.getWidth() - 1, 0, mCapture.getWidth(),
   1797                 mCapture.getHeight(), sAlphaPaint);
   1798         c.drawRect(0, 0, mCapture.getWidth(), 1, sAlphaPaint);
   1799         c.drawRect(0, mCapture.getHeight() - 1, mCapture.getWidth(),
   1800                 mCapture.getHeight(), sAlphaPaint);
   1801         c.setBitmap(null);
   1802         mHandler.removeMessages(MSG_CAPTURE);
   1803         persistThumbnail();
   1804         TabControl tc = mWebViewController.getTabControl();
   1805         if (tc != null) {
   1806             OnThumbnailUpdatedListener updateListener
   1807                     = tc.getOnThumbnailUpdatedListener();
   1808             if (updateListener != null) {
   1809                 updateListener.onThumbnailUpdated(this);
   1810             }
   1811         }
   1812     }
   1813 
   1814     @Override
   1815     public void onNewPicture(WebView view, Picture picture) {
   1816         postCapture();
   1817     }
   1818 
   1819     private void postCapture() {
   1820         if (!mHandler.hasMessages(MSG_CAPTURE)) {
   1821             mHandler.sendEmptyMessageDelayed(MSG_CAPTURE, CAPTURE_DELAY);
   1822         }
   1823     }
   1824 
   1825     public boolean canGoBack() {
   1826         return mMainView != null ? mMainView.canGoBack() : false;
   1827     }
   1828 
   1829     public boolean canGoForward() {
   1830         return mMainView != null ? mMainView.canGoForward() : false;
   1831     }
   1832 
   1833     public void goBack() {
   1834         if (mMainView != null) {
   1835             mMainView.goBack();
   1836         }
   1837     }
   1838 
   1839     public void goForward() {
   1840         if (mMainView != null) {
   1841             mMainView.goForward();
   1842         }
   1843     }
   1844 
   1845     /**
   1846      * Causes the tab back/forward stack to be cleared once, if the given URL is the next URL
   1847      * to be added to the stack.
   1848      *
   1849      * This is used to ensure that preloaded URLs that are not subsequently seen by the user do
   1850      * not appear in the back stack.
   1851      */
   1852     public void clearBackStackWhenItemAdded(Pattern urlPattern) {
   1853         mClearHistoryUrlPattern = urlPattern;
   1854     }
   1855 
   1856     protected void persistThumbnail() {
   1857         DataController.getInstance(mContext).saveThumbnail(this);
   1858     }
   1859 
   1860     protected void deleteThumbnail() {
   1861         DataController.getInstance(mContext).deleteThumbnail(this);
   1862     }
   1863 
   1864     void updateCaptureFromBlob(byte[] blob) {
   1865         synchronized (Tab.this) {
   1866             if (mCapture == null) {
   1867                 return;
   1868             }
   1869             ByteBuffer buffer = ByteBuffer.wrap(blob);
   1870             try {
   1871                 mCapture.copyPixelsFromBuffer(buffer);
   1872             } catch (RuntimeException rex) {
   1873                 Log.e(LOGTAG, "Load capture has mismatched sizes; buffer: "
   1874                         + buffer.capacity() + " blob: " + blob.length
   1875                         + "capture: " + mCapture.getByteCount());
   1876                 throw rex;
   1877             }
   1878         }
   1879     }
   1880 
   1881     @Override
   1882     public String toString() {
   1883         StringBuilder builder = new StringBuilder(100);
   1884         builder.append(mId);
   1885         builder.append(") has parent: ");
   1886         if (getParent() != null) {
   1887             builder.append("true[");
   1888             builder.append(getParent().getId());
   1889             builder.append("]");
   1890         } else {
   1891             builder.append("false");
   1892         }
   1893         builder.append(", incog: ");
   1894         builder.append(isPrivateBrowsingEnabled());
   1895         if (!isPrivateBrowsingEnabled()) {
   1896             builder.append(", title: ");
   1897             builder.append(getTitle());
   1898             builder.append(", url: ");
   1899             builder.append(getUrl());
   1900         }
   1901         return builder.toString();
   1902     }
   1903 
   1904     private void handleProceededAfterSslError(SslError error) {
   1905         if (error.getUrl().equals(mCurrentState.mUrl)) {
   1906             // The security state should currently be SECURITY_STATE_SECURE.
   1907             setSecurityState(SecurityState.SECURITY_STATE_BAD_CERTIFICATE);
   1908             mCurrentState.mSslCertificateError = error;
   1909         } else if (getSecurityState() == SecurityState.SECURITY_STATE_SECURE) {
   1910             // The page's main resource is secure and this error is for a
   1911             // sub-resource.
   1912             setSecurityState(SecurityState.SECURITY_STATE_MIXED);
   1913         }
   1914     }
   1915 }
   1916