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