OSDN Git Service

22c8286770a69da2f8af73484b14b6d8337628b0
[android-x86/packages-apps-Browser.git] / src / com / android / browser / Tab.java
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.LinkedList;
21 import java.util.Vector;
22
23 import android.app.AlertDialog;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.DialogInterface;
27 import android.content.DialogInterface.OnCancelListener;
28 import android.database.Cursor;
29 import android.database.sqlite.SQLiteDatabase;
30 import android.database.sqlite.SQLiteException;
31 import android.graphics.Bitmap;
32 import android.net.Uri;
33 import android.net.http.SslError;
34 import android.os.AsyncTask;
35 import android.os.Bundle;
36 import android.os.Message;
37 import android.provider.Browser;
38 import android.util.Log;
39 import android.view.KeyEvent;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.View.OnClickListener;
44 import android.webkit.CookieSyncManager;
45 import android.webkit.GeolocationPermissions;
46 import android.webkit.HttpAuthHandler;
47 import android.webkit.SslErrorHandler;
48 import android.webkit.URLUtil;
49 import android.webkit.ValueCallback;
50 import android.webkit.WebBackForwardList;
51 import android.webkit.WebChromeClient;
52 import android.webkit.WebHistoryItem;
53 import android.webkit.WebIconDatabase;
54 import android.webkit.WebStorage;
55 import android.webkit.WebView;
56 import android.webkit.WebViewClient;
57 import android.widget.FrameLayout;
58 import android.widget.ImageButton;
59 import android.widget.LinearLayout;
60 import android.widget.TextView;
61
62 /**
63  * Class for maintaining Tabs with a main WebView and a subwindow.
64  */
65 class Tab {
66     // Log Tag
67     private static final String LOGTAG = "Tab";
68     // The Geolocation permissions prompt
69     private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
70     // Main WebView wrapper
71     private View mContainer;
72     // Main WebView
73     private WebView mMainView;
74     // Subwindow container
75     private View mSubViewContainer;
76     // Subwindow WebView
77     private WebView mSubView;
78     // Saved bundle for when we are running low on memory. It contains the
79     // information needed to restore the WebView if the user goes back to the
80     // tab.
81     private Bundle mSavedState;
82     // Data used when displaying the tab in the picker.
83     private PickerData mPickerData;
84     // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
85     // created by the UI
86     private Tab mParentTab;
87     // Tab that constructed by this Tab. This is used when this Tab is
88     // destroyed, it clears all mParentTab values in the children.
89     private Vector<Tab> mChildTabs;
90     // If true, the tab will be removed when back out of the first page.
91     private boolean mCloseOnExit;
92     // If true, the tab is in the foreground of the current activity.
93     private boolean mInForeground;
94     // If true, the tab is in loading state.
95     private boolean mInLoad;
96     // Application identifier used to find tabs that another application wants
97     // to reuse.
98     private String mAppId;
99     // Keep the original url around to avoid killing the old WebView if the url
100     // has not changed.
101     private String mOriginalUrl;
102     // Error console for the tab
103     private ErrorConsoleView mErrorConsole;
104     // the lock icon type and previous lock icon type for the tab
105     private int mLockIconType;
106     private int mPrevLockIconType;
107     // Inflation service for making subwindows.
108     private final LayoutInflater mInflateService;
109     // The BrowserActivity which owners the Tab
110     private final BrowserActivity mActivity;
111
112     // AsyncTask for downloading touch icons
113     DownloadTouchIcon mTouchIconLoader;
114
115     // Extra saved information for displaying the tab in the picker.
116     private static class PickerData {
117         String  mUrl;
118         String  mTitle;
119         Bitmap  mFavicon;
120     }
121
122     // Used for saving and restoring each Tab
123     static final String WEBVIEW = "webview";
124     static final String NUMTABS = "numTabs";
125     static final String CURRTAB = "currentTab";
126     static final String CURRURL = "currentUrl";
127     static final String CURRTITLE = "currentTitle";
128     static final String CURRPICTURE = "currentPicture";
129     static final String CLOSEONEXIT = "closeonexit";
130     static final String PARENTTAB = "parentTab";
131     static final String APPID = "appid";
132     static final String ORIGINALURL = "originalUrl";
133
134     // -------------------------------------------------------------------------
135
136     // Container class for the next error dialog that needs to be displayed
137     private class ErrorDialog {
138         public final int mTitle;
139         public final String mDescription;
140         public final int mError;
141         ErrorDialog(int title, String desc, int error) {
142             mTitle = title;
143             mDescription = desc;
144             mError = error;
145         }
146     };
147
148     private void processNextError() {
149         if (mQueuedErrors == null) {
150             return;
151         }
152         // The first one is currently displayed so just remove it.
153         mQueuedErrors.removeFirst();
154         if (mQueuedErrors.size() == 0) {
155             mQueuedErrors = null;
156             return;
157         }
158         showError(mQueuedErrors.getFirst());
159     }
160
161     private DialogInterface.OnDismissListener mDialogListener =
162             new DialogInterface.OnDismissListener() {
163                 public void onDismiss(DialogInterface d) {
164                     processNextError();
165                 }
166             };
167     private LinkedList<ErrorDialog> mQueuedErrors;
168
169     private void queueError(int err, String desc) {
170         if (mQueuedErrors == null) {
171             mQueuedErrors = new LinkedList<ErrorDialog>();
172         }
173         for (ErrorDialog d : mQueuedErrors) {
174             if (d.mError == err) {
175                 // Already saw a similar error, ignore the new one.
176                 return;
177             }
178         }
179         ErrorDialog errDialog = new ErrorDialog(
180                 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
181                 R.string.browserFrameFileErrorLabel :
182                 R.string.browserFrameNetworkErrorLabel,
183                 desc, err);
184         mQueuedErrors.addLast(errDialog);
185
186         // Show the dialog now if the queue was empty and it is in foreground
187         if (mQueuedErrors.size() == 1 && mInForeground) {
188             showError(errDialog);
189         }
190     }
191
192     private void showError(ErrorDialog errDialog) {
193         if (mInForeground) {
194             AlertDialog d = new AlertDialog.Builder(mActivity)
195                     .setTitle(errDialog.mTitle)
196                     .setMessage(errDialog.mDescription)
197                     .setPositiveButton(R.string.ok, null)
198                     .create();
199             d.setOnDismissListener(mDialogListener);
200             d.show();
201         }
202     }
203
204     // -------------------------------------------------------------------------
205     // WebViewClient implementation for the main WebView
206     // -------------------------------------------------------------------------
207
208     private final WebViewClient mWebViewClient = new WebViewClient() {
209         @Override
210         public void onPageStarted(WebView view, String url, Bitmap favicon) {
211             mInLoad = true;
212
213             // We've started to load a new page. If there was a pending message
214             // to save a screenshot then we will now take the new page and save
215             // an incorrect screenshot. Therefore, remove any pending thumbnail
216             // messages from the queue.
217             mActivity.removeMessages(BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL,
218                     view);
219
220             // If we start a touch icon load and then load a new page, we don't
221             // want to cancel the current touch icon loader. But, we do want to
222             // create a new one when the touch icon url is known.
223             if (mTouchIconLoader != null) {
224                 mTouchIconLoader.mTab = null;
225                 mTouchIconLoader = null;
226             }
227
228             // reset the error console
229             if (mErrorConsole != null) {
230                 mErrorConsole.clearErrorMessages();
231                 if (mActivity.shouldShowErrorConsole()) {
232                     mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
233                 }
234             }
235
236             // update the bookmark database for favicon
237             if (favicon != null) {
238                 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
239                         .getContentResolver(), view.getOriginalUrl(), view
240                         .getUrl(), favicon);
241             }
242
243             // reset sync timer to avoid sync starts during loading a page
244             CookieSyncManager.getInstance().resetSync();
245
246             if (!mActivity.isNetworkUp()) {
247                 view.setNetworkAvailable(false);
248             }
249
250             // finally update the UI in the activity if it is in the foreground
251             if (mInForeground) {
252                 mActivity.onPageStarted(view, url, favicon);
253             }
254         }
255
256         @Override
257         public void onPageFinished(WebView view, String url) {
258             mInLoad = false;
259
260             if (mInForeground && !mActivity.didUserStopLoading()
261                     || !mInForeground) {
262                 // Only update the bookmark screenshot if the user did not
263                 // cancel the load early.
264                 mActivity.postMessage(
265                         BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 0, 0, view,
266                         500);
267             }
268
269             // finally update the UI in the activity if it is in the foreground
270             if (mInForeground) {
271                 mActivity.onPageFinished(view, url);
272             }
273         }
274
275         // return true if want to hijack the url to let another app to handle it
276         @Override
277         public boolean shouldOverrideUrlLoading(WebView view, String url) {
278             if (mInForeground) {
279                 return mActivity.shouldOverrideUrlLoading(view, url);
280             } else {
281                 return false;
282             }
283         }
284
285         /**
286          * Updates the lock icon. This method is called when we discover another
287          * resource to be loaded for this page (for example, javascript). While
288          * we update the icon type, we do not update the lock icon itself until
289          * we are done loading, it is slightly more secure this way.
290          */
291         @Override
292         public void onLoadResource(WebView view, String url) {
293             if (url != null && url.length() > 0) {
294                 // It is only if the page claims to be secure that we may have
295                 // to update the lock:
296                 if (mLockIconType == BrowserActivity.LOCK_ICON_SECURE) {
297                     // If NOT a 'safe' url, change the lock to mixed content!
298                     if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
299                             || URLUtil.isAboutUrl(url))) {
300                         mLockIconType = BrowserActivity.LOCK_ICON_MIXED;
301                     }
302                 }
303             }
304         }
305
306         /**
307          * Show a dialog informing the user of the network error reported by
308          * WebCore if it is in the foreground.
309          */
310         @Override
311         public void onReceivedError(WebView view, int errorCode,
312                 String description, String failingUrl) {
313             if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
314                     errorCode != WebViewClient.ERROR_CONNECT &&
315                     errorCode != WebViewClient.ERROR_BAD_URL &&
316                     errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
317                     errorCode != WebViewClient.ERROR_FILE) {
318                 queueError(errorCode, description);
319             }
320             Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
321                     + " " + description);
322
323             // We need to reset the title after an error if it is in foreground.
324             if (mInForeground) {
325                 mActivity.resetTitleAndRevertLockIcon();
326             }
327         }
328
329         /**
330          * Check with the user if it is ok to resend POST data as the page they
331          * are trying to navigate to is the result of a POST.
332          */
333         @Override
334         public void onFormResubmission(WebView view, final Message dontResend,
335                                        final Message resend) {
336             if (!mInForeground) {
337                 dontResend.sendToTarget();
338                 return;
339             }
340             new AlertDialog.Builder(mActivity).setTitle(
341                     R.string.browserFrameFormResubmitLabel).setMessage(
342                     R.string.browserFrameFormResubmitMessage)
343                     .setPositiveButton(R.string.ok,
344                             new DialogInterface.OnClickListener() {
345                                 public void onClick(DialogInterface dialog,
346                                         int which) {
347                                     resend.sendToTarget();
348                                 }
349                             }).setNegativeButton(R.string.cancel,
350                             new DialogInterface.OnClickListener() {
351                                 public void onClick(DialogInterface dialog,
352                                         int which) {
353                                     dontResend.sendToTarget();
354                                 }
355                             }).setOnCancelListener(new OnCancelListener() {
356                         public void onCancel(DialogInterface dialog) {
357                             dontResend.sendToTarget();
358                         }
359                     }).show();
360         }
361
362         /**
363          * Insert the url into the visited history database.
364          * @param url The url to be inserted.
365          * @param isReload True if this url is being reloaded.
366          * FIXME: Not sure what to do when reloading the page.
367          */
368         @Override
369         public void doUpdateVisitedHistory(WebView view, String url,
370                 boolean isReload) {
371             if (url.regionMatches(true, 0, "about:", 0, 6)) {
372                 return;
373             }
374             // remove "client" before updating it to the history so that it wont
375             // show up in the auto-complete list.
376             int index = url.indexOf("client=ms-");
377             if (index > 0 && url.contains(".google.")) {
378                 int end = url.indexOf('&', index);
379                 if (end > 0) {
380                     url = url.substring(0, index)
381                             .concat(url.substring(end + 1));
382                 } else {
383                     // the url.charAt(index-1) should be either '?' or '&'
384                     url = url.substring(0, index-1);
385                 }
386             }
387             Browser.updateVisitedHistory(mActivity.getContentResolver(), url,
388                     true);
389             WebIconDatabase.getInstance().retainIconForPageUrl(url);
390         }
391
392         /**
393          * Displays SSL error(s) dialog to the user.
394          */
395         @Override
396         public void onReceivedSslError(final WebView view,
397                 final SslErrorHandler handler, final SslError error) {
398             if (!mInForeground) {
399                 handler.cancel();
400                 return;
401             }
402             if (BrowserSettings.getInstance().showSecurityWarnings()) {
403                 final LayoutInflater factory =
404                     LayoutInflater.from(mActivity);
405                 final View warningsView =
406                     factory.inflate(R.layout.ssl_warnings, null);
407                 final LinearLayout placeholder =
408                     (LinearLayout)warningsView.findViewById(R.id.placeholder);
409
410                 if (error.hasError(SslError.SSL_UNTRUSTED)) {
411                     LinearLayout ll = (LinearLayout)factory
412                         .inflate(R.layout.ssl_warning, null);
413                     ((TextView)ll.findViewById(R.id.warning))
414                         .setText(R.string.ssl_untrusted);
415                     placeholder.addView(ll);
416                 }
417
418                 if (error.hasError(SslError.SSL_IDMISMATCH)) {
419                     LinearLayout ll = (LinearLayout)factory
420                         .inflate(R.layout.ssl_warning, null);
421                     ((TextView)ll.findViewById(R.id.warning))
422                         .setText(R.string.ssl_mismatch);
423                     placeholder.addView(ll);
424                 }
425
426                 if (error.hasError(SslError.SSL_EXPIRED)) {
427                     LinearLayout ll = (LinearLayout)factory
428                         .inflate(R.layout.ssl_warning, null);
429                     ((TextView)ll.findViewById(R.id.warning))
430                         .setText(R.string.ssl_expired);
431                     placeholder.addView(ll);
432                 }
433
434                 if (error.hasError(SslError.SSL_NOTYETVALID)) {
435                     LinearLayout ll = (LinearLayout)factory
436                         .inflate(R.layout.ssl_warning, null);
437                     ((TextView)ll.findViewById(R.id.warning))
438                         .setText(R.string.ssl_not_yet_valid);
439                     placeholder.addView(ll);
440                 }
441
442                 new AlertDialog.Builder(mActivity).setTitle(
443                         R.string.security_warning).setIcon(
444                         android.R.drawable.ic_dialog_alert).setView(
445                         warningsView).setPositiveButton(R.string.ssl_continue,
446                         new DialogInterface.OnClickListener() {
447                             public void onClick(DialogInterface dialog,
448                                     int whichButton) {
449                                 handler.proceed();
450                             }
451                         }).setNeutralButton(R.string.view_certificate,
452                         new DialogInterface.OnClickListener() {
453                             public void onClick(DialogInterface dialog,
454                                     int whichButton) {
455                                 mActivity.showSSLCertificateOnError(view,
456                                         handler, error);
457                             }
458                         }).setNegativeButton(R.string.cancel,
459                         new DialogInterface.OnClickListener() {
460                             public void onClick(DialogInterface dialog,
461                                     int whichButton) {
462                                 handler.cancel();
463                                 mActivity.resetTitleAndRevertLockIcon();
464                             }
465                         }).setOnCancelListener(
466                         new DialogInterface.OnCancelListener() {
467                             public void onCancel(DialogInterface dialog) {
468                                 handler.cancel();
469                                 mActivity.resetTitleAndRevertLockIcon();
470                             }
471                         }).show();
472             } else {
473                 handler.proceed();
474             }
475         }
476
477         /**
478          * Handles an HTTP authentication request.
479          *
480          * @param handler The authentication handler
481          * @param host The host
482          * @param realm The realm
483          */
484         @Override
485         public void onReceivedHttpAuthRequest(WebView view,
486                 final HttpAuthHandler handler, final String host,
487                 final String realm) {
488             String username = null;
489             String password = null;
490
491             boolean reuseHttpAuthUsernamePassword = handler
492                     .useHttpAuthUsernamePassword();
493
494             if (reuseHttpAuthUsernamePassword && mMainView != null) {
495                 String[] credentials = mMainView.getHttpAuthUsernamePassword(
496                         host, realm);
497                 if (credentials != null && credentials.length == 2) {
498                     username = credentials[0];
499                     password = credentials[1];
500                 }
501             }
502
503             if (username != null && password != null) {
504                 handler.proceed(username, password);
505             } else {
506                 if (mInForeground) {
507                     mActivity.showHttpAuthentication(handler, host, realm,
508                             null, null, null, 0);
509                 } else {
510                     handler.cancel();
511                 }
512             }
513         }
514
515         @Override
516         public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
517             if (!mInForeground) {
518                 return false;
519             }
520             if (mActivity.isMenuDown()) {
521                 // only check shortcut key when MENU is held
522                 return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
523                         event);
524             } else {
525                 return false;
526             }
527         }
528
529         @Override
530         public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
531             if (!mInForeground) {
532                 return;
533             }
534             if (event.isDown()) {
535                 mActivity.onKeyDown(event.getKeyCode(), event);
536             } else {
537                 mActivity.onKeyUp(event.getKeyCode(), event);
538             }
539         }
540     };
541
542     // -------------------------------------------------------------------------
543     // WebChromeClient implementation for the main WebView
544     // -------------------------------------------------------------------------
545
546     private final WebChromeClient mWebChromeClient = new WebChromeClient() {
547         // Helper method to create a new tab or sub window.
548         private void createWindow(final boolean dialog, final Message msg) {
549             WebView.WebViewTransport transport =
550                     (WebView.WebViewTransport) msg.obj;
551             if (dialog) {
552                 createSubWindow();
553                 mActivity.attachSubWindow(Tab.this);
554                 transport.setWebView(mSubView);
555             } else {
556                 final Tab newTab = mActivity.openTabAndShow(
557                         BrowserActivity.EMPTY_URL_DATA, false, null);
558                 if (newTab != Tab.this) {
559                     Tab.this.addChildTab(newTab);
560                 }
561                 transport.setWebView(newTab.getWebView());
562             }
563             msg.sendToTarget();
564         }
565
566         @Override
567         public boolean onCreateWindow(WebView view, final boolean dialog,
568                 final boolean userGesture, final Message resultMsg) {
569             // only allow new window or sub window for the foreground case
570             if (!mInForeground) {
571                 return false;
572             }
573             // Short-circuit if we can't create any more tabs or sub windows.
574             if (dialog && mSubView != null) {
575                 new AlertDialog.Builder(mActivity)
576                         .setTitle(R.string.too_many_subwindows_dialog_title)
577                         .setIcon(android.R.drawable.ic_dialog_alert)
578                         .setMessage(R.string.too_many_subwindows_dialog_message)
579                         .setPositiveButton(R.string.ok, null)
580                         .show();
581                 return false;
582             } else if (!mActivity.getTabControl().canCreateNewTab()) {
583                 new AlertDialog.Builder(mActivity)
584                         .setTitle(R.string.too_many_windows_dialog_title)
585                         .setIcon(android.R.drawable.ic_dialog_alert)
586                         .setMessage(R.string.too_many_windows_dialog_message)
587                         .setPositiveButton(R.string.ok, null)
588                         .show();
589                 return false;
590             }
591
592             // Short-circuit if this was a user gesture.
593             if (userGesture) {
594                 createWindow(dialog, resultMsg);
595                 return true;
596             }
597
598             // Allow the popup and create the appropriate window.
599             final AlertDialog.OnClickListener allowListener =
600                     new AlertDialog.OnClickListener() {
601                         public void onClick(DialogInterface d,
602                                 int which) {
603                             createWindow(dialog, resultMsg);
604                         }
605                     };
606
607             // Block the popup by returning a null WebView.
608             final AlertDialog.OnClickListener blockListener =
609                     new AlertDialog.OnClickListener() {
610                         public void onClick(DialogInterface d, int which) {
611                             resultMsg.sendToTarget();
612                         }
613                     };
614
615             // Build a confirmation dialog to display to the user.
616             final AlertDialog d =
617                     new AlertDialog.Builder(mActivity)
618                     .setTitle(R.string.attention)
619                     .setIcon(android.R.drawable.ic_dialog_alert)
620                     .setMessage(R.string.popup_window_attempt)
621                     .setPositiveButton(R.string.allow, allowListener)
622                     .setNegativeButton(R.string.block, blockListener)
623                     .setCancelable(false)
624                     .create();
625
626             // Show the confirmation dialog.
627             d.show();
628             return true;
629         }
630
631         @Override
632         public void onRequestFocus(WebView view) {
633             if (!mInForeground) {
634                 mActivity.switchToTab(mActivity.getTabControl().getTabIndex(
635                         Tab.this));
636             }
637         }
638
639         @Override
640         public void onCloseWindow(WebView window) {
641             if (mParentTab != null) {
642                 // JavaScript can only close popup window.
643                 if (mInForeground) {
644                     mActivity.switchToTab(mActivity.getTabControl()
645                             .getTabIndex(mParentTab));
646                 }
647                 mActivity.closeTab(Tab.this);
648             }
649         }
650
651         @Override
652         public void onProgressChanged(WebView view, int newProgress) {
653             if (newProgress == 100) {
654                 // sync cookies and cache promptly here.
655                 CookieSyncManager.getInstance().sync();
656             }
657             if (mInForeground) {
658                 mActivity.onProgressChanged(view, newProgress);
659             }
660         }
661
662         @Override
663         public void onReceivedTitle(WebView view, String title) {
664             String url = view.getUrl();
665             if (mInForeground) {
666                 // here, if url is null, we want to reset the title
667                 mActivity.setUrlTitle(url, title);
668             }
669             if (url == null ||
670                 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
671                 return;
672             }
673             // See if we can find the current url in our history database and
674             // add the new title to it.
675             if (url.startsWith("http://www.")) {
676                 url = url.substring(11);
677             } else if (url.startsWith("http://")) {
678                 url = url.substring(4);
679             }
680             try {
681                 final ContentResolver cr = mActivity.getContentResolver();
682                 url = "%" + url;
683                 String [] selArgs = new String[] { url };
684                 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
685                         + Browser.BookmarkColumns.BOOKMARK + " = 0";
686                 Cursor c = cr.query(Browser.BOOKMARKS_URI,
687                         Browser.HISTORY_PROJECTION, where, selArgs, null);
688                 if (c.moveToFirst()) {
689                     // Current implementation of database only has one entry per
690                     // url.
691                     ContentValues map = new ContentValues();
692                     map.put(Browser.BookmarkColumns.TITLE, title);
693                     cr.update(Browser.BOOKMARKS_URI, map, "_id = "
694                             + c.getInt(0), null);
695                 }
696                 c.close();
697             } catch (IllegalStateException e) {
698                 Log.e(LOGTAG, "Tab onReceived title", e);
699             } catch (SQLiteException ex) {
700                 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
701             }
702         }
703
704         @Override
705         public void onReceivedIcon(WebView view, Bitmap icon) {
706             if (icon != null) {
707                 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
708                         .getContentResolver(), view.getOriginalUrl(), view
709                         .getUrl(), icon);
710             }
711             if (mInForeground) {
712                 mActivity.setFavicon(icon);
713             }
714         }
715
716         @Override
717         public void onReceivedTouchIconUrl(WebView view, String url,
718                 boolean precomposed) {
719             final ContentResolver cr = mActivity.getContentResolver();
720             final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
721                             view.getOriginalUrl(), view.getUrl(), true);
722             if (c != null) {
723                 if (c.getCount() > 0) {
724                     // Let precomposed icons take precedence over non-composed
725                     // icons.
726                     if (precomposed && mTouchIconLoader != null) {
727                         mTouchIconLoader.cancel(false);
728                         mTouchIconLoader = null;
729                     }
730                     // Have only one async task at a time.
731                     if (mTouchIconLoader == null) {
732                         mTouchIconLoader = new DownloadTouchIcon(Tab.this, cr,
733                                 c, view);
734                         mTouchIconLoader.execute(url);
735                     }
736                 } else {
737                     c.close();
738                 }
739             }
740         }
741
742         @Override
743         public void onShowCustomView(View view,
744                 WebChromeClient.CustomViewCallback callback) {
745             if (mInForeground) mActivity.onShowCustomView(view, callback);
746         }
747
748         @Override
749         public void onHideCustomView() {
750             if (mInForeground) mActivity.onHideCustomView();
751         }
752
753         /**
754          * The origin has exceeded its database quota.
755          * @param url the URL that exceeded the quota
756          * @param databaseIdentifier the identifier of the database on which the
757          *            transaction that caused the quota overflow was run
758          * @param currentQuota the current quota for the origin.
759          * @param estimatedSize the estimated size of the database.
760          * @param totalUsedQuota is the sum of all origins' quota.
761          * @param quotaUpdater The callback to run when a decision to allow or
762          *            deny quota has been made. Don't forget to call this!
763          */
764         @Override
765         public void onExceededDatabaseQuota(String url,
766             String databaseIdentifier, long currentQuota, long estimatedSize,
767             long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
768             BrowserSettings.getInstance().getWebStorageSizeManager()
769                     .onExceededDatabaseQuota(url, databaseIdentifier,
770                             currentQuota, estimatedSize, totalUsedQuota,
771                             quotaUpdater);
772         }
773
774         /**
775          * The Application Cache has exceeded its max size.
776          * @param spaceNeeded is the amount of disk space that would be needed
777          *            in order for the last appcache operation to succeed.
778          * @param totalUsedQuota is the sum of all origins' quota.
779          * @param quotaUpdater A callback to inform the WebCore thread that a
780          *            new app cache size is available. This callback must always
781          *            be executed at some point to ensure that the sleeping
782          *            WebCore thread is woken up.
783          */
784         @Override
785         public void onReachedMaxAppCacheSize(long spaceNeeded,
786                 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
787             BrowserSettings.getInstance().getWebStorageSizeManager()
788                     .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
789                             quotaUpdater);
790         }
791
792         /**
793          * Instructs the browser to show a prompt to ask the user to set the
794          * Geolocation permission state for the specified origin.
795          * @param origin The origin for which Geolocation permissions are
796          *     requested.
797          * @param callback The callback to call once the user has set the
798          *     Geolocation permission state.
799          */
800         @Override
801         public void onGeolocationPermissionsShowPrompt(String origin,
802                 GeolocationPermissions.Callback callback) {
803             if (mInForeground) {
804                 mGeolocationPermissionsPrompt.show(origin, callback);
805             }
806         }
807
808         /**
809          * Instructs the browser to hide the Geolocation permissions prompt.
810          */
811         @Override
812         public void onGeolocationPermissionsHidePrompt() {
813             if (mInForeground) {
814                 mGeolocationPermissionsPrompt.hide();
815             }
816         }
817
818         /* Adds a JavaScript error message to the system log and if the JS
819          * console is enabled in the about:debug options, to that console
820          * also.
821          * @param message The error message to report.
822          * @param lineNumber The line number of the error.
823          * @param sourceID The name of the source file that caused the error.
824          */
825         @Override
826         public void onConsoleMessage(String message, int lineNumber,
827                 String sourceID) {
828             if (mInForeground) {
829                 // call getErrorConsole(true) so it will create one if needed
830                 ErrorConsoleView errorConsole = getErrorConsole(true);
831                 errorConsole.addErrorMessage(message, sourceID, lineNumber);
832                 if (mActivity.shouldShowErrorConsole()
833                         && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
834                     errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
835                 }
836             }
837             Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":"
838                     + lineNumber);
839         }
840
841         /**
842          * Ask the browser for an icon to represent a <video> element.
843          * This icon will be used if the Web page did not specify a poster attribute.
844          * @return Bitmap The icon or null if no such icon is available.
845          */
846         @Override
847         public Bitmap getDefaultVideoPoster() {
848             if (mInForeground) {
849                 return mActivity.getDefaultVideoPoster();
850             }
851             return null;
852         }
853
854         /**
855          * Ask the host application for a custom progress view to show while
856          * a <video> is loading.
857          * @return View The progress view.
858          */
859         @Override
860         public View getVideoLoadingProgressView() {
861             if (mInForeground) {
862                 return mActivity.getVideoLoadingProgressView();
863             }
864             return null;
865         }
866
867         @Override
868         public void openFileChooser(ValueCallback<Uri> uploadMsg) {
869             if (mInForeground) {
870                 mActivity.openFileChooser(uploadMsg);
871             } else {
872                 uploadMsg.onReceiveValue(null);
873             }
874         }
875
876         /**
877          * Deliver a list of already-visited URLs
878          */
879         @Override
880         public void getVisitedHistory(final ValueCallback<String[]> callback) {
881             AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
882                 public String[] doInBackground(Void... unused) {
883                     return Browser.getVisitedHistory(mActivity
884                             .getContentResolver());
885                 }
886                 public void onPostExecute(String[] result) {
887                     callback.onReceiveValue(result);
888                 };
889             };
890             task.execute();
891         };
892     };
893
894     // -------------------------------------------------------------------------
895     // WebViewClient implementation for the sub window
896     // -------------------------------------------------------------------------
897
898     // Subclass of WebViewClient used in subwindows to notify the main
899     // WebViewClient of certain WebView activities.
900     private static class SubWindowClient extends WebViewClient {
901         // The main WebViewClient.
902         private final WebViewClient mClient;
903
904         SubWindowClient(WebViewClient client) {
905             mClient = client;
906         }
907         @Override
908         public void doUpdateVisitedHistory(WebView view, String url,
909                 boolean isReload) {
910             mClient.doUpdateVisitedHistory(view, url, isReload);
911         }
912         @Override
913         public boolean shouldOverrideUrlLoading(WebView view, String url) {
914             return mClient.shouldOverrideUrlLoading(view, url);
915         }
916         @Override
917         public void onReceivedSslError(WebView view, SslErrorHandler handler,
918                 SslError error) {
919             mClient.onReceivedSslError(view, handler, error);
920         }
921         @Override
922         public void onReceivedHttpAuthRequest(WebView view,
923                 HttpAuthHandler handler, String host, String realm) {
924             mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
925         }
926         @Override
927         public void onFormResubmission(WebView view, Message dontResend,
928                 Message resend) {
929             mClient.onFormResubmission(view, dontResend, resend);
930         }
931         @Override
932         public void onReceivedError(WebView view, int errorCode,
933                 String description, String failingUrl) {
934             mClient.onReceivedError(view, errorCode, description, failingUrl);
935         }
936         @Override
937         public boolean shouldOverrideKeyEvent(WebView view,
938                 android.view.KeyEvent event) {
939             return mClient.shouldOverrideKeyEvent(view, event);
940         }
941         @Override
942         public void onUnhandledKeyEvent(WebView view,
943                 android.view.KeyEvent event) {
944             mClient.onUnhandledKeyEvent(view, event);
945         }
946     }
947
948     // -------------------------------------------------------------------------
949     // WebChromeClient implementation for the sub window
950     // -------------------------------------------------------------------------
951
952     private class SubWindowChromeClient extends WebChromeClient {
953         // The main WebChromeClient.
954         private final WebChromeClient mClient;
955
956         SubWindowChromeClient(WebChromeClient client) {
957             mClient = client;
958         }
959         @Override
960         public void onProgressChanged(WebView view, int newProgress) {
961             mClient.onProgressChanged(view, newProgress);
962         }
963         @Override
964         public boolean onCreateWindow(WebView view, boolean dialog,
965                 boolean userGesture, android.os.Message resultMsg) {
966             return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
967         }
968         @Override
969         public void onCloseWindow(WebView window) {
970             if (window != mSubView) {
971                 Log.e(LOGTAG, "Can't close the window");
972             }
973             mActivity.dismissSubWindow(Tab.this);
974         }
975     }
976
977     // -------------------------------------------------------------------------
978
979     // Construct a new tab
980     Tab(BrowserActivity activity, WebView w, boolean closeOnExit, String appId,
981             String url) {
982         mActivity = activity;
983         mCloseOnExit = closeOnExit;
984         mAppId = appId;
985         mOriginalUrl = url;
986         mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
987         mPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
988         mInLoad = false;
989         mInForeground = false;
990
991         mInflateService = LayoutInflater.from(activity);
992
993         // The tab consists of a container view, which contains the main
994         // WebView, as well as any other UI elements associated with the tab.
995         mContainer = mInflateService.inflate(R.layout.tab, null);
996
997         mGeolocationPermissionsPrompt =
998             (GeolocationPermissionsPrompt) mContainer.findViewById(
999                 R.id.geolocation_permissions_prompt);
1000
1001         setWebView(w);
1002     }
1003
1004     /**
1005      * Sets the WebView for this tab, correctly removing the old WebView from
1006      * the container view.
1007      */
1008     void setWebView(WebView w) {
1009         if (mMainView == w) {
1010             return;
1011         }
1012         // If the WebView is changing, the page will be reloaded, so any ongoing
1013         // Geolocation permission requests are void.
1014         mGeolocationPermissionsPrompt.hide();
1015
1016         // Just remove the old one.
1017         FrameLayout wrapper =
1018                 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
1019         wrapper.removeView(mMainView);
1020
1021         // set the new one
1022         mMainView = w;
1023         // attached the WebViewClient and WebChromeClient
1024         if (mMainView != null) {
1025             mMainView.setWebViewClient(mWebViewClient);
1026             mMainView.setWebChromeClient(mWebChromeClient);
1027         }
1028     }
1029
1030     /**
1031      * Destroy the tab's main WebView and subWindow if any
1032      */
1033     void destroy() {
1034         if (mMainView != null) {
1035             dismissSubWindow();
1036             BrowserSettings.getInstance().deleteObserver(mMainView.getSettings());
1037             // save the WebView to call destroy() after detach it from the tab
1038             WebView webView = mMainView;
1039             setWebView(null);
1040             webView.destroy();
1041         }
1042     }
1043
1044     /**
1045      * Remove the tab from the parent
1046      */
1047     void removeFromTree() {
1048         // detach the children
1049         if (mChildTabs != null) {
1050             for(Tab t : mChildTabs) {
1051                 t.setParentTab(null);
1052             }
1053         }
1054         // remove itself from the parent list
1055         if (mParentTab != null) {
1056             mParentTab.mChildTabs.remove(this);
1057         }
1058     }
1059
1060     /**
1061      * Create a new subwindow unless a subwindow already exists.
1062      * @return True if a new subwindow was created. False if one already exists.
1063      */
1064     boolean createSubWindow() {
1065         if (mSubView == null) {
1066             mSubViewContainer = mInflateService.inflate(
1067                     R.layout.browser_subwindow, null);
1068             mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
1069             // use trackball directly
1070             mSubView.setMapTrackballToArrowKeys(false);
1071             mSubView.setWebViewClient(new SubWindowClient(mWebViewClient));
1072             mSubView.setWebChromeClient(new SubWindowChromeClient(
1073                     mWebChromeClient));
1074             mSubView.setDownloadListener(mActivity);
1075             mSubView.setOnCreateContextMenuListener(mActivity);
1076             final BrowserSettings s = BrowserSettings.getInstance();
1077             s.addObserver(mSubView.getSettings()).update(s, null);
1078             final ImageButton cancel = (ImageButton) mSubViewContainer
1079                     .findViewById(R.id.subwindow_close);
1080             cancel.setOnClickListener(new OnClickListener() {
1081                 public void onClick(View v) {
1082                     mSubView.getWebChromeClient().onCloseWindow(mSubView);
1083                 }
1084             });
1085             return true;
1086         }
1087         return false;
1088     }
1089
1090     /**
1091      * Dismiss the subWindow for the tab.
1092      */
1093     void dismissSubWindow() {
1094         if (mSubView != null) {
1095             BrowserSettings.getInstance().deleteObserver(
1096                     mSubView.getSettings());
1097             mSubView.destroy();
1098             mSubView = null;
1099             mSubViewContainer = null;
1100         }
1101     }
1102
1103     /**
1104      * Attach the sub window to the content view.
1105      */
1106     void attachSubWindow(ViewGroup content) {
1107         if (mSubView != null) {
1108             content.addView(mSubViewContainer,
1109                     BrowserActivity.COVER_SCREEN_PARAMS);
1110         }
1111     }
1112
1113     /**
1114      * Remove the sub window from the content view.
1115      */
1116     void removeSubWindow(ViewGroup content) {
1117         if (mSubView != null) {
1118             content.removeView(mSubViewContainer);
1119         }
1120     }
1121
1122     /**
1123      * This method attaches both the WebView and any sub window to the
1124      * given content view.
1125      */
1126     void attachTabToContentView(ViewGroup content) {
1127         if (mMainView == null) {
1128             return;
1129         }
1130
1131         // Attach the WebView to the container and then attach the
1132         // container to the content view.
1133         FrameLayout wrapper =
1134                 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
1135         wrapper.addView(mMainView);
1136         content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
1137         attachSubWindow(content);
1138     }
1139
1140     /**
1141      * Remove the WebView and any sub window from the given content view.
1142      */
1143     void removeTabFromContentView(ViewGroup content) {
1144         if (mMainView == null) {
1145             return;
1146         }
1147
1148         // Remove the container from the content and then remove the
1149         // WebView from the container. This will trigger a focus change
1150         // needed by WebView.
1151         FrameLayout wrapper =
1152                 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
1153         wrapper.removeView(mMainView);
1154         content.removeView(mContainer);
1155         removeSubWindow(content);
1156     }
1157
1158     /**
1159      * Set the parent tab of this tab.
1160      */
1161     void setParentTab(Tab parent) {
1162         mParentTab = parent;
1163         // This tab may have been freed due to low memory. If that is the case,
1164         // the parent tab index is already saved. If we are changing that index
1165         // (most likely due to removing the parent tab) we must update the
1166         // parent tab index in the saved Bundle.
1167         if (mSavedState != null) {
1168             if (parent == null) {
1169                 mSavedState.remove(PARENTTAB);
1170             } else {
1171                 mSavedState.putInt(PARENTTAB, mActivity.getTabControl()
1172                         .getTabIndex(parent));
1173             }
1174         }
1175     }
1176
1177     /**
1178      * When a Tab is created through the content of another Tab, then we
1179      * associate the Tabs.
1180      * @param child the Tab that was created from this Tab
1181      */
1182     void addChildTab(Tab child) {
1183         if (mChildTabs == null) {
1184             mChildTabs = new Vector<Tab>();
1185         }
1186         mChildTabs.add(child);
1187         child.setParentTab(this);
1188     }
1189
1190     Vector<Tab> getChildTabs() {
1191         return mChildTabs;
1192     }
1193
1194     void resume() {
1195         if (mMainView != null) {
1196             mMainView.onResume();
1197             if (mSubView != null) {
1198                 mSubView.onResume();
1199             }
1200         }
1201     }
1202
1203     void pause() {
1204         if (mMainView != null) {
1205             mMainView.onPause();
1206             if (mSubView != null) {
1207                 mSubView.onPause();
1208             }
1209         }
1210     }
1211
1212     void putInForeground() {
1213         mInForeground = true;
1214         resume();
1215         mMainView.setOnCreateContextMenuListener(mActivity);
1216         if (mSubView != null) {
1217             mSubView.setOnCreateContextMenuListener(mActivity);
1218         }
1219         // Show the pending error dialog if the queue is not empty
1220         if (mQueuedErrors != null && mQueuedErrors.size() >  0) {
1221             showError(mQueuedErrors.getFirst());
1222         }
1223     }
1224
1225     void putInBackground() {
1226         mInForeground = false;
1227         pause();
1228         mMainView.setOnCreateContextMenuListener(null);
1229         if (mSubView != null) {
1230             mSubView.setOnCreateContextMenuListener(null);
1231         }
1232     }
1233
1234     /**
1235      * Return the top window of this tab; either the subwindow if it is not
1236      * null or the main window.
1237      * @return The top window of this tab.
1238      */
1239     WebView getTopWindow() {
1240         if (mSubView != null) {
1241             return mSubView;
1242         }
1243         return mMainView;
1244     }
1245
1246     /**
1247      * Return the main window of this tab. Note: if a tab is freed in the
1248      * background, this can return null. It is only guaranteed to be
1249      * non-null for the current tab.
1250      * @return The main WebView of this tab.
1251      */
1252     WebView getWebView() {
1253         return mMainView;
1254     }
1255
1256     /**
1257      * Return the subwindow of this tab or null if there is no subwindow.
1258      * @return The subwindow of this tab or null.
1259      */
1260     WebView getSubWebView() {
1261         return mSubView;
1262     }
1263
1264     /**
1265      * @return The geolocation permissions prompt for this tab.
1266      */
1267     GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
1268         return mGeolocationPermissionsPrompt;
1269     }
1270
1271     /**
1272      * @return The application id string
1273      */
1274     String getAppId() {
1275         return mAppId;
1276     }
1277
1278     /**
1279      * Set the application id string
1280      * @param id
1281      */
1282     void setAppId(String id) {
1283         mAppId = id;
1284     }
1285
1286     /**
1287      * @return The original url associated with this Tab
1288      */
1289     String getOriginalUrl() {
1290         return mOriginalUrl;
1291     }
1292
1293     /**
1294      * Set the original url associated with this tab
1295      */
1296     void setOriginalUrl(String url) {
1297         mOriginalUrl = url;
1298     }
1299
1300     /**
1301      * Get the url of this tab. Valid after calling populatePickerData, but
1302      * before calling wipePickerData, or if the webview has been destroyed.
1303      * @return The WebView's url or null.
1304      */
1305     String getUrl() {
1306         if (mPickerData != null) {
1307             return mPickerData.mUrl;
1308         }
1309         return null;
1310     }
1311
1312     /**
1313      * Get the title of this tab. Valid after calling populatePickerData, but
1314      * before calling wipePickerData, or if the webview has been destroyed. If
1315      * the url has no title, use the url instead.
1316      * @return The WebView's title (or url) or null.
1317      */
1318     String getTitle() {
1319         if (mPickerData != null) {
1320             return mPickerData.mTitle;
1321         }
1322         return null;
1323     }
1324
1325     /**
1326      * Get the favicon of this tab. Valid after calling populatePickerData, but
1327      * before calling wipePickerData, or if the webview has been destroyed.
1328      * @return The WebView's favicon or null.
1329      */
1330     Bitmap getFavicon() {
1331         if (mPickerData != null) {
1332             return mPickerData.mFavicon;
1333         }
1334         return null;
1335     }
1336
1337     /**
1338      * Return the tab's error console. Creates the console if createIfNEcessary
1339      * is true and we haven't already created the console.
1340      * @param createIfNecessary Flag to indicate if the console should be
1341      *            created if it has not been already.
1342      * @return The tab's error console, or null if one has not been created and
1343      *         createIfNecessary is false.
1344      */
1345     ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
1346         if (createIfNecessary && mErrorConsole == null) {
1347             mErrorConsole = new ErrorConsoleView(mActivity);
1348             mErrorConsole.setWebView(mMainView);
1349         }
1350         return mErrorConsole;
1351     }
1352
1353     /**
1354      * If this Tab was created through another Tab, then this method returns
1355      * that Tab.
1356      * @return the Tab parent or null
1357      */
1358     public Tab getParentTab() {
1359         return mParentTab;
1360     }
1361
1362     /**
1363      * Return whether this tab should be closed when it is backing out of the
1364      * first page.
1365      * @return TRUE if this tab should be closed when exit.
1366      */
1367     boolean closeOnExit() {
1368         return mCloseOnExit;
1369     }
1370
1371     /**
1372      * Saves the current lock-icon state before resetting the lock icon. If we
1373      * have an error, we may need to roll back to the previous state.
1374      */
1375     void resetLockIcon(String url) {
1376         mPrevLockIconType = mLockIconType;
1377         mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
1378         if (URLUtil.isHttpsUrl(url)) {
1379             mLockIconType = BrowserActivity.LOCK_ICON_SECURE;
1380         }
1381     }
1382
1383     /**
1384      * Reverts the lock-icon state to the last saved state, for example, if we
1385      * had an error, and need to cancel the load.
1386      */
1387     void revertLockIcon() {
1388         mLockIconType = mPrevLockIconType;
1389     }
1390
1391     /**
1392      * @return The tab's lock icon type.
1393      */
1394     int getLockIconType() {
1395         return mLockIconType;
1396     }
1397
1398     /**
1399      * @return TRUE if onPageStarted is called while onPageFinished is not
1400      *         called yet.
1401      */
1402     boolean inLoad() {
1403         return mInLoad;
1404     }
1405
1406     // force mInLoad to be false. This should only be called before closing the
1407     // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly.
1408     void clearInLoad() {
1409         mInLoad = false;
1410     }
1411
1412     void populatePickerData() {
1413         if (mMainView == null) {
1414             populatePickerDataFromSavedState();
1415             return;
1416         }
1417
1418         // FIXME: The only place we cared about subwindow was for
1419         // bookmarking (i.e. not when saving state). Was this deliberate?
1420         final WebBackForwardList list = mMainView.copyBackForwardList();
1421         final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
1422         populatePickerData(item);
1423     }
1424
1425     // Populate the picker data using the given history item and the current top
1426     // WebView.
1427     private void populatePickerData(WebHistoryItem item) {
1428         mPickerData = new PickerData();
1429         if (item != null) {
1430             mPickerData.mUrl = item.getUrl();
1431             mPickerData.mTitle = item.getTitle();
1432             mPickerData.mFavicon = item.getFavicon();
1433             if (mPickerData.mTitle == null) {
1434                 mPickerData.mTitle = mPickerData.mUrl;
1435             }
1436         }
1437     }
1438
1439     // Create the PickerData and populate it using the saved state of the tab.
1440     void populatePickerDataFromSavedState() {
1441         if (mSavedState == null) {
1442             return;
1443         }
1444         mPickerData = new PickerData();
1445         mPickerData.mUrl = mSavedState.getString(CURRURL);
1446         mPickerData.mTitle = mSavedState.getString(CURRTITLE);
1447     }
1448
1449     void clearPickerData() {
1450         mPickerData = null;
1451     }
1452
1453     /**
1454      * Get the saved state bundle.
1455      * @return
1456      */
1457     Bundle getSavedState() {
1458         return mSavedState;
1459     }
1460
1461     /**
1462      * Set the saved state.
1463      */
1464     void setSavedState(Bundle state) {
1465         mSavedState = state;
1466     }
1467
1468     /**
1469      * @return TRUE if succeed in saving the state.
1470      */
1471     boolean saveState() {
1472         // If the WebView is null it means we ran low on memory and we already
1473         // stored the saved state in mSavedState.
1474         if (mMainView == null) {
1475             return mSavedState != null;
1476         }
1477
1478         mSavedState = new Bundle();
1479         final WebBackForwardList list = mMainView.saveState(mSavedState);
1480         if (list != null) {
1481             final File f = new File(mActivity.getTabControl().getThumbnailDir(),
1482                     mMainView.hashCode() + "_pic.save");
1483             if (mMainView.savePicture(mSavedState, f)) {
1484                 mSavedState.putString(CURRPICTURE, f.getPath());
1485             }
1486         }
1487
1488         // Store some extra info for displaying the tab in the picker.
1489         final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
1490         populatePickerData(item);
1491
1492         if (mPickerData.mUrl != null) {
1493             mSavedState.putString(CURRURL, mPickerData.mUrl);
1494         }
1495         if (mPickerData.mTitle != null) {
1496             mSavedState.putString(CURRTITLE, mPickerData.mTitle);
1497         }
1498         mSavedState.putBoolean(CLOSEONEXIT, mCloseOnExit);
1499         if (mAppId != null) {
1500             mSavedState.putString(APPID, mAppId);
1501         }
1502         if (mOriginalUrl != null) {
1503             mSavedState.putString(ORIGINALURL, mOriginalUrl);
1504         }
1505         // Remember the parent tab so the relationship can be restored.
1506         if (mParentTab != null) {
1507             mSavedState.putInt(PARENTTAB, mActivity.getTabControl().getTabIndex(
1508                     mParentTab));
1509         }
1510         return true;
1511     }
1512
1513     /*
1514      * Restore the state of the tab.
1515      */
1516     boolean restoreState(Bundle b) {
1517         if (b == null) {
1518             return false;
1519         }
1520         // Restore the internal state even if the WebView fails to restore.
1521         // This will maintain the app id, original url and close-on-exit values.
1522         mSavedState = null;
1523         mPickerData = null;
1524         mCloseOnExit = b.getBoolean(CLOSEONEXIT);
1525         mAppId = b.getString(APPID);
1526         mOriginalUrl = b.getString(ORIGINALURL);
1527
1528         final WebBackForwardList list = mMainView.restoreState(b);
1529         if (list == null) {
1530             return false;
1531         }
1532         if (b.containsKey(CURRPICTURE)) {
1533             final File f = new File(b.getString(CURRPICTURE));
1534             mMainView.restorePicture(b, f);
1535             f.delete();
1536         }
1537         return true;
1538     }
1539 }