OSDN Git Service

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