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