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