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 java.io.File;
     20 import java.util.ArrayList;
     21 import java.util.HashMap;
     22 import java.util.Iterator;
     23 import java.util.LinkedList;
     24 import java.util.Map;
     25 import java.util.Vector;
     26 
     27 import android.app.AlertDialog;
     28 import android.app.SearchManager;
     29 import android.content.ContentResolver;
     30 import android.content.ContentValues;
     31 import android.content.DialogInterface;
     32 import android.content.DialogInterface.OnCancelListener;
     33 import android.content.Intent;
     34 import android.database.Cursor;
     35 import android.database.sqlite.SQLiteDatabase;
     36 import android.database.sqlite.SQLiteException;
     37 import android.graphics.Bitmap;
     38 import android.net.Uri;
     39 import android.net.http.SslError;
     40 import android.os.AsyncTask;
     41 import android.os.Bundle;
     42 import android.os.Message;
     43 import android.os.SystemClock;
     44 import android.provider.Browser;
     45 import android.speech.RecognizerResultsIntent;
     46 import android.util.Log;
     47 import android.view.KeyEvent;
     48 import android.view.LayoutInflater;
     49 import android.view.View;
     50 import android.view.ViewGroup;
     51 import android.view.ViewStub;
     52 import android.view.View.OnClickListener;
     53 import android.webkit.ConsoleMessage;
     54 import android.webkit.CookieSyncManager;
     55 import android.webkit.DownloadListener;
     56 import android.webkit.GeolocationPermissions;
     57 import android.webkit.HttpAuthHandler;
     58 import android.webkit.SslErrorHandler;
     59 import android.webkit.URLUtil;
     60 import android.webkit.ValueCallback;
     61 import android.webkit.WebBackForwardList;
     62 import android.webkit.WebBackForwardListClient;
     63 import android.webkit.WebChromeClient;
     64 import android.webkit.WebHistoryItem;
     65 import android.webkit.WebIconDatabase;
     66 import android.webkit.WebStorage;
     67 import android.webkit.WebView;
     68 import android.webkit.WebViewClient;
     69 import android.widget.FrameLayout;
     70 import android.widget.ImageButton;
     71 import android.widget.LinearLayout;
     72 import android.widget.TextView;
     73 
     74 import com.android.common.speech.LoggingEvents;
     75 
     76 /**
     77  * Class for maintaining Tabs with a main WebView and a subwindow.
     78  */
     79 class Tab {
     80     // Log Tag
     81     private static final String LOGTAG = "Tab";
     82     // Special case the logtag for messages for the Console to make it easier to
     83     // filter them and match the logtag used for these messages in older versions
     84     // of the browser.
     85     private static final String CONSOLE_LOGTAG = "browser";
     86 
     87     // The Geolocation permissions prompt
     88     private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
     89     // Main WebView wrapper
     90     private LinearLayout mContainer;
     91     // Main WebView
     92     private WebView mMainView;
     93     // Subwindow container
     94     private View mSubViewContainer;
     95     // Subwindow WebView
     96     private WebView mSubView;
     97     // Saved bundle for when we are running low on memory. It contains the
     98     // information needed to restore the WebView if the user goes back to the
     99     // tab.
    100     private Bundle mSavedState;
    101     // Data used when displaying the tab in the picker.
    102     private PickerData mPickerData;
    103     // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
    104     // created by the UI
    105     private Tab mParentTab;
    106     // Tab that constructed by this Tab. This is used when this Tab is
    107     // destroyed, it clears all mParentTab values in the children.
    108     private Vector<Tab> mChildTabs;
    109     // If true, the tab will be removed when back out of the first page.
    110     private boolean mCloseOnExit;
    111     // If true, the tab is in the foreground of the current activity.
    112     private boolean mInForeground;
    113     // If true, the tab is in loading state.
    114     private boolean mInLoad;
    115     // The time the load started, used to find load page time
    116     private long mLoadStartTime;
    117     // Application identifier used to find tabs that another application wants
    118     // to reuse.
    119     private String mAppId;
    120     // Keep the original url around to avoid killing the old WebView if the url
    121     // has not changed.
    122     private String mOriginalUrl;
    123     // Error console for the tab
    124     private ErrorConsoleView mErrorConsole;
    125     // the lock icon type and previous lock icon type for the tab
    126     private int mLockIconType;
    127     private int mPrevLockIconType;
    128     // Inflation service for making subwindows.
    129     private final LayoutInflater mInflateService;
    130     // The BrowserActivity which owners the Tab
    131     private final BrowserActivity mActivity;
    132     // The listener that gets invoked when a download is started from the
    133     // mMainView
    134     private final DownloadListener mDownloadListener;
    135     // Listener used to know when we move forward or back in the history list.
    136     private final WebBackForwardListClient mWebBackForwardListClient;
    137 
    138     // AsyncTask for downloading touch icons
    139     DownloadTouchIcon mTouchIconLoader;
    140 
    141     // Extra saved information for displaying the tab in the picker.
    142     private static class PickerData {
    143         String  mUrl;
    144         String  mTitle;
    145         Bitmap  mFavicon;
    146     }
    147 
    148     // Used for saving and restoring each Tab
    149     static final String WEBVIEW = "webview";
    150     static final String NUMTABS = "numTabs";
    151     static final String CURRTAB = "currentTab";
    152     static final String CURRURL = "currentUrl";
    153     static final String CURRTITLE = "currentTitle";
    154     static final String CLOSEONEXIT = "closeonexit";
    155     static final String PARENTTAB = "parentTab";
    156     static final String APPID = "appid";
    157     static final String ORIGINALURL = "originalUrl";
    158 
    159     // -------------------------------------------------------------------------
    160 
    161     /**
    162      * Private information regarding the latest voice search.  If the Tab is not
    163      * in voice search mode, this will be null.
    164      */
    165     private VoiceSearchData mVoiceSearchData;
    166     /**
    167      * Remove voice search mode from this tab.
    168      */
    169     public void revertVoiceSearchMode() {
    170         if (mVoiceSearchData != null) {
    171             mVoiceSearchData = null;
    172             if (mInForeground) {
    173                 mActivity.revertVoiceTitleBar();
    174             }
    175         }
    176     }
    177     /**
    178      * Return whether the tab is in voice search mode.
    179      */
    180     public boolean isInVoiceSearchMode() {
    181         return mVoiceSearchData != null;
    182     }
    183     /**
    184      * Return true if the Tab is in voice search mode and the voice search
    185      * Intent came with a String identifying that Google provided the Intent.
    186      */
    187     public boolean voiceSearchSourceIsGoogle() {
    188         return mVoiceSearchData != null && mVoiceSearchData.mSourceIsGoogle;
    189     }
    190     /**
    191      * Get the title to display for the current voice search page.  If the Tab
    192      * is not in voice search mode, return null.
    193      */
    194     public String getVoiceDisplayTitle() {
    195         if (mVoiceSearchData == null) return null;
    196         return mVoiceSearchData.mLastVoiceSearchTitle;
    197     }
    198     /**
    199      * Get the latest array of voice search results, to be passed to the
    200      * BrowserProvider.  If the Tab is not in voice search mode, return null.
    201      */
    202     public ArrayList<String> getVoiceSearchResults() {
    203         if (mVoiceSearchData == null) return null;
    204         return mVoiceSearchData.mVoiceSearchResults;
    205     }
    206     /**
    207      * Activate voice search mode.
    208      * @param intent Intent which has the results to use, or an index into the
    209      *      results when reusing the old results.
    210      */
    211     /* package */ void activateVoiceSearchMode(Intent intent) {
    212         int index = 0;
    213         ArrayList<String> results = intent.getStringArrayListExtra(
    214                     RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_STRINGS);
    215         if (results != null) {
    216             ArrayList<String> urls = intent.getStringArrayListExtra(
    217                         RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS);
    218             ArrayList<String> htmls = intent.getStringArrayListExtra(
    219                         RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_HTML);
    220             ArrayList<String> baseUrls = intent.getStringArrayListExtra(
    221                         RecognizerResultsIntent
    222                         .EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS);
    223             // This tab is now entering voice search mode for the first time, or
    224             // a new voice search was done.
    225             int size = results.size();
    226             if (urls == null || size != urls.size()) {
    227                 throw new AssertionError("improper extras passed in Intent");
    228             }
    229             if (htmls == null || htmls.size() != size || baseUrls == null ||
    230                     (baseUrls.size() != size && baseUrls.size() != 1)) {
    231                 // If either of these arrays are empty/incorrectly sized, ignore
    232                 // them.
    233                 htmls = null;
    234                 baseUrls = null;
    235             }
    236             mVoiceSearchData = new VoiceSearchData(results, urls, htmls,
    237                     baseUrls);
    238             mVoiceSearchData.mHeaders = intent.getParcelableArrayListExtra(
    239                     RecognizerResultsIntent
    240                     .EXTRA_VOICE_SEARCH_RESULT_HTTP_HEADERS);
    241             mVoiceSearchData.mSourceIsGoogle = intent.getBooleanExtra(
    242                     VoiceSearchData.SOURCE_IS_GOOGLE, false);
    243             mVoiceSearchData.mVoiceSearchIntent = new Intent(intent);
    244         }
    245         String extraData = intent.getStringExtra(
    246                 SearchManager.EXTRA_DATA_KEY);
    247         if (extraData != null) {
    248             index = Integer.parseInt(extraData);
    249             if (index >= mVoiceSearchData.mVoiceSearchResults.size()) {
    250                 throw new AssertionError("index must be less than "
    251                         + "size of mVoiceSearchResults");
    252             }
    253             if (mVoiceSearchData.mSourceIsGoogle) {
    254                 Intent logIntent = new Intent(
    255                         LoggingEvents.ACTION_LOG_EVENT);
    256                 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
    257                         LoggingEvents.VoiceSearch.N_BEST_CHOOSE);
    258                 logIntent.putExtra(
    259                         LoggingEvents.VoiceSearch.EXTRA_N_BEST_CHOOSE_INDEX,
    260                         index);
    261                 mActivity.sendBroadcast(logIntent);
    262             }
    263             if (mVoiceSearchData.mVoiceSearchIntent != null) {
    264                 // Copy the Intent, so that each history item will have its own
    265                 // Intent, with different (or none) extra data.
    266                 Intent latest = new Intent(mVoiceSearchData.mVoiceSearchIntent);
    267                 latest.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
    268                 mVoiceSearchData.mVoiceSearchIntent = latest;
    269             }
    270         }
    271         mVoiceSearchData.mLastVoiceSearchTitle
    272                 = mVoiceSearchData.mVoiceSearchResults.get(index);
    273         if (mInForeground) {
    274             mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle);
    275         }
    276         if (mVoiceSearchData.mVoiceSearchHtmls != null) {
    277             // When index was found it was already ensured that it was valid
    278             String uriString = mVoiceSearchData.mVoiceSearchHtmls.get(index);
    279             if (uriString != null) {
    280                 Uri dataUri = Uri.parse(uriString);
    281                 if (RecognizerResultsIntent.URI_SCHEME_INLINE.equals(
    282                         dataUri.getScheme())) {
    283                     // If there is only one base URL, use it.  If there are
    284                     // more, there will be one for each index, so use the base
    285                     // URL corresponding to the index.
    286                     String baseUrl = mVoiceSearchData.mVoiceSearchBaseUrls.get(
    287                             mVoiceSearchData.mVoiceSearchBaseUrls.size() > 1 ?
    288                             index : 0);
    289                     mVoiceSearchData.mLastVoiceSearchUrl = baseUrl;
    290                     mMainView.loadDataWithBaseURL(baseUrl,
    291                             uriString.substring(RecognizerResultsIntent
    292                             .URI_SCHEME_INLINE.length() + 1), "text/html",
    293                             "utf-8", baseUrl);
    294                     return;
    295                 }
    296             }
    297         }
    298         mVoiceSearchData.mLastVoiceSearchUrl
    299                 = mVoiceSearchData.mVoiceSearchUrls.get(index);
    300         if (null == mVoiceSearchData.mLastVoiceSearchUrl) {
    301             mVoiceSearchData.mLastVoiceSearchUrl = mActivity.smartUrlFilter(
    302                     mVoiceSearchData.mLastVoiceSearchTitle);
    303         }
    304         Map<String, String> headers = null;
    305         if (mVoiceSearchData.mHeaders != null) {
    306             int bundleIndex = mVoiceSearchData.mHeaders.size() == 1 ? 0
    307                     : index;
    308             Bundle bundle = mVoiceSearchData.mHeaders.get(bundleIndex);
    309             if (bundle != null && !bundle.isEmpty()) {
    310                 Iterator<String> iter = bundle.keySet().iterator();
    311                 headers = new HashMap<String, String>();
    312                 while (iter.hasNext()) {
    313                     String key = iter.next();
    314                     headers.put(key, bundle.getString(key));
    315                 }
    316             }
    317         }
    318         mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl, headers);
    319     }
    320     /* package */ static class VoiceSearchData {
    321         public VoiceSearchData(ArrayList<String> results,
    322                 ArrayList<String> urls, ArrayList<String> htmls,
    323                 ArrayList<String> baseUrls) {
    324             mVoiceSearchResults = results;
    325             mVoiceSearchUrls = urls;
    326             mVoiceSearchHtmls = htmls;
    327             mVoiceSearchBaseUrls = baseUrls;
    328         }
    329         /*
    330          * ArrayList of suggestions to be displayed when opening the
    331          * SearchManager
    332          */
    333         public ArrayList<String> mVoiceSearchResults;
    334         /*
    335          * ArrayList of urls, associated with the suggestions in
    336          * mVoiceSearchResults.
    337          */
    338         public ArrayList<String> mVoiceSearchUrls;
    339         /*
    340          * ArrayList holding content to load for each item in
    341          * mVoiceSearchResults.
    342          */
    343         public ArrayList<String> mVoiceSearchHtmls;
    344         /*
    345          * ArrayList holding base urls for the items in mVoiceSearchResults.
    346          * If non null, this will either have the same size as
    347          * mVoiceSearchResults or have a size of 1, in which case all will use
    348          * the same base url
    349          */
    350         public ArrayList<String> mVoiceSearchBaseUrls;
    351         /*
    352          * The last url provided by voice search.  Used for comparison to see if
    353          * we are going to a page by some method besides voice search.
    354          */
    355         public String mLastVoiceSearchUrl;
    356         /**
    357          * The last title used for voice search.  Needed to update the title bar
    358          * when switching tabs.
    359          */
    360         public String mLastVoiceSearchTitle;
    361         /**
    362          * Whether the Intent which turned on voice search mode contained the
    363          * String signifying that Google was the source.
    364          */
    365         public boolean mSourceIsGoogle;
    366         /**
    367          * List of headers to be passed into the WebView containing location
    368          * information
    369          */
    370         public ArrayList<Bundle> mHeaders;
    371         /**
    372          * The Intent used to invoke voice search.  Placed on the
    373          * WebHistoryItem so that when coming back to a previous voice search
    374          * page we can again activate voice search.
    375          */
    376         public Intent mVoiceSearchIntent;
    377         /**
    378          * String used to identify Google as the source of voice search.
    379          */
    380         public static String SOURCE_IS_GOOGLE
    381                 = "android.speech.extras.SOURCE_IS_GOOGLE";
    382     }
    383 
    384     // Container class for the next error dialog that needs to be displayed
    385     private class ErrorDialog {
    386         public final int mTitle;
    387         public final String mDescription;
    388         public final int mError;
    389         ErrorDialog(int title, String desc, int error) {
    390             mTitle = title;
    391             mDescription = desc;
    392             mError = error;
    393         }
    394     };
    395 
    396     private void processNextError() {
    397         if (mQueuedErrors == null) {
    398             return;
    399         }
    400         // The first one is currently displayed so just remove it.
    401         mQueuedErrors.removeFirst();
    402         if (mQueuedErrors.size() == 0) {
    403             mQueuedErrors = null;
    404             return;
    405         }
    406         showError(mQueuedErrors.getFirst());
    407     }
    408 
    409     private DialogInterface.OnDismissListener mDialogListener =
    410             new DialogInterface.OnDismissListener() {
    411                 public void onDismiss(DialogInterface d) {
    412                     processNextError();
    413                 }
    414             };
    415     private LinkedList<ErrorDialog> mQueuedErrors;
    416 
    417     private void queueError(int err, String desc) {
    418         if (mQueuedErrors == null) {
    419             mQueuedErrors = new LinkedList<ErrorDialog>();
    420         }
    421         for (ErrorDialog d : mQueuedErrors) {
    422             if (d.mError == err) {
    423                 // Already saw a similar error, ignore the new one.
    424                 return;
    425             }
    426         }
    427         ErrorDialog errDialog = new ErrorDialog(
    428                 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
    429                 R.string.browserFrameFileErrorLabel :
    430                 R.string.browserFrameNetworkErrorLabel,
    431                 desc, err);
    432         mQueuedErrors.addLast(errDialog);
    433 
    434         // Show the dialog now if the queue was empty and it is in foreground
    435         if (mQueuedErrors.size() == 1 && mInForeground) {
    436             showError(errDialog);
    437         }
    438     }
    439 
    440     private void showError(ErrorDialog errDialog) {
    441         if (mInForeground) {
    442             AlertDialog d = new AlertDialog.Builder(mActivity)
    443                     .setTitle(errDialog.mTitle)
    444                     .setMessage(errDialog.mDescription)
    445                     .setPositiveButton(R.string.ok, null)
    446                     .create();
    447             d.setOnDismissListener(mDialogListener);
    448             d.show();
    449         }
    450     }
    451 
    452     // -------------------------------------------------------------------------
    453     // WebViewClient implementation for the main WebView
    454     // -------------------------------------------------------------------------
    455 
    456     private final WebViewClient mWebViewClient = new WebViewClient() {
    457         private Message mDontResend;
    458         private Message mResend;
    459         @Override
    460         public void onPageStarted(WebView view, String url, Bitmap favicon) {
    461             mInLoad = true;
    462             mLoadStartTime = SystemClock.uptimeMillis();
    463             if (mVoiceSearchData != null
    464                     && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
    465                 if (mVoiceSearchData.mSourceIsGoogle) {
    466                     Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
    467                     i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
    468                     mActivity.sendBroadcast(i);
    469                 }
    470                 revertVoiceSearchMode();
    471             }
    472 
    473             // We've started to load a new page. If there was a pending message
    474             // to save a screenshot then we will now take the new page and save
    475             // an incorrect screenshot. Therefore, remove any pending thumbnail
    476             // messages from the queue.
    477             mActivity.removeMessages(BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL,
    478                     view);
    479 
    480             // If we start a touch icon load and then load a new page, we don't
    481             // want to cancel the current touch icon loader. But, we do want to
    482             // create a new one when the touch icon url is known.
    483             if (mTouchIconLoader != null) {
    484                 mTouchIconLoader.mTab = null;
    485                 mTouchIconLoader = null;
    486             }
    487 
    488             // reset the error console
    489             if (mErrorConsole != null) {
    490                 mErrorConsole.clearErrorMessages();
    491                 if (mActivity.shouldShowErrorConsole()) {
    492                     mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
    493                 }
    494             }
    495 
    496             // update the bookmark database for favicon
    497             if (favicon != null) {
    498                 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
    499                         .getContentResolver(), null, url, favicon);
    500             }
    501 
    502             // reset sync timer to avoid sync starts during loading a page
    503             CookieSyncManager.getInstance().resetSync();
    504 
    505             if (!mActivity.isNetworkUp()) {
    506                 view.setNetworkAvailable(false);
    507             }
    508 
    509             // finally update the UI in the activity if it is in the foreground
    510             if (mInForeground) {
    511                 mActivity.onPageStarted(view, url, favicon);
    512             }
    513         }
    514 
    515         @Override
    516         public void onPageFinished(WebView view, String url) {
    517             LogTag.logPageFinishedLoading(
    518                     url, SystemClock.uptimeMillis() - mLoadStartTime);
    519             mInLoad = false;
    520 
    521             if (mInForeground && !mActivity.didUserStopLoading()
    522                     || !mInForeground) {
    523                 // Only update the bookmark screenshot if the user did not
    524                 // cancel the load early.
    525                 mActivity.postMessage(
    526                         BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 0, 0, view,
    527                         500);
    528             }
    529 
    530             // finally update the UI in the activity if it is in the foreground
    531             if (mInForeground) {
    532                 mActivity.onPageFinished(view, url);
    533             }
    534         }
    535 
    536         // return true if want to hijack the url to let another app to handle it
    537         @Override
    538         public boolean shouldOverrideUrlLoading(WebView view, String url) {
    539             if (voiceSearchSourceIsGoogle()) {
    540                 // This method is called when the user clicks on a link.
    541                 // VoiceSearchMode is turned off when the user leaves the
    542                 // Google results page, so at this point the user must be on
    543                 // that page.  If the user clicked a link on that page, assume
    544                 // that the voice search was effective, and broadcast an Intent
    545                 // so a receiver can take note of that fact.
    546                 Intent logIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
    547                 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
    548                         LoggingEvents.VoiceSearch.RESULT_CLICKED);
    549                 mActivity.sendBroadcast(logIntent);
    550             }
    551             if (mInForeground) {
    552                 return mActivity.shouldOverrideUrlLoading(view, url);
    553             } else {
    554                 return false;
    555             }
    556         }
    557 
    558         /**
    559          * Updates the lock icon. This method is called when we discover another
    560          * resource to be loaded for this page (for example, javascript). While
    561          * we update the icon type, we do not update the lock icon itself until
    562          * we are done loading, it is slightly more secure this way.
    563          */
    564         @Override
    565         public void onLoadResource(WebView view, String url) {
    566             if (url != null && url.length() > 0) {
    567                 // It is only if the page claims to be secure that we may have
    568                 // to update the lock:
    569                 if (mLockIconType == BrowserActivity.LOCK_ICON_SECURE) {
    570                     // If NOT a 'safe' url, change the lock to mixed content!
    571                     if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
    572                             || URLUtil.isAboutUrl(url))) {
    573                         mLockIconType = BrowserActivity.LOCK_ICON_MIXED;
    574                     }
    575                 }
    576             }
    577         }
    578 
    579         /**
    580          * Show a dialog informing the user of the network error reported by
    581          * WebCore if it is in the foreground.
    582          */
    583         @Override
    584         public void onReceivedError(WebView view, int errorCode,
    585                 String description, String failingUrl) {
    586             if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
    587                     errorCode != WebViewClient.ERROR_CONNECT &&
    588                     errorCode != WebViewClient.ERROR_BAD_URL &&
    589                     errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
    590                     errorCode != WebViewClient.ERROR_FILE) {
    591                 queueError(errorCode, description);
    592             }
    593             Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
    594                     + " " + description);
    595 
    596             // We need to reset the title after an error if it is in foreground.
    597             if (mInForeground) {
    598                 mActivity.resetTitleAndRevertLockIcon();
    599             }
    600         }
    601 
    602         /**
    603          * Check with the user if it is ok to resend POST data as the page they
    604          * are trying to navigate to is the result of a POST.
    605          */
    606         @Override
    607         public void onFormResubmission(WebView view, final Message dontResend,
    608                                        final Message resend) {
    609             if (!mInForeground) {
    610                 dontResend.sendToTarget();
    611                 return;
    612             }
    613             if (mDontResend != null) {
    614                 Log.w(LOGTAG, "onFormResubmission should not be called again "
    615                         + "while dialog is still up");
    616                 dontResend.sendToTarget();
    617                 return;
    618             }
    619             mDontResend = dontResend;
    620             mResend = resend;
    621             new AlertDialog.Builder(mActivity).setTitle(
    622                     R.string.browserFrameFormResubmitLabel).setMessage(
    623                     R.string.browserFrameFormResubmitMessage)
    624                     .setPositiveButton(R.string.ok,
    625                             new DialogInterface.OnClickListener() {
    626                                 public void onClick(DialogInterface dialog,
    627                                         int which) {
    628                                     if (mResend != null) {
    629                                         mResend.sendToTarget();
    630                                         mResend = null;
    631                                         mDontResend = null;
    632                                     }
    633                                 }
    634                             }).setNegativeButton(R.string.cancel,
    635                             new DialogInterface.OnClickListener() {
    636                                 public void onClick(DialogInterface dialog,
    637                                         int which) {
    638                                     if (mDontResend != null) {
    639                                         mDontResend.sendToTarget();
    640                                         mResend = null;
    641                                         mDontResend = null;
    642                                     }
    643                                 }
    644                             }).setOnCancelListener(new OnCancelListener() {
    645                         public void onCancel(DialogInterface dialog) {
    646                             if (mDontResend != null) {
    647                                 mDontResend.sendToTarget();
    648                                 mResend = null;
    649                                 mDontResend = null;
    650                             }
    651                         }
    652                     }).show();
    653         }
    654 
    655         /**
    656          * Insert the url into the visited history database.
    657          * @param url The url to be inserted.
    658          * @param isReload True if this url is being reloaded.
    659          * FIXME: Not sure what to do when reloading the page.
    660          */
    661         @Override
    662         public void doUpdateVisitedHistory(WebView view, String url,
    663                 boolean isReload) {
    664             if (url.regionMatches(true, 0, "about:", 0, 6)) {
    665                 return;
    666             }
    667             // remove "client" before updating it to the history so that it wont
    668             // show up in the auto-complete list.
    669             int index = url.indexOf("client=ms-");
    670             if (index > 0 && url.contains(".google.")) {
    671                 int end = url.indexOf('&', index);
    672                 if (end > 0) {
    673                     url = url.substring(0, index)
    674                             .concat(url.substring(end + 1));
    675                 } else {
    676                     // the url.charAt(index-1) should be either '?' or '&'
    677                     url = url.substring(0, index-1);
    678                 }
    679             }
    680             final ContentResolver cr = mActivity.getContentResolver();
    681             final String newUrl = url;
    682             new AsyncTask<Void, Void, Void>() {
    683                 protected Void doInBackground(Void... unused) {
    684                     Browser.updateVisitedHistory(cr, newUrl, true);
    685                     return null;
    686                 }
    687             }.execute();
    688             WebIconDatabase.getInstance().retainIconForPageUrl(url);
    689         }
    690 
    691         /**
    692          * Displays SSL error(s) dialog to the user.
    693          */
    694         @Override
    695         public void onReceivedSslError(final WebView view,
    696                 final SslErrorHandler handler, final SslError error) {
    697             if (!mInForeground) {
    698                 handler.cancel();
    699                 return;
    700             }
    701             if (BrowserSettings.getInstance().showSecurityWarnings()) {
    702                 final LayoutInflater factory =
    703                     LayoutInflater.from(mActivity);
    704                 final View warningsView =
    705                     factory.inflate(R.layout.ssl_warnings, null);
    706                 final LinearLayout placeholder =
    707                     (LinearLayout)warningsView.findViewById(R.id.placeholder);
    708 
    709                 if (error.hasError(SslError.SSL_UNTRUSTED)) {
    710                     LinearLayout ll = (LinearLayout)factory
    711                         .inflate(R.layout.ssl_warning, null);
    712                     ((TextView)ll.findViewById(R.id.warning))
    713                         .setText(R.string.ssl_untrusted);
    714                     placeholder.addView(ll);
    715                 }
    716 
    717                 if (error.hasError(SslError.SSL_IDMISMATCH)) {
    718                     LinearLayout ll = (LinearLayout)factory
    719                         .inflate(R.layout.ssl_warning, null);
    720                     ((TextView)ll.findViewById(R.id.warning))
    721                         .setText(R.string.ssl_mismatch);
    722                     placeholder.addView(ll);
    723                 }
    724 
    725                 if (error.hasError(SslError.SSL_EXPIRED)) {
    726                     LinearLayout ll = (LinearLayout)factory
    727                         .inflate(R.layout.ssl_warning, null);
    728                     ((TextView)ll.findViewById(R.id.warning))
    729                         .setText(R.string.ssl_expired);
    730                     placeholder.addView(ll);
    731                 }
    732 
    733                 if (error.hasError(SslError.SSL_NOTYETVALID)) {
    734                     LinearLayout ll = (LinearLayout)factory
    735                         .inflate(R.layout.ssl_warning, null);
    736                     ((TextView)ll.findViewById(R.id.warning))
    737                         .setText(R.string.ssl_not_yet_valid);
    738                     placeholder.addView(ll);
    739                 }
    740 
    741                 new AlertDialog.Builder(mActivity).setTitle(
    742                         R.string.security_warning).setIcon(
    743                         android.R.drawable.ic_dialog_alert).setView(
    744                         warningsView).setPositiveButton(R.string.ssl_continue,
    745                         new DialogInterface.OnClickListener() {
    746                             public void onClick(DialogInterface dialog,
    747                                     int whichButton) {
    748                                 handler.proceed();
    749                             }
    750                         }).setNeutralButton(R.string.view_certificate,
    751                         new DialogInterface.OnClickListener() {
    752                             public void onClick(DialogInterface dialog,
    753                                     int whichButton) {
    754                                 mActivity.showSSLCertificateOnError(view,
    755                                         handler, error);
    756                             }
    757                         }).setNegativeButton(R.string.cancel,
    758                         new DialogInterface.OnClickListener() {
    759                             public void onClick(DialogInterface dialog,
    760                                     int whichButton) {
    761                                 handler.cancel();
    762                                 mActivity.resetTitleAndRevertLockIcon();
    763                             }
    764                         }).setOnCancelListener(
    765                         new DialogInterface.OnCancelListener() {
    766                             public void onCancel(DialogInterface dialog) {
    767                                 handler.cancel();
    768                                 mActivity.resetTitleAndRevertLockIcon();
    769                             }
    770                         }).show();
    771             } else {
    772                 handler.proceed();
    773             }
    774         }
    775 
    776         /**
    777          * Handles an HTTP authentication request.
    778          *
    779          * @param handler The authentication handler
    780          * @param host The host
    781          * @param realm The realm
    782          */
    783         @Override
    784         public void onReceivedHttpAuthRequest(WebView view,
    785                 final HttpAuthHandler handler, final String host,
    786                 final String realm) {
    787             String username = null;
    788             String password = null;
    789 
    790             boolean reuseHttpAuthUsernamePassword = handler
    791                     .useHttpAuthUsernamePassword();
    792 
    793             if (reuseHttpAuthUsernamePassword && view != null) {
    794                 String[] credentials = view.getHttpAuthUsernamePassword(
    795                         host, realm);
    796                 if (credentials != null && credentials.length == 2) {
    797                     username = credentials[0];
    798                     password = credentials[1];
    799                 }
    800             }
    801 
    802             if (username != null && password != null) {
    803                 handler.proceed(username, password);
    804             } else {
    805                 if (mInForeground) {
    806                     mActivity.showHttpAuthentication(handler, host, realm,
    807                             null, null, null, 0);
    808                 } else {
    809                     handler.cancel();
    810                 }
    811             }
    812         }
    813 
    814         @Override
    815         public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
    816             if (!mInForeground) {
    817                 return false;
    818             }
    819             if (mActivity.isMenuDown()) {
    820                 // only check shortcut key when MENU is held
    821                 return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
    822                         event);
    823             } else {
    824                 return false;
    825             }
    826         }
    827 
    828         @Override
    829         public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
    830             if (!mInForeground || mActivity.mActivityInPause) {
    831                 return;
    832             }
    833             if (event.isDown()) {
    834                 mActivity.onKeyDown(event.getKeyCode(), event);
    835             } else {
    836                 mActivity.onKeyUp(event.getKeyCode(), event);
    837             }
    838         }
    839     };
    840 
    841     // -------------------------------------------------------------------------
    842     // WebChromeClient implementation for the main WebView
    843     // -------------------------------------------------------------------------
    844 
    845     private final WebChromeClient mWebChromeClient = new WebChromeClient() {
    846         // Helper method to create a new tab or sub window.
    847         private void createWindow(final boolean dialog, final Message msg) {
    848             WebView.WebViewTransport transport =
    849                     (WebView.WebViewTransport) msg.obj;
    850             if (dialog) {
    851                 createSubWindow();
    852                 mActivity.attachSubWindow(Tab.this);
    853                 transport.setWebView(mSubView);
    854             } else {
    855                 final Tab newTab = mActivity.openTabAndShow(
    856                         BrowserActivity.EMPTY_URL_DATA, false, null);
    857                 if (newTab != Tab.this) {
    858                     Tab.this.addChildTab(newTab);
    859                 }
    860                 transport.setWebView(newTab.getWebView());
    861             }
    862             msg.sendToTarget();
    863         }
    864 
    865         @Override
    866         public boolean onCreateWindow(WebView view, final boolean dialog,
    867                 final boolean userGesture, final Message resultMsg) {
    868             // only allow new window or sub window for the foreground case
    869             if (!mInForeground) {
    870                 return false;
    871             }
    872             // Short-circuit if we can't create any more tabs or sub windows.
    873             if (dialog && mSubView != null) {
    874                 new AlertDialog.Builder(mActivity)
    875                         .setTitle(R.string.too_many_subwindows_dialog_title)
    876                         .setIcon(android.R.drawable.ic_dialog_alert)
    877                         .setMessage(R.string.too_many_subwindows_dialog_message)
    878                         .setPositiveButton(R.string.ok, null)
    879                         .show();
    880                 return false;
    881             } else if (!mActivity.getTabControl().canCreateNewTab()) {
    882                 new AlertDialog.Builder(mActivity)
    883                         .setTitle(R.string.too_many_windows_dialog_title)
    884                         .setIcon(android.R.drawable.ic_dialog_alert)
    885                         .setMessage(R.string.too_many_windows_dialog_message)
    886                         .setPositiveButton(R.string.ok, null)
    887                         .show();
    888                 return false;
    889             }
    890 
    891             // Short-circuit if this was a user gesture.
    892             if (userGesture) {
    893                 createWindow(dialog, resultMsg);
    894                 return true;
    895             }
    896 
    897             // Allow the popup and create the appropriate window.
    898             final AlertDialog.OnClickListener allowListener =
    899                     new AlertDialog.OnClickListener() {
    900                         public void onClick(DialogInterface d,
    901                                 int which) {
    902                             createWindow(dialog, resultMsg);
    903                         }
    904                     };
    905 
    906             // Block the popup by returning a null WebView.
    907             final AlertDialog.OnClickListener blockListener =
    908                     new AlertDialog.OnClickListener() {
    909                         public void onClick(DialogInterface d, int which) {
    910                             resultMsg.sendToTarget();
    911                         }
    912                     };
    913 
    914             // Build a confirmation dialog to display to the user.
    915             final AlertDialog d =
    916                     new AlertDialog.Builder(mActivity)
    917                     .setTitle(R.string.attention)
    918                     .setIcon(android.R.drawable.ic_dialog_alert)
    919                     .setMessage(R.string.popup_window_attempt)
    920                     .setPositiveButton(R.string.allow, allowListener)
    921                     .setNegativeButton(R.string.block, blockListener)
    922                     .setCancelable(false)
    923                     .create();
    924 
    925             // Show the confirmation dialog.
    926             d.show();
    927             return true;
    928         }
    929 
    930         @Override
    931         public void onRequestFocus(WebView view) {
    932             if (!mInForeground) {
    933                 mActivity.switchToTab(mActivity.getTabControl().getTabIndex(
    934                         Tab.this));
    935             }
    936         }
    937 
    938         @Override
    939         public void onCloseWindow(WebView window) {
    940             if (mParentTab != null) {
    941                 // JavaScript can only close popup window.
    942                 if (mInForeground) {
    943                     mActivity.switchToTab(mActivity.getTabControl()
    944                             .getTabIndex(mParentTab));
    945                 }
    946                 mActivity.closeTab(Tab.this);
    947             }
    948         }
    949 
    950         @Override
    951         public void onProgressChanged(WebView view, int newProgress) {
    952             if (newProgress == 100) {
    953                 // sync cookies and cache promptly here.
    954                 CookieSyncManager.getInstance().sync();
    955             }
    956             if (mInForeground) {
    957                 mActivity.onProgressChanged(view, newProgress);
    958             }
    959         }
    960 
    961         @Override
    962         public void onReceivedTitle(WebView view, final String title) {
    963             final String pageUrl = view.getUrl();
    964             if (mInForeground) {
    965                 // here, if url is null, we want to reset the title
    966                 mActivity.setUrlTitle(pageUrl, title);
    967             }
    968             if (pageUrl == null || pageUrl.length()
    969                     >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
    970                 return;
    971             }
    972             new AsyncTask<Void, Void, Void>() {
    973                 protected Void doInBackground(Void... unused) {
    974                     // See if we can find the current url in our history
    975                     // database and add the new title to it.
    976                     String url = pageUrl;
    977                     if (url.startsWith("http://www.")) {
    978                         url = url.substring(11);
    979                     } else if (url.startsWith("http://")) {
    980                         url = url.substring(4);
    981                     }
    982                     // Escape wildcards for LIKE operator.
    983                     url = url.replace("\\", "\\\\").replace("%", "\\%")
    984                             .replace("_", "\\_");
    985                     Cursor c = null;
    986                     try {
    987                         final ContentResolver cr
    988                                 = mActivity.getContentResolver();
    989                         url = "%" + url;
    990                         String [] selArgs = new String[] { url };
    991                         String where = Browser.BookmarkColumns.URL
    992                                 + " LIKE ? ESCAPE '\\' AND "
    993                                 + Browser.BookmarkColumns.BOOKMARK + " = 0";
    994                         c = cr.query(Browser.BOOKMARKS_URI, new String[]
    995                                 { Browser.BookmarkColumns._ID }, where, selArgs,
    996                                 null);
    997                         if (c.moveToFirst()) {
    998                             // Current implementation of database only has one
    999                             // entry per url.
   1000                             ContentValues map = new ContentValues();
   1001                             map.put(Browser.BookmarkColumns.TITLE, title);
   1002                             String[] projection = new String[]
   1003                                     { Integer.valueOf(c.getInt(0)).toString() };
   1004                             cr.update(Browser.BOOKMARKS_URI, map, "_id = ?",
   1005                                     projection);
   1006                         }
   1007                     } catch (IllegalStateException e) {
   1008                         Log.e(LOGTAG, "Tab onReceived title", e);
   1009                     } catch (SQLiteException ex) {
   1010                         Log.e(LOGTAG,
   1011                                 "onReceivedTitle() caught SQLiteException: ",
   1012                                 ex);
   1013                     } finally {
   1014                         if (c != null) c.close();
   1015                     }
   1016                     return null;
   1017                 }
   1018             }.execute();
   1019         }
   1020 
   1021         @Override
   1022         public void onReceivedIcon(WebView view, Bitmap icon) {
   1023             if (icon != null) {
   1024                 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
   1025                         .getContentResolver(), view.getOriginalUrl(), view
   1026                         .getUrl(), icon);
   1027             }
   1028             if (mInForeground) {
   1029                 mActivity.setFavicon(icon);
   1030             }
   1031         }
   1032 
   1033         @Override
   1034         public void onReceivedTouchIconUrl(WebView view, String url,
   1035                 boolean precomposed) {
   1036             final ContentResolver cr = mActivity.getContentResolver();
   1037             // Let precomposed icons take precedence over non-composed
   1038             // icons.
   1039             if (precomposed && mTouchIconLoader != null) {
   1040                 mTouchIconLoader.cancel(false);
   1041                 mTouchIconLoader = null;
   1042             }
   1043             // Have only one async task at a time.
   1044             if (mTouchIconLoader == null) {
   1045                 mTouchIconLoader = new DownloadTouchIcon(Tab.this, cr, view);
   1046                 mTouchIconLoader.execute(url);
   1047             }
   1048         }
   1049 
   1050         @Override
   1051         public void onSelectionDone(WebView view) {
   1052             if (mInForeground) mActivity.closeDialogs();
   1053         }
   1054 
   1055         @Override
   1056         public void onSelectionStart(WebView view) {
   1057             if (false && mInForeground) mActivity.showSelectDialog();
   1058         }
   1059 
   1060         @Override
   1061         public void onShowCustomView(View view,
   1062                 WebChromeClient.CustomViewCallback callback) {
   1063             if (mInForeground) mActivity.onShowCustomView(view, callback);
   1064         }
   1065 
   1066         @Override
   1067         public void onHideCustomView() {
   1068             if (mInForeground) mActivity.onHideCustomView();
   1069         }
   1070 
   1071         /**
   1072          * The origin has exceeded its database quota.
   1073          * @param url the URL that exceeded the quota
   1074          * @param databaseIdentifier the identifier of the database on which the
   1075          *            transaction that caused the quota overflow was run
   1076          * @param currentQuota the current quota for the origin.
   1077          * @param estimatedSize the estimated size of the database.
   1078          * @param totalUsedQuota is the sum of all origins' quota.
   1079          * @param quotaUpdater The callback to run when a decision to allow or
   1080          *            deny quota has been made. Don't forget to call this!
   1081          */
   1082         @Override
   1083         public void onExceededDatabaseQuota(String url,
   1084             String databaseIdentifier, long currentQuota, long estimatedSize,
   1085             long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
   1086             BrowserSettings.getInstance().getWebStorageSizeManager()
   1087                     .onExceededDatabaseQuota(url, databaseIdentifier,
   1088                             currentQuota, estimatedSize, totalUsedQuota,
   1089                             quotaUpdater);
   1090         }
   1091 
   1092         /**
   1093          * The Application Cache has exceeded its max size.
   1094          * @param spaceNeeded is the amount of disk space that would be needed
   1095          *            in order for the last appcache operation to succeed.
   1096          * @param totalUsedQuota is the sum of all origins' quota.
   1097          * @param quotaUpdater A callback to inform the WebCore thread that a
   1098          *            new app cache size is available. This callback must always
   1099          *            be executed at some point to ensure that the sleeping
   1100          *            WebCore thread is woken up.
   1101          */
   1102         @Override
   1103         public void onReachedMaxAppCacheSize(long spaceNeeded,
   1104                 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
   1105             BrowserSettings.getInstance().getWebStorageSizeManager()
   1106                     .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
   1107                             quotaUpdater);
   1108         }
   1109 
   1110         /**
   1111          * Instructs the browser to show a prompt to ask the user to set the
   1112          * Geolocation permission state for the specified origin.
   1113          * @param origin The origin for which Geolocation permissions are
   1114          *     requested.
   1115          * @param callback The callback to call once the user has set the
   1116          *     Geolocation permission state.
   1117          */
   1118         @Override
   1119         public void onGeolocationPermissionsShowPrompt(String origin,
   1120                 GeolocationPermissions.Callback callback) {
   1121             if (mInForeground) {
   1122                 getGeolocationPermissionsPrompt().show(origin, callback);
   1123             }
   1124         }
   1125 
   1126         /**
   1127          * Instructs the browser to hide the Geolocation permissions prompt.
   1128          */
   1129         @Override
   1130         public void onGeolocationPermissionsHidePrompt() {
   1131             if (mInForeground && mGeolocationPermissionsPrompt != null) {
   1132                 mGeolocationPermissionsPrompt.hide();
   1133             }
   1134         }
   1135 
   1136         /* Adds a JavaScript error message to the system log and if the JS
   1137          * console is enabled in the about:debug options, to that console
   1138          * also.
   1139          * @param consoleMessage the message object.
   1140          */
   1141         @Override
   1142         public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
   1143             if (mInForeground) {
   1144                 // call getErrorConsole(true) so it will create one if needed
   1145                 ErrorConsoleView errorConsole = getErrorConsole(true);
   1146                 errorConsole.addErrorMessage(consoleMessage);
   1147                 if (mActivity.shouldShowErrorConsole()
   1148                         && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
   1149                     errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
   1150                 }
   1151             }
   1152 
   1153             String message = "Console: " + consoleMessage.message() + " "
   1154                     + consoleMessage.sourceId() +  ":"
   1155                     + consoleMessage.lineNumber();
   1156 
   1157             switch (consoleMessage.messageLevel()) {
   1158                 case TIP:
   1159                     Log.v(CONSOLE_LOGTAG, message);
   1160                     break;
   1161                 case LOG:
   1162                     Log.i(CONSOLE_LOGTAG, message);
   1163                     break;
   1164                 case WARNING:
   1165                     Log.w(CONSOLE_LOGTAG, message);
   1166                     break;
   1167                 case ERROR:
   1168                     Log.e(CONSOLE_LOGTAG, message);
   1169                     break;
   1170                 case DEBUG:
   1171                     Log.d(CONSOLE_LOGTAG, message);
   1172                     break;
   1173             }
   1174 
   1175             return true;
   1176         }
   1177 
   1178         /**
   1179          * Ask the browser for an icon to represent a <video> element.
   1180          * This icon will be used if the Web page did not specify a poster attribute.
   1181          * @return Bitmap The icon or null if no such icon is available.
   1182          */
   1183         @Override
   1184         public Bitmap getDefaultVideoPoster() {
   1185             if (mInForeground) {
   1186                 return mActivity.getDefaultVideoPoster();
   1187             }
   1188             return null;
   1189         }
   1190 
   1191         /**
   1192          * Ask the host application for a custom progress view to show while
   1193          * a <video> is loading.
   1194          * @return View The progress view.
   1195          */
   1196         @Override
   1197         public View getVideoLoadingProgressView() {
   1198             if (mInForeground) {
   1199                 return mActivity.getVideoLoadingProgressView();
   1200             }
   1201             return null;
   1202         }
   1203 
   1204         @Override
   1205         public void openFileChooser(ValueCallback<Uri> uploadMsg) {
   1206             if (mInForeground) {
   1207                 mActivity.openFileChooser(uploadMsg);
   1208             } else {
   1209                 uploadMsg.onReceiveValue(null);
   1210             }
   1211         }
   1212 
   1213         /**
   1214          * Deliver a list of already-visited URLs
   1215          */
   1216         @Override
   1217         public void getVisitedHistory(final ValueCallback<String[]> callback) {
   1218             AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
   1219                 public String[] doInBackground(Void... unused) {
   1220                     return Browser.getVisitedHistory(mActivity
   1221                             .getContentResolver());
   1222                 }
   1223                 public void onPostExecute(String[] result) {
   1224                     callback.onReceiveValue(result);
   1225                 };
   1226             };
   1227             task.execute();
   1228         };
   1229     };
   1230 
   1231     // -------------------------------------------------------------------------
   1232     // WebViewClient implementation for the sub window
   1233     // -------------------------------------------------------------------------
   1234 
   1235     // Subclass of WebViewClient used in subwindows to notify the main
   1236     // WebViewClient of certain WebView activities.
   1237     private static class SubWindowClient extends WebViewClient {
   1238         // The main WebViewClient.
   1239         private final WebViewClient mClient;
   1240         private final BrowserActivity mBrowserActivity;
   1241 
   1242         SubWindowClient(WebViewClient client, BrowserActivity activity) {
   1243             mClient = client;
   1244             mBrowserActivity = activity;
   1245         }
   1246         @Override
   1247         public void onPageStarted(WebView view, String url, Bitmap favicon) {
   1248             // Unlike the others, do not call mClient's version, which would
   1249             // change the progress bar.  However, we do want to remove the
   1250             // find or select dialog.
   1251             mBrowserActivity.closeDialogs();
   1252         }
   1253         @Override
   1254         public void doUpdateVisitedHistory(WebView view, String url,
   1255                 boolean isReload) {
   1256             mClient.doUpdateVisitedHistory(view, url, isReload);
   1257         }
   1258         @Override
   1259         public boolean shouldOverrideUrlLoading(WebView view, String url) {
   1260             return mClient.shouldOverrideUrlLoading(view, url);
   1261         }
   1262         @Override
   1263         public void onReceivedSslError(WebView view, SslErrorHandler handler,
   1264                 SslError error) {
   1265             mClient.onReceivedSslError(view, handler, error);
   1266         }
   1267         @Override
   1268         public void onReceivedHttpAuthRequest(WebView view,
   1269                 HttpAuthHandler handler, String host, String realm) {
   1270             mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
   1271         }
   1272         @Override
   1273         public void onFormResubmission(WebView view, Message dontResend,
   1274                 Message resend) {
   1275             mClient.onFormResubmission(view, dontResend, resend);
   1276         }
   1277         @Override
   1278         public void onReceivedError(WebView view, int errorCode,
   1279                 String description, String failingUrl) {
   1280             mClient.onReceivedError(view, errorCode, description, failingUrl);
   1281         }
   1282         @Override
   1283         public boolean shouldOverrideKeyEvent(WebView view,
   1284                 android.view.KeyEvent event) {
   1285             return mClient.shouldOverrideKeyEvent(view, event);
   1286         }
   1287         @Override
   1288         public void onUnhandledKeyEvent(WebView view,
   1289                 android.view.KeyEvent event) {
   1290             mClient.onUnhandledKeyEvent(view, event);
   1291         }
   1292     }
   1293 
   1294     // -------------------------------------------------------------------------
   1295     // WebChromeClient implementation for the sub window
   1296     // -------------------------------------------------------------------------
   1297 
   1298     private class SubWindowChromeClient extends WebChromeClient {
   1299         // The main WebChromeClient.
   1300         private final WebChromeClient mClient;
   1301 
   1302         SubWindowChromeClient(WebChromeClient client) {
   1303             mClient = client;
   1304         }
   1305         @Override
   1306         public void onProgressChanged(WebView view, int newProgress) {
   1307             mClient.onProgressChanged(view, newProgress);
   1308         }
   1309         @Override
   1310         public boolean onCreateWindow(WebView view, boolean dialog,
   1311                 boolean userGesture, android.os.Message resultMsg) {
   1312             return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
   1313         }
   1314         @Override
   1315         public void onCloseWindow(WebView window) {
   1316             if (window != mSubView) {
   1317                 Log.e(LOGTAG, "Can't close the window");
   1318             }
   1319             mActivity.dismissSubWindow(Tab.this);
   1320         }
   1321     }
   1322 
   1323     // -------------------------------------------------------------------------
   1324 
   1325     // Construct a new tab
   1326     Tab(BrowserActivity activity, WebView w, boolean closeOnExit, String appId,
   1327             String url) {
   1328         mActivity = activity;
   1329         mCloseOnExit = closeOnExit;
   1330         mAppId = appId;
   1331         mOriginalUrl = url;
   1332         mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
   1333         mPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
   1334         mInLoad = false;
   1335         mInForeground = false;
   1336 
   1337         mInflateService = LayoutInflater.from(activity);
   1338 
   1339         // The tab consists of a container view, which contains the main
   1340         // WebView, as well as any other UI elements associated with the tab.
   1341         mContainer = (LinearLayout) mInflateService.inflate(R.layout.tab, null);
   1342 
   1343         mDownloadListener = new DownloadListener() {
   1344             public void onDownloadStart(String url, String userAgent,
   1345                     String contentDisposition, String mimetype,
   1346                     long contentLength) {
   1347                 mActivity.onDownloadStart(url, userAgent, contentDisposition,
   1348                         mimetype, contentLength);
   1349                 if (mMainView.copyBackForwardList().getSize() == 0) {
   1350                     // This Tab was opened for the sole purpose of downloading a
   1351                     // file. Remove it.
   1352                     if (mActivity.getTabControl().getCurrentWebView()
   1353                             == mMainView) {
   1354                         // In this case, the Tab is still on top.
   1355                         mActivity.goBackOnePageOrQuit();
   1356                     } else {
   1357                         // In this case, it is not.
   1358                         mActivity.closeTab(Tab.this);
   1359                     }
   1360                 }
   1361             }
   1362         };
   1363         mWebBackForwardListClient = new WebBackForwardListClient() {
   1364             @Override
   1365             public void onNewHistoryItem(WebHistoryItem item) {
   1366                 if (isInVoiceSearchMode()) {
   1367                     item.setCustomData(mVoiceSearchData.mVoiceSearchIntent);
   1368                 }
   1369             }
   1370             @Override
   1371             public void onIndexChanged(WebHistoryItem item, int index) {
   1372                 Object data = item.getCustomData();
   1373                 if (data != null && data instanceof Intent) {
   1374                     activateVoiceSearchMode((Intent) data);
   1375                 }
   1376             }
   1377         };
   1378 
   1379         setWebView(w);
   1380     }
   1381 
   1382     /**
   1383      * Sets the WebView for this tab, correctly removing the old WebView from
   1384      * the container view.
   1385      */
   1386     void setWebView(WebView w) {
   1387         if (mMainView == w) {
   1388             return;
   1389         }
   1390         // If the WebView is changing, the page will be reloaded, so any ongoing
   1391         // Geolocation permission requests are void.
   1392         if (mGeolocationPermissionsPrompt != null) {
   1393             mGeolocationPermissionsPrompt.hide();
   1394         }
   1395 
   1396         // Just remove the old one.
   1397         FrameLayout wrapper =
   1398                 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
   1399         wrapper.removeView(mMainView);
   1400 
   1401         // set the new one
   1402         mMainView = w;
   1403         // attach the WebViewClient, WebChromeClient and DownloadListener
   1404         if (mMainView != null) {
   1405             mMainView.setWebViewClient(mWebViewClient);
   1406             mMainView.setWebChromeClient(mWebChromeClient);
   1407             // Attach DownloadManager so that downloads can start in an active
   1408             // or a non-active window. This can happen when going to a site that
   1409             // does a redirect after a period of time. The user could have
   1410             // switched to another tab while waiting for the download to start.
   1411             mMainView.setDownloadListener(mDownloadListener);
   1412             mMainView.setWebBackForwardListClient(mWebBackForwardListClient);
   1413         }
   1414     }
   1415 
   1416     /**
   1417      * Destroy the tab's main WebView and subWindow if any
   1418      */
   1419     void destroy() {
   1420         if (mMainView != null) {
   1421             dismissSubWindow();
   1422             BrowserSettings.getInstance().deleteObserver(mMainView.getSettings());
   1423             // save the WebView to call destroy() after detach it from the tab
   1424             WebView webView = mMainView;
   1425             setWebView(null);
   1426             webView.destroy();
   1427         }
   1428     }
   1429 
   1430     /**
   1431      * Remove the tab from the parent
   1432      */
   1433     void removeFromTree() {
   1434         // detach the children
   1435         if (mChildTabs != null) {
   1436             for(Tab t : mChildTabs) {
   1437                 t.setParentTab(null);
   1438             }
   1439         }
   1440         // remove itself from the parent list
   1441         if (mParentTab != null) {
   1442             mParentTab.mChildTabs.remove(this);
   1443         }
   1444     }
   1445 
   1446     /**
   1447      * Create a new subwindow unless a subwindow already exists.
   1448      * @return True if a new subwindow was created. False if one already exists.
   1449      */
   1450     boolean createSubWindow() {
   1451         if (mSubView == null) {
   1452             mActivity.closeDialogs();
   1453             mSubViewContainer = mInflateService.inflate(
   1454                     R.layout.browser_subwindow, null);
   1455             mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
   1456             mSubView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
   1457             // use trackball directly
   1458             mSubView.setMapTrackballToArrowKeys(false);
   1459             // Enable the built-in zoom
   1460             mSubView.getSettings().setBuiltInZoomControls(true);
   1461             mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
   1462                     mActivity));
   1463             mSubView.setWebChromeClient(new SubWindowChromeClient(
   1464                     mWebChromeClient));
   1465             // Set a different DownloadListener for the mSubView, since it will
   1466             // just need to dismiss the mSubView, rather than close the Tab
   1467             mSubView.setDownloadListener(new DownloadListener() {
   1468                 public void onDownloadStart(String url, String userAgent,
   1469                         String contentDisposition, String mimetype,
   1470                         long contentLength) {
   1471                     mActivity.onDownloadStart(url, userAgent,
   1472                             contentDisposition, mimetype, contentLength);
   1473                     if (mSubView.copyBackForwardList().getSize() == 0) {
   1474                         // This subwindow was opened for the sole purpose of
   1475                         // downloading a file. Remove it.
   1476                         mActivity.dismissSubWindow(Tab.this);
   1477                     }
   1478                 }
   1479             });
   1480             mSubView.setOnCreateContextMenuListener(mActivity);
   1481             final BrowserSettings s = BrowserSettings.getInstance();
   1482             s.addObserver(mSubView.getSettings()).update(s, null);
   1483             final ImageButton cancel = (ImageButton) mSubViewContainer
   1484                     .findViewById(R.id.subwindow_close);
   1485             cancel.setOnClickListener(new OnClickListener() {
   1486                 public void onClick(View v) {
   1487                     mSubView.getWebChromeClient().onCloseWindow(mSubView);
   1488                 }
   1489             });
   1490             return true;
   1491         }
   1492         return false;
   1493     }
   1494 
   1495     /**
   1496      * Dismiss the subWindow for the tab.
   1497      */
   1498     void dismissSubWindow() {
   1499         if (mSubView != null) {
   1500             mActivity.closeDialogs();
   1501             BrowserSettings.getInstance().deleteObserver(
   1502                     mSubView.getSettings());
   1503             mSubView.destroy();
   1504             mSubView = null;
   1505             mSubViewContainer = null;
   1506         }
   1507     }
   1508 
   1509     /**
   1510      * Attach the sub window to the content view.
   1511      */
   1512     void attachSubWindow(ViewGroup content) {
   1513         if (mSubView != null) {
   1514             content.addView(mSubViewContainer,
   1515                     BrowserActivity.COVER_SCREEN_PARAMS);
   1516         }
   1517     }
   1518 
   1519     /**
   1520      * Remove the sub window from the content view.
   1521      */
   1522     void removeSubWindow(ViewGroup content) {
   1523         if (mSubView != null) {
   1524             content.removeView(mSubViewContainer);
   1525             mActivity.closeDialogs();
   1526         }
   1527     }
   1528 
   1529     /**
   1530      * This method attaches both the WebView and any sub window to the
   1531      * given content view.
   1532      */
   1533     void attachTabToContentView(ViewGroup content) {
   1534         if (mMainView == null) {
   1535             return;
   1536         }
   1537 
   1538         // Attach the WebView to the container and then attach the
   1539         // container to the content view.
   1540         FrameLayout wrapper =
   1541                 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
   1542         ViewGroup parent = (ViewGroup) mMainView.getParent();
   1543         if (parent != wrapper) {
   1544             if (parent != null) {
   1545                 Log.w(LOGTAG, "mMainView already has a parent in"
   1546                         + " attachTabToContentView!");
   1547                 parent.removeView(mMainView);
   1548             }
   1549             wrapper.addView(mMainView);
   1550         } else {
   1551             Log.w(LOGTAG, "mMainView is already attached to wrapper in"
   1552                     + " attachTabToContentView!");
   1553         }
   1554         parent = (ViewGroup) mContainer.getParent();
   1555         if (parent != content) {
   1556             if (parent != null) {
   1557                 Log.w(LOGTAG, "mContainer already has a parent in"
   1558                         + " attachTabToContentView!");
   1559                 parent.removeView(mContainer);
   1560             }
   1561             content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
   1562         } else {
   1563             Log.w(LOGTAG, "mContainer is already attached to content in"
   1564                     + " attachTabToContentView!");
   1565         }
   1566         attachSubWindow(content);
   1567     }
   1568 
   1569     /**
   1570      * Remove the WebView and any sub window from the given content view.
   1571      */
   1572     void removeTabFromContentView(ViewGroup content) {
   1573         if (mMainView == null) {
   1574             return;
   1575         }
   1576 
   1577         // Remove the container from the content and then remove the
   1578         // WebView from the container. This will trigger a focus change
   1579         // needed by WebView.
   1580         FrameLayout wrapper =
   1581                 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
   1582         wrapper.removeView(mMainView);
   1583         content.removeView(mContainer);
   1584         mActivity.closeDialogs();
   1585         removeSubWindow(content);
   1586     }
   1587 
   1588     /**
   1589      * Set the parent tab of this tab.
   1590      */
   1591     void setParentTab(Tab parent) {
   1592         mParentTab = parent;
   1593         // This tab may have been freed due to low memory. If that is the case,
   1594         // the parent tab index is already saved. If we are changing that index
   1595         // (most likely due to removing the parent tab) we must update the
   1596         // parent tab index in the saved Bundle.
   1597         if (mSavedState != null) {
   1598             if (parent == null) {
   1599                 mSavedState.remove(PARENTTAB);
   1600             } else {
   1601                 mSavedState.putInt(PARENTTAB, mActivity.getTabControl()
   1602                         .getTabIndex(parent));
   1603             }
   1604         }
   1605     }
   1606 
   1607     /**
   1608      * When a Tab is created through the content of another Tab, then we
   1609      * associate the Tabs.
   1610      * @param child the Tab that was created from this Tab
   1611      */
   1612     void addChildTab(Tab child) {
   1613         if (mChildTabs == null) {
   1614             mChildTabs = new Vector<Tab>();
   1615         }
   1616         mChildTabs.add(child);
   1617         child.setParentTab(this);
   1618     }
   1619 
   1620     Vector<Tab> getChildTabs() {
   1621         return mChildTabs;
   1622     }
   1623 
   1624     void resume() {
   1625         if (mMainView != null) {
   1626             mMainView.onResume();
   1627             if (mSubView != null) {
   1628                 mSubView.onResume();
   1629             }
   1630         }
   1631     }
   1632 
   1633     void pause() {
   1634         if (mMainView != null) {
   1635             mMainView.onPause();
   1636             if (mSubView != null) {
   1637                 mSubView.onPause();
   1638             }
   1639         }
   1640     }
   1641 
   1642     void putInForeground() {
   1643         mInForeground = true;
   1644         resume();
   1645         mMainView.setOnCreateContextMenuListener(mActivity);
   1646         if (mSubView != null) {
   1647             mSubView.setOnCreateContextMenuListener(mActivity);
   1648         }
   1649         // Show the pending error dialog if the queue is not empty
   1650         if (mQueuedErrors != null && mQueuedErrors.size() >  0) {
   1651             showError(mQueuedErrors.getFirst());
   1652         }
   1653     }
   1654 
   1655     void putInBackground() {
   1656         mInForeground = false;
   1657         pause();
   1658         mMainView.setOnCreateContextMenuListener(null);
   1659         if (mSubView != null) {
   1660             mSubView.setOnCreateContextMenuListener(null);
   1661         }
   1662     }
   1663 
   1664     /**
   1665      * Return the top window of this tab; either the subwindow if it is not
   1666      * null or the main window.
   1667      * @return The top window of this tab.
   1668      */
   1669     WebView getTopWindow() {
   1670         if (mSubView != null) {
   1671             return mSubView;
   1672         }
   1673         return mMainView;
   1674     }
   1675 
   1676     /**
   1677      * Return the main window of this tab. Note: if a tab is freed in the
   1678      * background, this can return null. It is only guaranteed to be
   1679      * non-null for the current tab.
   1680      * @return The main WebView of this tab.
   1681      */
   1682     WebView getWebView() {
   1683         return mMainView;
   1684     }
   1685 
   1686     /**
   1687      * Return the subwindow of this tab or null if there is no subwindow.
   1688      * @return The subwindow of this tab or null.
   1689      */
   1690     WebView getSubWebView() {
   1691         return mSubView;
   1692     }
   1693 
   1694     /**
   1695      * @return The geolocation permissions prompt for this tab.
   1696      */
   1697     GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
   1698         if (mGeolocationPermissionsPrompt == null) {
   1699             ViewStub stub = (ViewStub) mContainer
   1700                     .findViewById(R.id.geolocation_permissions_prompt);
   1701             mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
   1702                     .inflate();
   1703             mGeolocationPermissionsPrompt.init();
   1704         }
   1705         return mGeolocationPermissionsPrompt;
   1706     }
   1707 
   1708     /**
   1709      * @return The application id string
   1710      */
   1711     String getAppId() {
   1712         return mAppId;
   1713     }
   1714 
   1715     /**
   1716      * Set the application id string
   1717      * @param id
   1718      */
   1719     void setAppId(String id) {
   1720         mAppId = id;
   1721     }
   1722 
   1723     /**
   1724      * @return The original url associated with this Tab
   1725      */
   1726     String getOriginalUrl() {
   1727         return mOriginalUrl;
   1728     }
   1729 
   1730     /**
   1731      * Set the original url associated with this tab
   1732      */
   1733     void setOriginalUrl(String url) {
   1734         mOriginalUrl = url;
   1735     }
   1736 
   1737     /**
   1738      * Get the url of this tab. Valid after calling populatePickerData, but
   1739      * before calling wipePickerData, or if the webview has been destroyed.
   1740      * @return The WebView's url or null.
   1741      */
   1742     String getUrl() {
   1743         if (mPickerData != null) {
   1744             return mPickerData.mUrl;
   1745         }
   1746         return null;
   1747     }
   1748 
   1749     /**
   1750      * Get the title of this tab. Valid after calling populatePickerData, but
   1751      * before calling wipePickerData, or if the webview has been destroyed. If
   1752      * the url has no title, use the url instead.
   1753      * @return The WebView's title (or url) or null.
   1754      */
   1755     String getTitle() {
   1756         if (mPickerData != null) {
   1757             return mPickerData.mTitle;
   1758         }
   1759         return null;
   1760     }
   1761 
   1762     /**
   1763      * Get the favicon of this tab. Valid after calling populatePickerData, but
   1764      * before calling wipePickerData, or if the webview has been destroyed.
   1765      * @return The WebView's favicon or null.
   1766      */
   1767     Bitmap getFavicon() {
   1768         if (mPickerData != null) {
   1769             return mPickerData.mFavicon;
   1770         }
   1771         return null;
   1772     }
   1773 
   1774     /**
   1775      * Return the tab's error console. Creates the console if createIfNEcessary
   1776      * is true and we haven't already created the console.
   1777      * @param createIfNecessary Flag to indicate if the console should be
   1778      *            created if it has not been already.
   1779      * @return The tab's error console, or null if one has not been created and
   1780      *         createIfNecessary is false.
   1781      */
   1782     ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
   1783         if (createIfNecessary && mErrorConsole == null) {
   1784             mErrorConsole = new ErrorConsoleView(mActivity);
   1785             mErrorConsole.setWebView(mMainView);
   1786         }
   1787         return mErrorConsole;
   1788     }
   1789 
   1790     /**
   1791      * If this Tab was created through another Tab, then this method returns
   1792      * that Tab.
   1793      * @return the Tab parent or null
   1794      */
   1795     public Tab getParentTab() {
   1796         return mParentTab;
   1797     }
   1798 
   1799     /**
   1800      * Return whether this tab should be closed when it is backing out of the
   1801      * first page.
   1802      * @return TRUE if this tab should be closed when exit.
   1803      */
   1804     boolean closeOnExit() {
   1805         return mCloseOnExit;
   1806     }
   1807 
   1808     /**
   1809      * Saves the current lock-icon state before resetting the lock icon. If we
   1810      * have an error, we may need to roll back to the previous state.
   1811      */
   1812     void resetLockIcon(String url) {
   1813         mPrevLockIconType = mLockIconType;
   1814         mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
   1815         if (URLUtil.isHttpsUrl(url)) {
   1816             mLockIconType = BrowserActivity.LOCK_ICON_SECURE;
   1817         }
   1818     }
   1819 
   1820     /**
   1821      * Reverts the lock-icon state to the last saved state, for example, if we
   1822      * had an error, and need to cancel the load.
   1823      */
   1824     void revertLockIcon() {
   1825         mLockIconType = mPrevLockIconType;
   1826     }
   1827 
   1828     /**
   1829      * @return The tab's lock icon type.
   1830      */
   1831     int getLockIconType() {
   1832         return mLockIconType;
   1833     }
   1834 
   1835     /**
   1836      * @return TRUE if onPageStarted is called while onPageFinished is not
   1837      *         called yet.
   1838      */
   1839     boolean inLoad() {
   1840         return mInLoad;
   1841     }
   1842 
   1843     // force mInLoad to be false. This should only be called before closing the
   1844     // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly.
   1845     void clearInLoad() {
   1846         mInLoad = false;
   1847     }
   1848 
   1849     void populatePickerData() {
   1850         if (mMainView == null) {
   1851             populatePickerDataFromSavedState();
   1852             return;
   1853         }
   1854 
   1855         // FIXME: The only place we cared about subwindow was for
   1856         // bookmarking (i.e. not when saving state). Was this deliberate?
   1857         final WebBackForwardList list = mMainView.copyBackForwardList();
   1858         final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
   1859         populatePickerData(item);
   1860     }
   1861 
   1862     // Populate the picker data using the given history item and the current top
   1863     // WebView.
   1864     private void populatePickerData(WebHistoryItem item) {
   1865         mPickerData = new PickerData();
   1866         if (item != null) {
   1867             mPickerData.mUrl = item.getUrl();
   1868             mPickerData.mTitle = item.getTitle();
   1869             mPickerData.mFavicon = item.getFavicon();
   1870             if (mPickerData.mTitle == null) {
   1871                 mPickerData.mTitle = mPickerData.mUrl;
   1872             }
   1873         }
   1874     }
   1875 
   1876     // Create the PickerData and populate it using the saved state of the tab.
   1877     void populatePickerDataFromSavedState() {
   1878         if (mSavedState == null) {
   1879             return;
   1880         }
   1881         mPickerData = new PickerData();
   1882         mPickerData.mUrl = mSavedState.getString(CURRURL);
   1883         mPickerData.mTitle = mSavedState.getString(CURRTITLE);
   1884     }
   1885 
   1886     void clearPickerData() {
   1887         mPickerData = null;
   1888     }
   1889 
   1890     /**
   1891      * Get the saved state bundle.
   1892      * @return
   1893      */
   1894     Bundle getSavedState() {
   1895         return mSavedState;
   1896     }
   1897 
   1898     /**
   1899      * Set the saved state.
   1900      */
   1901     void setSavedState(Bundle state) {
   1902         mSavedState = state;
   1903     }
   1904 
   1905     /**
   1906      * @return TRUE if succeed in saving the state.
   1907      */
   1908     boolean saveState() {
   1909         // If the WebView is null it means we ran low on memory and we already
   1910         // stored the saved state in mSavedState.
   1911         if (mMainView == null) {
   1912             return mSavedState != null;
   1913         }
   1914 
   1915         mSavedState = new Bundle();
   1916         final WebBackForwardList list = mMainView.saveState(mSavedState);
   1917 
   1918         // Store some extra info for displaying the tab in the picker.
   1919         final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
   1920         populatePickerData(item);
   1921 
   1922         if (mPickerData.mUrl != null) {
   1923             mSavedState.putString(CURRURL, mPickerData.mUrl);
   1924         }
   1925         if (mPickerData.mTitle != null) {
   1926             mSavedState.putString(CURRTITLE, mPickerData.mTitle);
   1927         }
   1928         mSavedState.putBoolean(CLOSEONEXIT, mCloseOnExit);
   1929         if (mAppId != null) {
   1930             mSavedState.putString(APPID, mAppId);
   1931         }
   1932         if (mOriginalUrl != null) {
   1933             mSavedState.putString(ORIGINALURL, mOriginalUrl);
   1934         }
   1935         // Remember the parent tab so the relationship can be restored.
   1936         if (mParentTab != null) {
   1937             mSavedState.putInt(PARENTTAB, mActivity.getTabControl().getTabIndex(
   1938                     mParentTab));
   1939         }
   1940         return true;
   1941     }
   1942 
   1943     /*
   1944      * Restore the state of the tab.
   1945      */
   1946     boolean restoreState(Bundle b) {
   1947         if (b == null) {
   1948             return false;
   1949         }
   1950         // Restore the internal state even if the WebView fails to restore.
   1951         // This will maintain the app id, original url and close-on-exit values.
   1952         mSavedState = null;
   1953         mPickerData = null;
   1954         mCloseOnExit = b.getBoolean(CLOSEONEXIT);
   1955         mAppId = b.getString(APPID);
   1956         mOriginalUrl = b.getString(ORIGINALURL);
   1957 
   1958         final WebBackForwardList list = mMainView.restoreState(b);
   1959         if (list == null) {
   1960             return false;
   1961         }
   1962         return true;
   1963     }
   1964 
   1965     /*
   1966      * Opens the find and select text dialogs.  Called by BrowserActivity.
   1967      */
   1968     WebView showDialog(WebDialog dialog) {
   1969         LinearLayout container;
   1970         WebView view;
   1971         if (mSubView != null) {
   1972             view = mSubView;
   1973             container = (LinearLayout) mSubViewContainer.findViewById(
   1974                     R.id.inner_container);
   1975         } else {
   1976             view = mMainView;
   1977             container = mContainer;
   1978         }
   1979         dialog.show();
   1980         container.addView(dialog, 0, new LinearLayout.LayoutParams(
   1981                 ViewGroup.LayoutParams.MATCH_PARENT,
   1982                 ViewGroup.LayoutParams.WRAP_CONTENT));
   1983         dialog.setWebView(view);
   1984         return view;
   1985     }
   1986 
   1987     /*
   1988      * Close the find or select dialog. Called by BrowserActivity.closeDialog.
   1989      */
   1990     void closeDialog(WebDialog dialog) {
   1991         // The dialog may be attached to the subwindow.  Ensure that the
   1992         // correct parent has it removed.
   1993         LinearLayout parent = (LinearLayout) dialog.getParent();
   1994         if (parent != null) parent.removeView(dialog);
   1995     }
   1996 }
   1997