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