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