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