OSDN Git Service

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